def test_dex_as_dict(self): dex = Dex(filename="any-file-name", size=10, md5hash="any-file-md5", sha1hash="any-file-sha1", sha256hash="any-file-sha256", sha512hash="any-file-sha512", strings=["any-command", "any-string", "any-url"], urls=["any-url"], shell_commands=["any-command"], custom_signatures=[]) result = dex.as_dict() self.assertEqual( { "file": "any-file-name", "size": 10, "md5": "any-file-md5", "sha1": "any-file-sha1", "sha256": "any-file-sha256", "sha512": "any-file-sha512", "strings": ["any-command", "any-string", "any-url"], "urls": ["any-url"], "shell_commands": ["any-command"] }, result)
def test_init(self): for filename in self.dexes: self.assertTrue(self.dexes[filename] is not None) self.assertTrue(type(self.dexes[filename]) is Dex) # Test the class raise when a non-existing file is given: with self.assertRaises(ParsingError): Dex(join("tests", "data", "aaa_this_is_a_non_existent_file_xxx"))
def _extract_and_set_entries(self, string_processing: bool): """ Extract the APK package entries (e.g. AndroidManifest.xml, CERT.RSA, classes.dex, ...) and set the corresponding attributes. :param string_processing: If True (default), the URLs and shell commands in the classes.dex will be extracted. :return: If one of the APK entries is invalid. :raise APKParsingError: """ exists_invalid_entry = False apk_filepath = self.get_file_path() with ZipFile(apk_filepath) as apk: tmpdir = tempfile.mkdtemp(APK._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 AndroidManifest.looks_like_a_manifest(filename): self.logger.debug("%s looks like a manifest", filename) self._manifest = AndroidManifest(entry_filepath, True, apk_filepath) elif Cert.looks_like_a_cert(filename): self.logger.debug("%s looks like a certificate", filename) self._cert = Cert(entry_filepath, filename) elif Dex.looks_like_a_dex(filename): self.logger.debug("%s looks like a DEX", filename) self._dex_files.append(Dex(entry_filepath, filename, string_processing)) else: self.logger.debug("%s looks like a general resource", filename) if not os.path.isdir(entry_filepath): self._files.append(File(entry_filepath, filename)) except (ParsingError, AndroidManifestParsingError, CertParsingError): exists_invalid_entry = True try: shutil.rmtree(tmpdir) except OSError: pass if exists_invalid_entry: raise APKParsingError
def test_apk_as_dict(self): apk = APK( filename="any-apk-file-name", size=10, md5hash="any-apk-file-md5", sha1hash="any-apk-file-sha1", sha256hash="any-apk-file-sha256", sha512hash="any-apk-file-sha512", app_name="any-app-name", cert=Cert( filename="any-cert-file-name", size=20, md5hash="any-cert-file-md5", sha1hash="any-cert-file-sha1", sha256hash="any-cert-file-sha256", sha512hash="any-cert-file-sha512", serial_number="any-cert-serial-number", validity=CertValidity( valid_from="any-cert-validity-from", valid_to="any-cert-validity-to" ), fingerprint=CertFingerprint( md5="any-cert-fingerprint-md5", sha1="any-cert-fingerprint-sha1", sha256="any-cert-fingerprint-sha256", signature="any-cert-fingerprint-signature", version="any-cert-fingerprint-version" ), owner=CertParticipant( name="any-cert-owner-name", email="any-cert-owner-email", unit="any-cert-owner-unit", organization="any-cert-owner-organization", city="any-cert-owner-city", state="any-cert-owner-state", country="any-cert-owner-country", domain="any-cert-owner-domain" ), issuer=CertParticipant( name="any-cert-issuer-name", email="any-cert-issuer-email", unit="any-cert-issuer-unit", organization="any-cert-issuer-organization", city="any-cert-issuer-city", state="any-cert-issuer-state", country="any-cert-issuer-country", domain="any-cert-issuer-domain" ) ), manifest=AndroidManifest( filename="any-manifest-file-name", size=30, md5hash="any-manifest-file-md5", sha1hash="any-manifest-file-sha1", sha256hash="any-manifest-file-sha256", sha512hash="any-manifest-file-sha512", package_name="any-package-name", version=AppVersion(code=1, name="any-version-name"), sdk=AppSdk(min_version="10", target_version="15", max_version="20"), permissions=[], activities=[], services=[], receivers=[] ), dex_files=[ Dex( filename="any-dex-file-name", size=40, md5hash="any-dex-file-md5", sha1hash="any-dex-file-sha1", sha256hash="any-dex-file-sha256", sha512hash="any-dex-file-sha512", strings=[], urls=[], shell_commands=[], custom_signatures=[] ) ], other_files=[ any_file( filename="any-resource-file-name", size=50, md5="any-resource-file-md5", sha1="any-resource-file-sha1", sha256="any-resource-file-sha256", sha512="any-resource-file-sha512" ) ] ) result = apk.as_dict() self.assertEqual( { "file": "any-apk-file-name", "size": 10, "md5": "any-apk-file-md5", "sha1": "any-apk-file-sha1", "sha256": "any-apk-file-sha256", "sha512": "any-apk-file-sha512", "name": "any-app-name", "cert": { "file": "any-cert-file-name", "size": 20, "md5": "any-cert-file-md5", "sha1": "any-cert-file-sha1", "sha256": "any-cert-file-sha256", "sha512": "any-cert-file-sha512", "serial_number": "any-cert-serial-number", "validity": { "from": "any-cert-validity-from", "until": "any-cert-validity-to" }, "fingerprint": { "md5": "any-cert-fingerprint-md5", "sha1": "any-cert-fingerprint-sha1", "sha256": "any-cert-fingerprint-sha256", "signature": "any-cert-fingerprint-signature", "version": "any-cert-fingerprint-version" }, "owner": { "name": "any-cert-owner-name", "email": "any-cert-owner-email", "unit": "any-cert-owner-unit", "organization": "any-cert-owner-organization", "city": "any-cert-owner-city", "state": "any-cert-owner-state", "country": "any-cert-owner-country", "domain": "any-cert-owner-domain" }, "issuer": { "name": "any-cert-issuer-name", "email": "any-cert-issuer-email", "unit": "any-cert-issuer-unit", "organization": "any-cert-issuer-organization", "city": "any-cert-issuer-city", "state": "any-cert-issuer-state", "country": "any-cert-issuer-country", "domain": "any-cert-issuer-domain" } }, "manifest": { "file": "any-manifest-file-name", "size": 30, "md5": "any-manifest-file-md5", "sha1": "any-manifest-file-sha1", "sha256": "any-manifest-file-sha256", "sha512": "any-manifest-file-sha512", "package": "any-package-name", "version": { "code": 1, "name": "any-version-name" }, "sdk": { "min": "10", "target": "15", "max": "20" }, "permissions": [] }, "dex": [ { "file": "any-dex-file-name", "size": 40, "md5": "any-dex-file-md5", "sha1": "any-dex-file-sha1", "sha256": "any-dex-file-sha256", "sha512": "any-dex-file-sha512", "strings": [], "urls": [], "shell_commands": [] } ], "other": [ { "file": "any-resource-file-name", "size": 50, "md5": "any-resource-file-md5", "sha1": "any-resource-file-sha1", "sha256": "any-resource-file-sha256", "sha512": "any-resource-file-sha512", } ] }, result )
def setUpClass(cls): cls.dexes = {} for file in listdir(join("tests", "data")): if file in cls.dex_properties: cls.dexes[file] = Dex(join("tests", "data", file), file)
class APK(File, APKInterface): """ Parser implementation for Android APK package. """ _TEMPORARY_DIR = ".ninjadroid" 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 _extract_and_set_entries(self, string_processing: bool): """ Extract the APK package entries (e.g. AndroidManifest.xml, CERT.RSA, classes.dex, ...) and set the correspondent attributes. :param string_processing: If True (default), the URLs and shell commands in the classes.dex will be extracted. :return: If one of the APK entries is invalid. :raise APKParsingError: """ exists_invalid_entry = False apk_filepath = self.get_file_path() with ZipFile(apk_filepath) as apk: tmpdir = tempfile.mkdtemp(APK._TEMPORARY_DIR) for filename in apk.namelist(): entry_filepath = apk.extract(filename, tmpdir) try: if AndroidManifest.looks_like_a_manifest(filename): self._manifest = AndroidManifest( entry_filepath, True, apk_filepath) elif Cert.looks_like_a_cert(filename): self._cert = Cert(entry_filepath, filename) elif Dex.looks_like_a_dex(filename): self._dex = Dex(entry_filepath, string_processing) else: if not os.path.isdir(entry_filepath): self._files.append(File(entry_filepath, filename)) except (ParsingError, AndroidManifestParsingError, CertParsingError): exists_invalid_entry = True try: shutil.rmtree(tmpdir) except OSError: pass if exists_invalid_entry: raise APKParsingError @staticmethod def looks_like_an_apk(filepath: str) -> bool: return File.is_a_file(filepath) and is_zipfile(filepath) def dump(self) -> Dict: dump = super(APK, self).dump() dump["app_name"] = self._app_name dump["cert"] = self._cert.dump() if self._cert is not None else None dump["manifest"] = self._manifest.dump( ) if self._manifest is not None else None dump["dex"] = self._dex.dump() if self._dex is not None else None dump["other_files"] = [] for file in self._files: dump["other_files"].append(file.dump()) return dump def get_file_list(self) -> List: return self._files def get_manifest(self) -> AndroidManifest: return self._manifest def get_cert(self) -> Cert: return self._cert def get_dex(self) -> Dex: return self._dex def get_app_name(self) -> str: return self._app_name