summaryrefslogtreecommitdiff
path: root/plugins/python
diff options
context:
space:
mode:
authorCyrille Bagard <nocbos@gmail.com>2012-08-03 13:03:26 (GMT)
committerCyrille Bagard <nocbos@gmail.com>2012-08-03 13:03:26 (GMT)
commitb7c83221f2a60be8ee5d44a7599dbe6869af005f (patch)
tree814e294533920d18f8734baa9aaef47c676e520a /plugins/python
parent7d2b7ca95966c2d687526cd75a96d1ea67d3f503 (diff)
Loaded the permissions used by an APK file.
git-svn-id: svn://svn.gna.org/svn/chrysalide/trunk@255 abbe820e-26c8-41b2-8c08-b7b2b41f8b0a
Diffstat (limited to 'plugins/python')
-rw-r--r--plugins/python/Makefile.am2
-rw-r--r--plugins/python/androperms/Makefile.am12
-rw-r--r--plugins/python/androperms/__init__.py2
-rw-r--r--plugins/python/androperms/androperms.py41
-rw-r--r--plugins/python/androperms/defs.py53
-rw-r--r--plugins/python/androperms/manifest.py75
-rw-r--r--plugins/python/androperms/parser.py328
-rw-r--r--plugins/python/androperms/reader.py41
-rw-r--r--plugins/python/androperms/stack.py108
-rw-r--r--plugins/python/androperms/string.py82
-rw-r--r--plugins/python/apkfiles/apkfiles.py7
11 files changed, 748 insertions, 3 deletions
diff --git a/plugins/python/Makefile.am b/plugins/python/Makefile.am
index efbb704..3583a21 100644
--- a/plugins/python/Makefile.am
+++ b/plugins/python/Makefile.am
@@ -1,2 +1,2 @@
-SUBDIRS = apkfiles
+SUBDIRS = androperms apkfiles
diff --git a/plugins/python/androperms/Makefile.am b/plugins/python/androperms/Makefile.am
new file mode 100644
index 0000000..3d1755c
--- /dev/null
+++ b/plugins/python/androperms/Makefile.am
@@ -0,0 +1,12 @@
+
+andropermsdir = $(datadir)/openida/plugins/python/androperms
+
+androperms_DATA = \
+ __init__.py \
+ androperms.py \
+ defs.py \
+ manifest.py \
+ parser.py \
+ reader.py \
+ stack.py \
+ string.py
diff --git a/plugins/python/androperms/__init__.py b/plugins/python/androperms/__init__.py
new file mode 100644
index 0000000..8a5a159
--- /dev/null
+++ b/plugins/python/androperms/__init__.py
@@ -0,0 +1,2 @@
+
+from androperms import AndroPerms as androperms
diff --git a/plugins/python/androperms/androperms.py b/plugins/python/androperms/androperms.py
new file mode 100644
index 0000000..f85d402
--- /dev/null
+++ b/plugins/python/androperms/androperms.py
@@ -0,0 +1,41 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+from pychrysalide import Plugin
+from manifest import AndroidManifest
+from xml.dom import minidom
+
+import zipfile
+
+
+class AndroPerms(Plugin):
+ """List all permissions given to an APK files."""
+
+
+ def get_action(self):
+ """Register the plugin for given actions."""
+
+ return Plugin.PGA_DISASS_PROCESS
+
+
+ def execute_on_binary(self, binary, action):
+ """Process once a binary is disassembled."""
+
+ zf = zipfile.ZipFile(binary.get_filename())
+
+ f = zf.open('AndroidManifest.xml', 'r')
+ data = f.read()
+ f.closed
+
+ manifest = AndroidManifest(data)
+ xml = minidom.parseString(manifest.getXML())
+
+ print
+ print "Permissions for ", binary.get_filename(), " :"
+ print "-------------"
+ print
+
+ for p in xml.getElementsByTagName("uses-permission"):
+ print p.getAttribute("android:name")
+
+ print
diff --git a/plugins/python/androperms/defs.py b/plugins/python/androperms/defs.py
new file mode 100644
index 0000000..e23511e
--- /dev/null
+++ b/plugins/python/androperms/defs.py
@@ -0,0 +1,53 @@
+#!/usr/bin/python -u
+# -*- coding: utf-8 -*-
+
+
+ATTRIBUTE_IX_NAMESPACE_URI = 0
+ATTRIBUTE_IX_NAME = 1
+ATTRIBUTE_IX_VALUE_STRING = 2
+ATTRIBUTE_IX_VALUE_TYPE = 3
+ATTRIBUTE_IX_VALUE_DATA = 4
+ATTRIBUTE_LENGHT = 5
+
+TYPE_NULL = 0
+TYPE_REFERENCE = 1
+TYPE_ATTRIBUTE = 2
+TYPE_STRING = 3
+TYPE_FLOAT = 4
+TYPE_DIMENSION = 5
+TYPE_FRACTION = 6
+TYPE_FIRST_INT = 16
+TYPE_INT_DEC = 16
+TYPE_INT_HEX = 17
+TYPE_INT_BOOLEAN = 18
+TYPE_FIRST_COLOR_INT = 28
+TYPE_INT_COLOR_ARGB8 = 28
+TYPE_INT_COLOR_RGB8 = 29
+TYPE_INT_COLOR_ARGB4 = 30
+TYPE_INT_COLOR_RGB4 = 31
+TYPE_LAST_COLOR_INT = 31
+TYPE_LAST_INT = 31
+
+RADIX_MULTS = [ 0.00390625, 3.051758E-005, 1.192093E-007, 4.656613E-010 ]
+DIMENSION_UNITS = [ "px", "dip", "sp", "pt", "in", "mm", "", "" ]
+FRACTION_UNITS = [ "%", "%p", "", "", "", "", "", "" ]
+COMPLEX_UNIT_MASK = 15
+
+
+CHUNK_AXML_FILE = 0x00080003
+CHUNK_RESOURCEIDS = 0x00080180
+CHUNK_XML_FIRST = 0x00100100
+CHUNK_XML_START_NAMESPACE = 0x00100100
+CHUNK_XML_END_NAMESPACE = 0x00100101
+CHUNK_XML_START_TAG = 0x00100102
+CHUNK_XML_END_TAG = 0x00100103
+CHUNK_XML_TEXT = 0x00100104
+CHUNK_XML_LAST = 0x00100104
+CHUNK_TYPE = 0x001C0001
+
+
+START_DOCUMENT = 0
+END_DOCUMENT = 1
+START_TAG = 2
+END_TAG = 3
+TEXT = 4
diff --git a/plugins/python/androperms/manifest.py b/plugins/python/androperms/manifest.py
new file mode 100644
index 0000000..63536b2
--- /dev/null
+++ b/plugins/python/androperms/manifest.py
@@ -0,0 +1,75 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+from defs import *
+from reader import AXMLReader
+from parser import AXMLParser
+
+
+class AndroidManifest():
+
+
+ def __init__(self, data):
+
+ self._buffer = ""
+
+ reader = AXMLReader(data)
+ parser = AXMLParser(reader)
+
+ has_ns = False
+ empty = False
+
+ while 1 :
+
+ tag = parser.next()
+
+ if tag == START_DOCUMENT :
+ self._buffer += '<?xml version="1.0" encoding="utf-8"?>\n'
+
+ elif tag == START_TAG:
+
+ if empty:
+ self._buffer += '>\n'
+
+ self._buffer += ' ' * (parser._namespaces.getDepth() - 2)
+ self._buffer += "<%s%s" % (parser.getTagPrefix(), parser.getTagName())
+
+ if not has_ns:
+ self._buffer += ' xmlns:%s="%s"' % (parser.getNamespacePrefix(0), parser.getNamespaceUri(0))
+ has_ns = True
+
+ for i in range(0, parser.countAttributes()):
+ self._buffer += ' %s%s="%s"' % (parser.getAttribPrefix(i), parser.getAttribName(i), parser.getAttribValue(i))
+
+ empty = True
+
+ elif tag == END_TAG:
+
+ if empty:
+ self._buffer += '/>\n'
+ empty = False
+
+ else:
+ self._buffer += ' ' * (parser._namespaces.getDepth() - 2)
+ self._buffer += "</%s%s>\n" % (parser.getTagPrefix(), parser.getTagName())
+
+ elif tag == TEXT:
+
+ if empty:
+ self._buffer += '>\n'
+ empty = False
+
+ self._buffer += ' ' * (parser._namespaces.getDepth() - 1)
+ self._buffer += "%s\n" % parser.getText()
+
+ elif tag == END_DOCUMENT :
+ break
+
+ else:
+ break
+
+
+ def getXML(self):
+ """Provide the XML content."""
+
+ return self._buffer
diff --git a/plugins/python/androperms/parser.py b/plugins/python/androperms/parser.py
new file mode 100644
index 0000000..1939bbe
--- /dev/null
+++ b/plugins/python/androperms/parser.py
@@ -0,0 +1,328 @@
+#!/usr/bin/python -u
+# -*- coding: utf-8 -*-
+
+
+from defs import *
+from stack import NamespaceStack
+from string import StringBlock
+
+from struct import unpack
+
+
+class AXMLParser():
+
+
+ def __init__(self, reader):
+
+ self._reader = reader
+
+ magic = reader.readInt()
+ if magic != CHUNK_AXML_FILE:
+ raise Exception('Bad Magic Number (0x%08lx)!' % magic)
+
+ # chunkSize
+ reader.skipInt()
+
+ self._strings = StringBlock(reader)
+ self._namespaces = NamespaceStack()
+ self._operational = True
+
+ self.resetEventInfo()
+
+
+ def resetEventInfo(self):
+
+ self._event = -1
+ self._line_number = -1
+ self._name = -1
+ self._namespace_uri = -1
+ self._attributes = None
+ self._id_attrib = -1
+ self._class_attrib = -1
+ self._style_attrib = -1
+
+ self._decreaseDepth = False
+
+
+
+ def next(self):
+
+ self.doNext()
+ return self._event
+
+
+ def doNext(self):
+
+
+
+
+
+ event = self._event
+
+
+ while True:
+
+ if self._decreaseDepth:
+ self._decreaseDepth = False
+ self._namespaces.decDepth()
+
+ # Fake END_DOCUMENT event.
+ if event == END_TAG and self._namespaces.getDepth() == 1 and self._namespaces.count() == 0:
+ self._event = END_DOCUMENT
+ break
+
+ if event == START_DOCUMENT:
+ # Fake event, see CHUNK_XML_START_TAG handler.
+ chunk = CHUNK_XML_START_TAG
+
+ else:
+ chunk = self._reader.readInt()
+
+ if chunk == CHUNK_RESOURCEIDS:
+
+ size = self._reader.readInt()
+ if size < 8 or size % 4 != 0:
+ raise Exception('Invalid resource ids size (%d).' % size)
+
+ self._resource_ids = self._reader.readIntArray(size / 4 - 2)
+
+ continue
+
+ if chunk < CHUNK_XML_FIRST or chunk > CHUNK_XML_LAST:
+ raise Exception('Invalid chunk type 0x%08lx.' % chunk)
+
+ # Fake START_DOCUMENT event.
+ if chunk == CHUNK_XML_START_TAG and event == -1:
+ self._event = START_DOCUMENT
+ break
+
+ # Common header.
+ self._reader.skipInt() # chunkSize
+ self._line_number = self._reader.readInt()
+ self._reader.skipInt() # 0xffffffff
+
+ if chunk == CHUNK_XML_START_NAMESPACE or chunk == CHUNK_XML_END_NAMESPACE:
+
+ if chunk == CHUNK_XML_START_NAMESPACE:
+
+ prefix = self._reader.readInt()
+ uri = self._reader.readInt()
+ self._namespaces.push(prefix, uri)
+
+ else:
+
+ self._reader.skipInt() # prefix
+ self._reader.skipInt() # uri
+ self._namespaces.pop()
+
+ continue
+
+ elif chunk == CHUNK_XML_START_TAG:
+
+ self._namespace_uri = self._reader.readInt()
+ self._name = self._reader.readInt()
+ self._reader.skipInt() # flags ?
+
+ attribs_count = self._reader.readInt()
+ self._id_attrib = (attribs_count >> 16) - 1
+ attribs_count &= 0xffff
+
+ self._class_attrib = self._reader.readInt()
+ self._style_attrib = (self._class_attrib >> 16) - 1
+ self._class_attrib = (self._class_attrib & 0xffff) - 1
+
+ self._attributes = self._reader.readIntArray(attribs_count * ATTRIBUTE_LENGHT)
+
+ for i in range(ATTRIBUTE_IX_VALUE_TYPE, len(self._attributes), ATTRIBUTE_LENGHT):
+ self._attributes[i] >>= 24
+
+ self._namespaces.incDepth()
+ self._event = START_TAG
+
+ break
+
+ elif chunk == CHUNK_XML_END_TAG:
+
+ self._namespaceUri = self._reader.readInt()
+ self._name = self._reader.readInt()
+
+ self._event = END_TAG
+ self._decreaseDepth = True
+
+ break
+
+ elif chunk == CHUNK_XML_TEXT:
+
+ self._name = self._reader.readInt()
+ self._reader.skipInt() # ???
+ self._reader.skipInt() # ???
+
+ self._event=TEXT
+
+ break
+
+ else:
+ raise Exception('Unknown chunck (0x%08lx)' % chunk)
+
+
+ ### ESPACES ###
+
+ def getNamespacePrefix(self, index):
+
+ name = self._namespaces.getPrefix(index)
+
+ if name == -1:
+ return ''
+
+ else:
+ return self._strings.getRaw(name)
+
+
+ def getNamespaceUri(self, index):
+
+ name = self._namespaces.getUri(index)
+
+ if name == -1:
+ return ''
+
+ else:
+ return self._strings.getRaw(name)
+
+
+ ### NAMES ###
+
+ def getTagPrefix(self):
+ """Provide the prefix linked to START_TAG or END_TAG."""
+
+ name = self._namespaces.findPrefix(self._namespace_uri)
+
+ if name == -1:
+ return ''
+
+ else:
+ return self._strings.getRaw(name) + ':'
+
+
+ def getTagName(self):
+ """Provide the name linked to START_TAG or END_TAG."""
+
+ if self._name == -1 or (self._event != START_TAG and self._event != END_TAG):
+ raise Exception('Invalid tag name.')
+
+ return self._strings.getRaw(self._name)
+
+
+ def getText(self):
+ """Provide the content linked to TEXT."""
+
+ if self._name == -1 or self._event != START_TEXT:
+ raise Exception('Invalid text content.')
+
+ return self._strings.getRaw(self._name)
+
+
+ ### ATRIBUTES ###
+
+ def countAttributes(self):
+ """Count the properties of the current tag."""
+
+ if self._event != START_TAG:
+ raise Exception('Invalid event.')
+
+ return len(self._attributes) / ATTRIBUTE_LENGHT
+
+
+ def getAttribPrefix(self, index):
+ """Get the prefix of a given attribute."""
+
+ index *= ATTRIBUTE_LENGHT
+
+ if index >= len(self._attributes):
+ raise Exception('Bad attribute index.')
+
+ uri = self._attributes[index + ATTRIBUTE_IX_NAMESPACE_URI]
+ name = self._namespaces.findPrefix(uri)
+
+ if name == -1:
+ return ''
+
+ else:
+ return self._strings.getRaw(name) + ':'
+
+
+ def getAttribName(self, index):
+ """Get the name of a given attribute."""
+
+ index *= ATTRIBUTE_LENGHT
+
+ if index >= len(self._attributes):
+ raise Exception('Bad attribute index.')
+
+ name = self._attributes[index + ATTRIBUTE_IX_NAME]
+
+ if name == -1:
+ return '???'
+
+ else:
+ return self._strings.getRaw(name)
+
+
+ def getAttribValue(self, index):
+ """Get the value of a given attribute."""
+
+ index *= ATTRIBUTE_LENGHT
+
+ if index >= len(self._attributes):
+ raise Exception('Bad attribute index.')
+
+ vtype = self._attributes[index + ATTRIBUTE_IX_VALUE_TYPE]
+ vdata = self._attributes[index + ATTRIBUTE_IX_VALUE_DATA]
+
+ if vtype == TYPE_NULL:
+ return '???'
+
+ elif vtype == TYPE_REFERENCE:
+ return '@%s%08X' % (self.getPackage(vdata), vdata)
+
+ elif vtype == TYPE_ATTRIBUTE:
+ return '?%s%08X' % (self.getPackage(vdata), vdata)
+
+ if vtype == TYPE_STRING:
+ vdata = self._attributes[index + ATTRIBUTE_IX_VALUE_STRING]
+ return self._strings.getRaw(vdata)
+
+ elif vtype == TYPE_FLOAT:
+ return '%f' % unpack('=f', pack('=L', vdata))[0]
+
+ elif vtype == TYPE_DIMENSION:
+ return '%f%s' % (self.complexToFloat(vdata), DIMENSION_UNITS[vdata & COMPLEX_UNIT_MASK])
+
+ elif vtype == TYPE_FRACTION:
+ return '%f%s' % (self.complexToFloat(vdata), FRACTION_UNITS[vdata & COMPLEX_UNIT_MASK])
+
+ elif vtype == TYPE_INT_HEX:
+ return '0x%08x' % vdata
+
+ elif vtype == TYPE_INT_BOOLEAN:
+ if vdata == 0:
+ return 'false'
+ else:
+ return 'true'
+
+ elif vtype >= TYPE_FIRST_COLOR_INT and vtype <= TYPE_LAST_COLOR_INT:
+ return '#%08x' % vdata
+
+ elif vtype >= TYPE_FIRST_INT and vtype <= TYPE_LAST_INT:
+ return str(vdata)
+
+ return "<0x%x, 0x%02x>" % (vdata, vtype)
+
+
+ def complexToFloat(self, xcomplex):
+ return (float)(xcomplex & 0xffffff00) * RADIX_MULTS[(xcomplex >> 4) & 3];
+
+ def getPackage(self, id):
+ if id >> 24 == 1:
+ return "android:"
+ else:
+ return ""
diff --git a/plugins/python/androperms/reader.py b/plugins/python/androperms/reader.py
new file mode 100644
index 0000000..f126850
--- /dev/null
+++ b/plugins/python/androperms/reader.py
@@ -0,0 +1,41 @@
+#!/usr/bin/python -u
+# -*- coding: utf-8 -*-
+
+
+from struct import unpack
+
+
+class AXMLReader():
+ """Provide various read helpers."""
+
+ def __init__(self, data):
+
+ self._data = data
+
+ self._position = 0
+ self._length = len(self._data)
+
+
+ def readInt(self):
+ """Read a 4-bytes value."""
+
+ self.skipInt()
+
+ value = unpack('<L', self._data[self._position - 4 : self._position])[0]
+
+ return value
+
+
+ def skipInt(self):
+ """Skip a 4-bytes value."""
+
+ self._position += 4
+
+ if self._position > self._length:
+ raise Exception("Reader out of bound (%d > %d)!" % (self._position, self._length))
+
+
+ def readIntArray(self, length):
+ """Read an array composed of 4-bytes values."""
+
+ return [ self.readInt() for i in range(0, length) ]
diff --git a/plugins/python/androperms/stack.py b/plugins/python/androperms/stack.py
new file mode 100644
index 0000000..38431dc
--- /dev/null
+++ b/plugins/python/androperms/stack.py
@@ -0,0 +1,108 @@
+#!/usr/bin/python -u
+# -*- coding: utf-8 -*-
+
+
+class NamespaceStack():
+
+
+ def __init__(self):
+
+ # self.increaseDepth()
+
+
+ self._depth = 1
+
+ self._prefix_2_uri = {}
+ self._uri_2_prefix = {}
+
+ self._pairs = []
+
+ pass
+
+
+
+ def getDepth(self):
+
+ return self._depth
+
+
+ def incDepth(self):
+
+ self._depth += 1
+
+
+ def decDepth(self):
+
+ self._depth -= 1
+
+
+
+
+
+ def count(self):
+ """Provider the current number of active namespaces."""
+
+ return len(self._pairs)
+
+
+
+
+
+
+
+ def push(self, prefix, uri):
+
+ self._prefix_2_uri[prefix] = uri
+ self._uri_2_prefix[uri] = prefix
+
+ self._pairs.append((prefix, uri))
+
+ #print "PUSH", prefix, uri
+
+
+
+
+ def pop(self):
+
+ self._pairs.pop()
+
+ #print "POP"
+
+
+
+
+
+ def getPrefix(self, index):
+
+ if index < len(self._pairs):
+ return self._pairs[index][0]
+
+ else:
+ return -1
+
+
+ def findPrefix(self, uri):
+
+ if uri in self._uri_2_prefix:
+ return self._uri_2_prefix[uri]
+
+ else:
+ return -1
+
+
+ def getUri(self, index):
+
+ if index < len(self._pairs):
+ return self._pairs[index][1]
+
+ else:
+ return -1
+
+
+ def findUri(self, prefix):
+
+ if prefix in self._prefix_2_uri:
+ return self._prefix_2_uri[prefix]
+
+ else:
+ return -1
diff --git a/plugins/python/androperms/string.py b/plugins/python/androperms/string.py
new file mode 100644
index 0000000..09a7b93
--- /dev/null
+++ b/plugins/python/androperms/string.py
@@ -0,0 +1,82 @@
+#!/usr/bin/python -u
+# -*- coding: utf-8 -*-
+
+
+from defs import CHUNK_TYPE
+
+
+class StringBlock():
+
+
+ def __init__(self, reader):
+
+ magic = reader.readInt()
+ if magic != CHUNK_TYPE:
+ raise Exception("Bad Magic Number (0x%08lx)!" % magic)
+
+ chunk_size = reader.readInt()
+ str_count = reader.readInt()
+ style_offset_count = reader.readInt()
+ reader.readInt() # ???
+ str_offset = reader.readInt()
+ styles_offset = reader.readInt()
+
+ self._str_offsets = reader.readIntArray(str_count);
+ self._style_offsets = reader.readIntArray(style_offset_count);
+
+ if styles_offset == 0:
+ size = chunk_size - str_offset
+ else:
+ size = styles_offset - str_offset
+
+ if size % 4 != 0:
+ raise Exception("String data size is not multiple of 4 (%d)!" % size)
+
+ self._strings = reader.readIntArray(size / 4)
+
+ if styles_offset > 0:
+
+ size = chunk_size - styles_offset
+
+ if size % 4 != 0:
+ raise Exception("Style data size is not multiple of 4 (%d)!" % size)
+
+ self._styles = reader.readIntArray(size / 4)
+
+ self._str_data = [ self.getRaw(i) for i in range(self.count()) ]
+
+
+ def count(self):
+ """Count the number of strings in the current block."""
+
+ return len(self._str_offsets)
+
+
+ def getRaw(self, index):
+ """Provide a raw string (without any styling information) at specified index."""
+
+ if index < 0 or index >= len(self._str_offsets):
+ raise Exception("Invalid Index (%d)!" % index)
+
+ offset = self._str_offsets[index]
+ length = self.getShort(self._strings, offset)
+
+ data = ''
+
+ for i in range(length):
+ offset += 2
+ data += unichr(self.getShort(self._strings, offset))
+
+ return data
+
+
+ def getShort(self, array, offset):
+
+ value = array[offset / 4]
+
+ if ((offset % 4) / 2) == 0:
+ value &= 0xFFFF
+ else:
+ value >>= 16
+
+ return value
diff --git a/plugins/python/apkfiles/apkfiles.py b/plugins/python/apkfiles/apkfiles.py
index 38d0e59..7c05ca9 100644
--- a/plugins/python/apkfiles/apkfiles.py
+++ b/plugins/python/apkfiles/apkfiles.py
@@ -1,7 +1,7 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
-from pychrysa import Plugin
+from pychrysalide import Plugin
import zipfile
@@ -9,11 +9,13 @@ import zipfile
class ApkFiles(Plugin):
"""Open and process APK files."""
+
def get_action(self):
"""Register the plugin for given actions."""
return Plugin.PGA_FORMAT_MATCHER
+
def is_matching(self, filename, data):
"""Define if the given file can be handled."""
@@ -22,7 +24,8 @@ class ApkFiles(Plugin):
zf = zipfile.ZipFile(filename)
- if zf.namelist().count('classes.dex') > 0:
+ if zf.namelist().count('classes.dex') > 0 \
+ and zf.namelist().count('AndroidManifest.xml') > 0:
f = zf.open('classes.dex', 'r')
data = f.read()