# encoding: utf-8
__author__ = "Nils Tobias Schmidt"
__email__ = "schmidt89 at informatik.uni-marburg.de"
from collections import OrderedDict
from xml.dom import minidom
from androguard.core.bytecodes.apk import AXMLPrinter
from androlyze.loader.exception import CouldNotOpenApk
from androlyze.log.Log import log
from androlyze.model.analysis.result.StaticResultKeys import *
from androlyze.model.android.Constants import ANDROID_FILE_EXTENSION, \
MANIFEST_ACTIVITY, MANIFEST_SERVICE, MANIFEST_RECEIVER, MANIFEST_PROVIDER, \
MANIFEST_NS
from androlyze.model.script.impl.manifest.components import get_components_cache, \
component_key_2_intent_key
from androlyze.util.Util import utc2local
[docs]class Apk(Hashable):
''' Defines an object for the basic attributes of an Apk file.
See Also
--------
http://developer.android.com/guide/topics/manifest/manifest-intro.html
'''
def __init__(self):
Hashable.__init__(self)
self._package_name = None
self._version_name = None
self._import_date = None
self._tag = None
self._size_app_code = 0
self._build_date = None
def __eq__(self, other):
if isinstance(other, Apk):
return self is other or self.hash == other.hash
return False
[docs] def get_import_date(self):
return self._import_date
[docs] def set_import_date(self, value):
self._import_date = value
[docs] def del_import_date(self):
del self._import_date
[docs] def get_package_name(self):
return self._package_name
[docs] def get_version_name(self):
return self._version_name
[docs] def set_package_name(self, value):
self._package_name = value
[docs] def set_version_name(self, value):
self._version_name = value
[docs] def del_package_name(self):
del self._package_name
[docs] def del_version_name(self):
del self._version_name
[docs] def get_tag(self):
return self._tag
[docs] def set_tag(self, value):
self._tag = value
[docs] def del_tag(self):
del self._tag
[docs] def get_hash(self):
'''
Get the sha256 message digest of the apk file
and store it.
Returns
-------
str
sha256 message digest as hexstring
None
If path is None
Raises
------
CouldNotOpenApk
If the APK could no be opened
'''
try:
return Hashable.get_hash(self)
except OSError as e:
raise CouldNotOpenApk(self.path, e)
[docs] def get_size_app_code(self):
return self._size_app_code
[docs] def set_size_app_code(self, value):
self._size_app_code = value
[docs] def del_size_app_code(self):
del self._size_app_code
[docs] def get_build_date(self):
''' Get the date of the classes.dex file (build date).
Returns
-------
datetime.datetime
The build date.
'''
return self._build_date
[docs] def set_build_date(self, value):
self._build_date = value
[docs] def del_build_date(self):
del self._build_date
package_name = property(get_package_name, set_package_name, del_package_name, "str - Package name of the apk. Unique apk identifier (at least in the store)")
version_name = property(get_version_name, set_version_name, del_version_name, "str - version")
import_date = property(get_import_date, set_import_date, del_import_date, " datetime.datetime : the import date (default is None)")
tag = property(get_tag, set_tag, del_tag, "str : some tag")
size_app_code = property(get_size_app_code, set_size_app_code, del_size_app_code, "int : size of the uncompressed .dex file")
build_date = property(get_build_date, set_build_date, del_build_date, "datetime.datetime : the build date (last timestamp of classes.dex) in zipfile")
def __str__(self):
SEP = '\n\t'
res = self.short_description()
res += '%ssha256: %s' % (SEP, self.hash)
if self.import_date is not None:
res += '%simport date: %s' % (SEP, utc2local(self.import_date))
if self.path is not None:
res += '%spath: %s' % (SEP, self.path)
if self.tag is not None:
res += "%stag: %s" % (SEP, self.tag)
if self.size_app_code is not None:
res += "%scode size: %d" % (SEP, self.size_app_code)
if self.build_date is not None:
res += "%sbuild date: %s" % (SEP, utc2local(self.build_date))
return res
def __repr__(self):
return '%s(%s, %s, %s, %s, %s, %s)' % (self.__class__.__name__, self.package_name, self.version_name, self.hash, self.import_date, self.tag, self.build_date)
def __hash__(self):
return self.hash
def __ne__(self, other):
return not self == other
[docs] def short_description(self):
return '%s %s' % (self.package_name, self.version_name)
[docs] def detailed_description(self):
''' Get a detailed description of the `Apk`. Includes package name, version name, hash, import date and path '''
return unicode(self)
[docs] def get_apk_filename_from_manifest(self):
''' Build the filename based on the manifest information (packageName and versionName) '''
return '%s_%s.%s' % (self.package_name, self.version_name, ANDROID_FILE_EXTENSION)
[docs] def app_code_comparator(self, other):
''' Comparator for app code size '''
if None in (self.size_app_code, other.size_app_code):
return 0
return self.size_app_code - other.size_app_code
[docs] def get_manifest_components_with_intent_filter(self):
'''
Get the package names which define an intent-filter.
Returns
-------
set<str>
Set of package names.
'''
res = set()
# intents
components_cache = get_components_cache(self)
for k, package_names in components_cache.items():
for package_name in package_names:
# get intent filter for activity, service or receiver
intent_key = component_key_2_intent_key(k)
package_intents = self.get_intent_filters(intent_key, package_name)
if package_intents:
res.add(package_name)
return res
[docs] def get_manifest_intent_filters(self, package_name, dalvik_syntax = True):
''' Check if the component specified through the `package_name` is callable from outside.
Parameters
----------
apk : Apk
package_name: str
dalvik_syntax: bool, optional (default is True)
Expects the package name in the dalvik bytecode form like e.g. ("Lde/nachtmaar/myapp/myclass;")
Returns
-------
list<dict>
Lists the public components with their intent filters
Example
-------
>>> get_manifest_intent_filters('Lorg.example.sqldemo.SQLDemo;')
[{'activity': {'action': [u'android.intent.action.MAIN'], 'category': [u'android.intent.category.LAUNCHER']}}]
'''
if dalvik_syntax:
package_name = package_name[1:]
package_name = package_name.replace("/", ".")
package_name = package_name[:-1]
res = []
for component_name in (MANIFEST_ACTIVITY, MANIFEST_SERVICE, MANIFEST_RECEIVER, MANIFEST_PROVIDER):
intent_filters = self.get_intent_filters(component_name, package_name)
if intent_filters:
res.append({component_name : intent_filters})
return res
[docs] def get_manifest_public_components(self):
'''
Get the package names which are publicly available.
Either through an intent or through an explicit export
Returns
-------
set<str>
Set of package names.
'''
return self.get_manifest_components_with_intent_filter().union(self.get_manifest_exported_components())
# TODO:
[docs] def get_manifest_exported_components(self):
'''
Get the package names which are exported.
Therefore the manifest looks like:
"
<activity android:name=".SQLDemo"
android:exported="true">
"
Returns
-------
set<str>
Set of package names.
'''
pns = set()
for i in self.zip.namelist():
if i == "AndroidManifest.xml":
ap = AXMLPrinter(self.zip.read(i))
dom = minidom.parseString(ap.get_buff())
for component_type in (MANIFEST_ACTIVITY, MANIFEST_PROVIDER, MANIFEST_RECEIVER, MANIFEST_SERVICE):
component_tags = dom.getElementsByTagName(component_type)
# check that tag is available
for component_tag in component_tags:
component_attributes = component_tag.attributes
component_name = component_attributes.getNamedItemNS(MANIFEST_NS, "name").nodeValue
exported = False
exported_tag = component_attributes.getNamedItemNS(MANIFEST_NS, "exported")
if exported_tag:
exported = exported_tag.nodeValue
if exported.lower() == "true":
pns.add(component_name)
return pns