def build_manifest_from_apk(file: File, extended_processing: bool, apk_path: str) -> AndroidManifest: apk = Aapt.get_apk_info(apk_path) activities = [] services = [] receivers = [] if extended_processing: manifest = Aapt.get_manifest_info(apk_path) activities = [AppActivity(name=activity) for activity in manifest["activities"]] services = [AppService(name=service) for service in manifest["services"]] receivers = [AppBroadcastReceiver(name=receiver) for receiver in manifest["receivers"]] return AndroidManifest( filename=file.get_file_name(), size=file.get_size(), md5hash=file.get_md5(), sha1hash=file.get_sha1(), sha256hash=file.get_sha256(), sha512hash=file.get_sha512(), package_name=apk["package_name"], version=AppVersion(code=apk["version"]["code"], name=apk["version"]["name"]), sdk=AppSdk( target_version=apk["sdk"]["target"], min_version=apk["sdk"]["min"], max_version=apk["sdk"]["max"] ), permissions=Aapt.get_app_permissions(apk_path), activities=activities, services=services, receivers=receivers )
def test_get_apk_info(self, mock_popen): dump_badging = b"package: name='com.example.app' versionCode='1' versionName='1.0' platformBuildVersionName='4'\n" \ b"sdkVersion:'10'\n" \ b"maxSdkVersion:'20'\n" \ b"targetSdkVersion:'15'" mock_popen.return_value = any_popen(dump_badging) apk = Aapt.get_apk_info("any-file-path") assert_popen_called_once(mock_popen) self.assertEqual( { "package_name": "com.example.app", "version": { "code": 1, "name": "1.0" }, "sdk": { "max": "20", "min": "10", "target": "15" } }, apk )
def test_execute_dump_xmltree(self, mock_popen): mock_popen.return_value = any_popen(b"any-result") result = Aapt._execute_dump_xmltree("any-file-path") assert_popen_called_once(mock_popen) self.assertEqual("any-result", result)
def test_extract_app_name_when_application_label_is_not_present_but_launchable_activity_label_is( self): app_name = Aapt._extract_app_name( "launchable-activity: name='com.example.app.HomeActivity' label='Example2'" ) self.assertEqual("Example2", app_name)
def test_get_app_permissions_when_dumb_permissions_fails(self, mock_popen): mock_popen.side_effect = RuntimeError() permissions = Aapt.get_app_permissions("any-file-path") assert_popen_called_once(mock_popen) self.assertEqual([], permissions)
def test_get_app_name_when_dumb_badging_fails(self, mock_popen): mock_popen.side_effect = RuntimeError() app_name = Aapt.get_app_name("any-file-path") assert_popen_called_once(mock_popen) self.assertEqual("", app_name)
def test_get_manifest_info(self, mock_popen): dump_xmltree = b"N: android=http://schemas.android.com/apk/res/android\n" \ b" E: manifest (line=2)\n" \ b" E: application (line=8)\n" \ b" E: activity (line=9)\n" \ b" A: android:name(0x01010003)=\"any-activity\" (Raw: \"any-activity\")\n" \ b" E: activity (line=15)\n" \ b" A: android:name(0x01010003)=\"any-other-activity\" (Raw: \"any-other-activity\")\n" \ b" E: service (line=25)\n" \ b" A: android:name(0x01010003)=\"any-service\" (Raw: \"any-service\")\n" \ b" E: service (line=26)\n" \ b" A: android:name(0x01010003)=\"any-other-service\" (Raw: \"any-other-service\")\n" \ b" E: receiver (line=28)\n" \ b" A: android:name(0x01010003)=\"any-receiver\" (Raw: \"any-receiver\")\n" \ b" E: receiver (line=29)\n" \ b" A: android:name(0x01010003)=\"any-other-receiver\" (Raw: \"any-other-receiver\")" mock_popen.return_value = any_popen(dump_xmltree) manifest = Aapt.get_manifest_info("any-file-path") assert_popen_called_once(mock_popen) self.assertEqual( { "activities": ["any-activity", "any-other-activity"], "services": ["any-service", "any-other-service"], "receivers": ["any-receiver", "any-other-receiver"] }, manifest)
def test_get_app_name(self, mock_popen): mock_popen.return_value = any_popen(b"application: label='Example' icon='res/ic_launcher.png'\n") app_name = Aapt.get_app_name("any-file-path") assert_popen_called_once(mock_popen) self.assertEqual("Example", app_name)
def parse(self, filepath: str, extended_processing: bool = True): """ :param filepath: path of the APK file :param extended_processing: (optional) whether should parse all information or only a summary. True by default. :return: the parsed APK file :raise: ApkParsingError if cannot parse the file as an APK """ self.logger.debug("Parsing APK file: filepath=\"%s\"", filepath) if not self.looks_like_apk(filepath): raise ApkParsingError file = self.file_parser.parse(filepath) cert = None manifest = None dex_files = [] other_files = [] with ZipFile(filepath) as apk: tmpdir = self.__create_temporary_directory(ApkParser.__TEMPORARY_DIR) for filename in apk.namelist(): entry_filepath = apk.extract(filename, tmpdir) self.logger.debug("Extracting APK resource %s to %s", filename, entry_filepath) try: if AndroidManifestParser.looks_like_manifest(filename): self.logger.debug("%s looks like an AndroidManifest.xml file", filename) manifest = self.manifest_parser.parse(entry_filepath, True, filepath, True) elif CertParser.looks_like_cert(filename): self.logger.debug("%s looks like a CERT file", filename) cert = self.__parse_cert(entry_filepath, filename, extended_processing) elif DexParser.looks_like_dex(filename): self.logger.debug("%s looks like a dex file", filename) dex = self.__parse_dex(entry_filepath, filename, extended_processing) dex_files.append(dex) else: self.logger.debug("%s looks like a generic file", filename) entry = self.__parse_file(entry_filepath, filename, extended_processing) if entry is not None: other_files.append(entry) except (AndroidManifestParsingError, CertParsingError, FileParsingError) as error: self.__remove_directory(tmpdir) raise ApkParsingError from error self.__remove_directory(tmpdir) if manifest is None or cert is None or not dex_files: raise ApkParsingError return APK( filename=file.get_file_name(), size=file.get_size(), md5hash=file.get_md5(), sha1hash=file.get_sha1(), sha256hash=file.get_sha256(), sha512hash=file.get_sha512(), app_name=Aapt.get_app_name(filepath), cert=cert, manifest=manifest, dex_files=dex_files, other_files=other_files )
def test_extract_app_name_when_application_label_is_present(self): dump_badging = "application-label:'Example0'\n" \ "application: label='Example1' icon='res/drawable-mdpi-v4/ic_launcher.png'\n" \ "launchable-activity: name='com.example.app.HomeActivity' label='Example2' icon=''" app_name = Aapt._extract_app_name(dump_badging) self.assertEqual("Example1", app_name)
def test_extract_sdk_target_when_present(self): dump_badging = "sdkVersion:'10'\n" \ "maxSdkVersion:'20'\n" \ "targetSdkVersion:'15'" sdk_target = Aapt._extract_sdk_target_version(dump_badging) self.assertEqual("15", sdk_target)
def test_extract_sdk_max_version_when_present(self): dump_badging = "sdkVersion:'10'\n" \ "maxSdkVersion:'20'\n" \ "targetSdkVersion:'15'" max_version = Aapt._extract_sdk_max_version(dump_badging) self.assertEqual("20", max_version)
def test_get_app_permissions(self): for filename in self.files: # When: permissions = Aapt.get_app_permissions(self.files[filename]) # Then: self.assertEqual(self.files_properties[filename]["permissions"], permissions)
def test_get_app_name(self): for filename in self.files: # When: files = Aapt.get_app_name(self.files[filename]) # Then: self.assertEqual(self.files_properties[filename]["app_name"], files)
def test_get_manifest_info_when_dumb_xmltree_fails(self, mock_popen): mock_popen.side_effect = RuntimeError() manifest = Aapt.get_manifest_info("any-file-path") assert_popen_called_once(mock_popen) self.assertEqual({ "activities": [], "services": [], "receivers": [] }, manifest)
def test_get_manifest_info(self): for filename in self.files: # When: manifest = Aapt.get_manifest_info(self.files[filename]) # Then: self.assertEqual(self.files_properties[filename]["activities"], manifest["activities"]) self.assertEqual(self.files_properties[filename]["services"], manifest["services"]) self.assertEqual(self.files_properties[filename]["receivers"], manifest["receivers"])
def test_get_apk_info(self): for filename in self.files: # When: apk = Aapt.get_apk_info(self.files[filename]) # Then: self.assertEqual(self.files_properties[filename]["package_name"], apk["package_name"]) self.assertEqual(self.files_properties[filename]["version"], apk["version"]) self.assertEqual(self.files_properties[filename]["sdk"], apk["sdk"])
def __init__(self, filepath: str, string_processing: bool = True): super(APK, self).__init__(filepath) if not self.looks_like_an_apk(filepath): raise APKParsingError self._files = [] # type: List self._extract_and_set_entries(string_processing) if len(self._files) == 0 or self._cert is None: raise APKParsingError self._app_name = Aapt.get_app_name(filepath)
def test_get_apk_info_with_invalid_version_code(self, mock_popen): mock_popen.return_value = any_popen(b"package: name='com.example.app' versionCode='A' versionName='1.0'\n") apk = Aapt.get_apk_info("any-file-path") assert_popen_called_once(mock_popen) self.assertEqual( { "code": "", # NOTE: None is converted into an empty string "name": "1.0" }, apk["version"] )
def test_extract_activities(self): dump_badging = """N: android=http://schemas.android.com/apk/res/android E: manifest (line=2) E: application (line=8) E: activity (line=9) A: android:name(0x01010003)="com.example.app.HomeActivity" (Raw: "...") E: activity (line=15) A: android:name(0x01010003)="com.example.app.OtherActivity" (Raw: "...") """ activities = Aapt._extract_activities(dump_badging) self.assertEqual(["com.example.app.HomeActivity", "com.example.app.OtherActivity"], activities)
def test_extract_services(self): dump_badging = """N: android=http://schemas.android.com/apk/res/android E: manifest (line=2) E: application (line=8) E: service (line=25) A: android:name(0x01010003)="com.example.app.ExampleService" (Raw: "...") E: service (line=28) A: android:name(0x01010003)="com.example.app.OtherService" (Raw: "...") """ services = Aapt._extract_services(dump_badging) self.assertEqual(["com.example.app.ExampleService", "com.example.app.OtherService"], services)
def test_get_apk_info_when_dumb_badging_fails(self, mock_popen): mock_popen.side_effect = RuntimeError() apk = Aapt.get_apk_info("any-file-path") assert_popen_called_once(mock_popen) self.assertEqual( { "package_name": "", "version": { "code": "", "name": "" }, "sdk": {} }, apk)
def __init__(self, filepath: str, string_processing: bool = True, logger: Logger = logger): super(APK, self).__init__(filepath) self.logger = logger if not self.looks_like_an_apk(filepath): raise APKParsingError self._files = [] # type: List self._dex_files = [] # type: List[Dex] self._extract_and_set_entries(string_processing) if len(self._files) == 0 or not hasattr(self, "_cert") or self._cert is None: raise APKParsingError self._app_name = Aapt.get_app_name(filepath)
def test_extract_broadcast_receivers(self): dump_badging = """N: android=http://schemas.android.com/apk/res/android E: manifest (line=2) E: application (line=8) E: receiver (line=38) A: android:name(0x01010003)="com.example.app.ExampleBrodcastReceiver" (Raw: "...") E: receiver (line=44) A: android:name(0x01010003)="com.example.app.OtherBrodcastReceiver" (Raw: "...") """ receivers = Aapt._extract_broadcast_receivers(dump_badging) self.assertEqual( ["com.example.app.ExampleBrodcastReceiver", "com.example.app.OtherBrodcastReceiver"], receivers )
def test_get_app_permissions(self, mock_popen): dump_permissions = b"package: com.example.app\n" \ b"uses-permission: name='android.permission.READ_EXTERNAL_STORAGE'\n" \ b"uses-permission: name='android.permission.RECEIVE_BOOT_COMPLETED'\n" \ b"uses-permission: name='android.permission.WRITE_EXTERNAL_STORAGE'\n" \ b"uses-permission: name='android.permission.INTERNET'" mock_popen.return_value = any_popen(dump_permissions) permissions = Aapt.get_app_permissions("any-file-path") assert_popen_called_once(mock_popen) self.assertEqual([ "android.permission.INTERNET", "android.permission.READ_EXTERNAL_STORAGE", "android.permission.RECEIVE_BOOT_COMPLETED", "android.permission.WRITE_EXTERNAL_STORAGE" ], permissions)
def _extract_version_code_when_present(self): version_code = Aapt._extract_version_code("package: name='com.example.app' versionCode='1' versionName='1.0' platformBuildVersionName='4'") self.assertEqual(1, version_code)
def __init__(self, filepath: str, binary: bool = False, apk_path: str = ""): super(AndroidManifest, self).__init__(filepath, "AndroidManifest.xml") # Load the AndroidManifest.xml structure: with open(AndroidManifest.__MANIFEST_CONFIG_FILE, 'r') as config: cfg = json.load(config) with open(filepath, 'rb') as fp: try: if binary: self._raw = AXMLPrinter(fp.read()).get_buff() xml = minidom.parseString(self._raw) else: xml = minidom.parse(filepath) except ExpatError: if apk_path != "": apk = Aapt.get_apk_info(apk_path) self._package_name = apk["package_name"] self._version = apk["version"] self._sdk = apk["sdk"] self._permissions = Aapt.get_app_permissions(apk_path) man = Aapt.get_manifest_info(apk_path) self._activities = man["activities"] self._services = man["services"] self._receivers = man["receivers"] else: raise AndroidManifestParsingError except IOError: raise AndroidManifestParsingError else: manifest = xml.documentElement # Extract the package info: self._package_name = manifest.getAttribute(cfg['package']['name']) self._version = {"code": "", "name": ""} try: self._version['code'] = int(manifest.getAttribute(cfg['package']['version']['code'])) except ValueError: pass self._version['name'] = manifest.getAttribute(cfg['package']['version']['name']) # Extract the SDK info: sdk = self._parse_element_to_list_of_dict(manifest, cfg['uses-sdk'], "uses-sdk") if len(sdk) > 0: self._sdk = sdk[0] else: self._sdk = {} # Extract the permissions info: self._permissions = AndroidManifest._parse_element_to_simple_list(manifest, "uses-permission", cfg['uses-permission'][0]) # Extract the application info: application = manifest.getElementsByTagName(cfg['application']['tag']) application = application[0] self._activities = AndroidManifest._parse_element_to_list_of_dict(application, cfg['application']['activity'], "activity") self._services = AndroidManifest._parse_element_to_list_of_dict(application, cfg['application']['service'], "service") self._receivers = AndroidManifest._parse_element_to_list_of_dict(application, cfg['application']['receiver'], "receiver")
def test_get_apk_info(self): for filename in self.files: apk = Aapt.get_apk_info(self.files[filename]) self.assertEqual(apk["package_name"], self.files_properties[filename]["package_name"]) self.assertEqual(apk["version"], self.files_properties[filename]["version"]) self.assertEqual(apk["sdk"], self.files_properties[filename]["sdk"])
def test_get_manifest_info(self): for filename in self.files: man = Aapt.get_manifest_info(self.files[filename]) self.assertEqual(man["activities"], self.files_properties[filename]["activities"]) self.assertEqual(man["services"], self.files_properties[filename]["services"]) self.assertEqual(man["receivers"], self.files_properties[filename]["receivers"])
def test_get_app_permissions(self): for filename in self.files: self.assertEqual(Aapt.get_app_permissions(self.files[filename]), self.files_properties[filename]["permissions"])
def test_extract_sdk_target_when_missing(self): sdk_target = Aapt._extract_sdk_target_version("") self.assertEqual("", sdk_target)
def _extract_version_code_when_missing(self): version_code = Aapt._extract_version_code("") self.assertIsNone(version_code)
def _extract_version_code_when_invalid(self): version_code = Aapt._extract_version_code("package: name='com.example.app' versionCode='A' versionName='1.0' platformBuildVersionName='4'") self.assertIsNone(version_code)
def test_get_app_name(self): for filename in self.files: self.assertTrue(Aapt.get_app_name(self.files[filename]) == self.files_properties[filename]["app_name"])
def test_extract_version_name(self): version_name = Aapt._extract_version_name("package: name='com.example.app' versionCode='1' versionName='1.0' platformBuildVersionName='4'") self.assertEqual("1.0", version_name)