def test_init(self): for filename in self.certs: self.assertTrue(self.certs[filename] is not None) self.assertTrue(type(self.certs[filename]) is Cert) # Test the class raise when a non-existing file is given: with self.assertRaises(ParsingError): Cert(join("tests", "data", "aaa_this_is_a_non_existent_file_xxx")) # Test the class raise when a non-CERT.RSA file is given: with self.assertRaises(CertParsingError): Cert(join("tests", "data", "Example.apk")) Cert(join("tests", "data", "AndroidManifest.xml")) Cert(join("tests", "data", "classes.dex"))
def setUpClass(cls): cls.certs = {} for filename in listdir(join("tests", "data")): if filename in cls.cert_properties: cls.certs[filename] = Cert(join("tests", "data", filename), filename)
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 test_cert_as_dict(self): cert = Cert( filename="any-file-name", size=10, md5hash="any-file-md5", sha1hash="any-file-sha1", sha256hash="any-file-sha256", sha512hash="any-file-sha512", serial_number="any-serial-number", validity=CertValidity( valid_from="any-validity-from", valid_to="any-validity-to" ), fingerprint=CertFingerprint( md5="any-fingerprint-md5", sha1="any-fingerprint-sha1", sha256="any-fingerprint-sha256", signature="any-fingerprint-signature", version="any-fingerprint-version" ), owner=CertParticipant( name="any-owner-name", email="any-owner-email", unit="any-owner-unit", organization="any-owner-organization", city="any-owner-city", state="any-owner-state", country="any-owner-country", domain="any-owner-domain" ), issuer=CertParticipant( name="any-issuer-name", email="any-issuer-email", unit="any-issuer-unit", organization="any-issuer-organization", city="any-issuer-city", state="any-issuer-state", country="any-issuer-country", domain="any-issuer-domain" ) ) result = cert.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", "serial_number": "any-serial-number", "validity": { "from": "any-validity-from", "until": "any-validity-to" }, "fingerprint": { "md5": "any-fingerprint-md5", "sha1": "any-fingerprint-sha1", "sha256": "any-fingerprint-sha256", "signature": "any-fingerprint-signature", "version": "any-fingerprint-version" }, "owner": { "name": "any-owner-name", "email": "any-owner-email", "unit": "any-owner-unit", "organization": "any-owner-organization", "city": "any-owner-city", "state": "any-owner-state", "country": "any-owner-country", "domain": "any-owner-domain" }, "issuer": { "name": "any-issuer-name", "email": "any-issuer-email", "unit": "any-issuer-unit", "organization": "any-issuer-organization", "city": "any-issuer-city", "state": "any-issuer-state", "country": "any-issuer-country", "domain": "any-issuer-domain" } }, result )
class APK(File, APKInterface): """ Parser implementation for Android APK package. """ _TEMPORARY_DIR = ".ninjadroid" 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 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 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 @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_files"] = [dex.dump() for dex in self._dex_files] 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_files(self) -> Sequence[Dex]: return self._dex_files def get_app_name(self) -> str: return self._app_name