diff options
Diffstat (limited to 'plugins/python')
-rw-r--r-- | plugins/python/Makefile.am | 2 | ||||
-rw-r--r-- | plugins/python/androperms/Makefile.am | 12 | ||||
-rw-r--r-- | plugins/python/androperms/__init__.py | 2 | ||||
-rw-r--r-- | plugins/python/androperms/androperms.py | 41 | ||||
-rw-r--r-- | plugins/python/androperms/defs.py | 53 | ||||
-rw-r--r-- | plugins/python/androperms/manifest.py | 75 | ||||
-rw-r--r-- | plugins/python/androperms/parser.py | 328 | ||||
-rw-r--r-- | plugins/python/androperms/reader.py | 41 | ||||
-rw-r--r-- | plugins/python/androperms/stack.py | 108 | ||||
-rw-r--r-- | plugins/python/androperms/string.py | 82 | ||||
-rw-r--r-- | plugins/python/apkfiles/apkfiles.py | 7 |
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() |