class AndroguardAnalysis(object): def __init__(self, app_path): self.app_path = app_path from androguard.core.bytecodes.apk import APK self.a = APK(app_path) self.d = None self.dx = None def get_detailed_analysis(self): from androguard.misc import AnalyzeDex self.d, self.dx = AnalyzeDex(self.a.get_dex(), raw=True) def get_developer_name(self): developer = "Unknown" if (self.a.is_signed()): try: signatures = self.a.get_signature_names() for signature in signatures: cert = self.a.get_certificate(signature) issuer = get_certificate_name_string(cert.issuer) attr_list = issuer.split(',') for attr in attr_list: if attr.startswith( " organizationName") or attr.startswith( "organizationName"): developer = attr.split('=')[1] except ValueError: print("certificat endommagée ... ") return developer
def testAPKCertFingerprint(self): """ Test if certificates are correctly unpacked from the SignatureBlock files Check if fingerprints matches :return: """ from androguard.core.bytecodes.apk import APK import binascii from hashlib import md5, sha1, sha256 a = APK("examples/android/TestsAndroguard/bin/TestActivity.apk", skip_analysis=True) # this one is not signed v2, it is v1 only self.assertTrue(a.is_signed_v1()) self.assertFalse(a.is_signed_v2()) self.assertTrue(a.is_signed()) self.assertEqual(a.get_certificates_der_v2(), []) self.assertEqual(a.get_certificates_v2(), []) self.assertEqual(a.get_signature_name(), "META-INF/CERT.RSA") self.assertEqual(a.get_signature_names(), ["META-INF/CERT.RSA"]) cert = a.get_certificate(a.get_signature_name()) cert_der = a.get_certificate_der(a.get_signature_name()) # Keytool are the hashes collected by keytool -printcert -file CERT.RSA for h2, keytool in [(md5, "99:FF:FC:37:D3:64:87:DD:BA:AB:F1:7F:94:59:89:B5"), (sha1, "1E:0B:E4:01:F9:34:60:E0:8D:89:A3:EF:6E:27:25:55:6B:E1:D1:6B"), (sha256, "6F:5C:31:60:8F:1F:9E:28:5E:B6:34:3C:7C:8A:F0:7D:E8:1C:1F:B2:14:8B:53:49:BE:C9:06:44:41:44:57:6D")]: x = h2() x.update(cert_der) hash_hashlib = x.hexdigest() self.assertEqual(hash_hashlib.lower(), keytool.replace(":", "").lower())
def testAPKCertFingerprint(self): """ Test if certificates are correctly unpacked from the SignatureBlock files Check if fingerprints matches :return: """ from androguard.core.bytecodes.apk import APK import binascii from cryptography.hazmat.primitives import hashes from hashlib import md5, sha1, sha256 a = APK("examples/android/TestsAndroguard/bin/TestActivity.apk", skip_analysis=True) cert = a.get_certificate(a.get_signature_name()) cert_der = a.get_certificate_der(a.get_signature_name()) # Keytool are the hashes collected by keytool -printcert -file CERT.RSA for h, h2, keytool in [(hashes.MD5, md5, "99:FF:FC:37:D3:64:87:DD:BA:AB:F1:7F:94:59:89:B5"), (hashes.SHA1, sha1, "1E:0B:E4:01:F9:34:60:E0:8D:89:A3:EF:6E:27:25:55:6B:E1:D1:6B"), (hashes.SHA256, sha256, "6F:5C:31:60:8F:1F:9E:28:5E:B6:34:3C:7C:8A:F0:7D:E8:1C:1F:B2:14:8B:53:49:BE:C9:06:44:41:44:57:6D")]: hash_x509 = binascii.hexlify(cert.fingerprint(h())).decode("ascii") x = h2() x.update(cert_der) hash_hashlib = x.hexdigest() self.assertEqual(hash_x509.lower(), hash_hashlib.lower()) self.assertEqual(hash_x509.lower(), keytool.replace(":", "").lower())
def testAPKCertFingerprint(self): """ Test if certificates are correctly unpacked from the SignatureBlock files Check if fingerprints matches :return: """ from androguard.core.bytecodes.apk import APK import binascii from hashlib import md5, sha1, sha256 a = APK("examples/android/TestsAndroguard/bin/TestActivity.apk", skip_analysis=True) # this one is not signed v2, it is v1 only self.assertTrue(a.is_signed_v1()) self.assertFalse(a.is_signed_v2()) self.assertTrue(a.is_signed()) self.assertEqual(a.get_certificates_der_v2(), []) self.assertEqual(a.get_certificates_v2(), []) self.assertEqual(a.get_signature_name(), "META-INF/CERT.RSA") self.assertEqual(a.get_signature_names(), ["META-INF/CERT.RSA"]) cert = a.get_certificate(a.get_signature_name()) cert_der = a.get_certificate_der(a.get_signature_name()) # Keytool are the hashes collected by keytool -printcert -file CERT.RSA for h2, keytool in [(md5, "99:FF:FC:37:D3:64:87:DD:BA:AB:F1:7F:94:59:89:B5"), (sha1, "1E:0B:E4:01:F9:34:60:E0:8D:89:A3:EF:6E:27:25:55:6B:E1:D1:6B"), (sha256, "6F:5C:31:60:8F:1F:9E:28:5E:B6:34:3C:7C:8A:F0:7D:E8:1C:1F:B2:14:8B:53:49:BE:C9:06:44:41:44:57:6D")]: x = h2() x.update(cert_der) hash_hashlib = x.hexdigest() self.assertEqual(hash_hashlib.lower(), keytool.replace(":", "").lower())
def testAPKCertFingerprint(self): """ Test if certificates are correctly unpacked from the SignatureBlock files Check if fingerprints matches :return: """ from androguard.core.bytecodes.apk import APK import binascii from cryptography.hazmat.primitives import hashes from hashlib import md5, sha1, sha256 a = APK("examples/android/TestsAndroguard/bin/TestActivity.apk", skip_analysis=True) cert = a.get_certificate(a.get_signature_name()) cert_der = a.get_certificate_der(a.get_signature_name()) # Keytool are the hashes collected by keytool -printcert -file CERT.RSA for h, h2, keytool in [ (hashes.MD5, md5, "99:FF:FC:37:D3:64:87:DD:BA:AB:F1:7F:94:59:89:B5"), (hashes.SHA1, sha1, "1E:0B:E4:01:F9:34:60:E0:8D:89:A3:EF:6E:27:25:55:6B:E1:D1:6B"), (hashes.SHA256, sha256, "6F:5C:31:60:8F:1F:9E:28:5E:B6:34:3C:7C:8A:F0:7D:E8:1C:1F:B2:14:8B:53:49:BE:C9:06:44:41:44:57:6D" ) ]: hash_x509 = binascii.hexlify(cert.fingerprint(h())).decode("ascii") x = h2() x.update(cert_der) hash_hashlib = x.hexdigest() self.assertEqual(hash_x509.lower(), hash_hashlib.lower()) self.assertEqual(hash_x509.lower(), keytool.replace(":", "").lower())
def testApksignAPKs(self): # These APKs are from the apksign testcases and cover # all different signature algorithms as well as some error cases from androguard.core.bytecodes.apk import APK import zipfile from asn1crypto import x509, pem import binascii root = "examples/signing/apksig" # Correct values generated with openssl: # In the apksig repo:src/test/resources/com/android/apksig # for f in *.pem; do openssl x509 -in $f -noout -sha256 -fingerprint; done certfp = { 'dsa-1024.x509.pem': 'fee7c19ff9bfb4197b3727b9fd92d95406b1bd96db99ea642f5faac019a389d7', 'dsa-2048.x509.pem': '97cce0bab292c2d5afb9de90e1810b41a5d25c006a10d10982896aa12ab35a9e', 'dsa-3072.x509.pem': '966a4537058d24098ea213f12d4b24e37ff5a1d8f68deb8a753374881f23e474', 'ec-p256.x509.pem': '6a8b96e278e58f62cfe3584022cec1d0527fcb85a9e5d2e1694eb0405be5b599', 'ec-p384.x509.pem': '5e7777ada7ee7ce8f9c4d1b07094876e5604617b7988b4c5d5b764a23431afbe', 'ec-p521.x509.pem': '69b50381d98bebcd27df6d7df8af8c8b38d0e51e9168a95ab992d1a9da6082da', 'rsa-1024_2.x509.pem': 'eba3685e799f59804684abebf0363e14ccb1c213e2b954a22669714ed97f61e9', 'rsa-1024.x509.pem': 'bc5e64eab1c4b5137c0fbc5ed05850b3a148d1c41775cffa4d96eea90bdd0eb8', 'rsa-16384.x509.pem': 'f3c6b37909f6df310652fbd7c55ec27d3079dcf695dc6e75e22ba7c4e1c95601', 'rsa-2048_2.x509.pem': '681b0e56a796350c08647352a4db800cc44b2adc8f4c72fa350bd05d4d50264d', 'rsa-2048_3.x509.pem': 'bb77a72efc60e66501ab75953af735874f82cfe52a70d035186a01b3482180f3', 'rsa-2048.x509.pem': 'fb5dbd3c669af9fc236c6991e6387b7f11ff0590997f22d0f5c74ff40e04fca8', 'rsa-3072.x509.pem': '483934461229a780010bc07cd6eeb0b67025fc4fe255757abbf5c3f2ed249e89', 'rsa-4096.x509.pem': '6a46158f87753395a807edcc7640ac99c9125f6b6e025bdbf461ff281e64e685', 'rsa-8192.x509.pem': '060d0a24fea9b60d857225873f78838e081795f7ef2d1ea401262bbd75a58234', } will_not_validate_correctly = [ "targetSandboxVersion-2.apk", "targetSandboxVersion-2.apk", "v1-only-with-cr-in-entry-name.apk", "v1-only-with-lf-in-entry-name.apk", "v1-only-with-nul-in-entry-name.apk", "v1-only-with-rsa-1024-cert-not-der2.apk", "v2-only-cert-and-public-key-mismatch.apk", "v2-only-with-dsa-sha256-1024-sig-does-not-verify.apk", "debuggable-boolean.apk", "debuggable-resource.apk", ] # Collect possible hashes for certificates # Unfortunately, not all certificates are supplied... for apath in os.listdir(root): if apath in certfp: with open(os.path.join(root, apath), "rb") as fp: cert = x509.Certificate.load(pem.unarmor(fp.read())[2]) h = cert.sha256_fingerprint.replace(" ","").lower() self.assertEqual(h, certfp[apath]) self.assertIn(h, certfp.values()) for apath in os.listdir(root): if apath.endswith(".apk"): if apath == "v2-only-garbage-between-cd-and-eocd.apk" or \ apath == "v2-only-truncated-cd.apk" or \ apath == "v1v2v3-with-rsa-2048-lineage-3-signers-invalid-zip.apk": # Can not load as APK with self.assertRaises(zipfile.BadZipFile): APK(os.path.join(root, apath)) continue elif apath in will_not_validate_correctly: # These APKs are faulty (by design) and will return a not correct fingerprint. # TODO: we need to check if we can prevent such errors... continue a = APK(os.path.join(root, apath)) self.assertIsInstance(a, APK) # Test if the correct method returns True, while others return # False m_tests = {'1': a.is_signed_v1, '2': a.is_signed_v2, '3': a.is_signed_v3} # These APKs will raise an error excluded = [ "v1v2v3-with-rsa-2048-lineage-3-signers-no-sig-block.apk", "v2-only-apk-sig-block-size-mismatch.apk", "v2-only-empty.apk", "v2-only-wrong-apk-sig-block-magic.apk", "v2-stripped.apk", "v2-stripped-with-ignorable-signing-schemes.apk", "v2v3-signed-v3-block-stripped.apk", "v3-only-empty.apk", "v3-only-with-ecdsa-sha512-p384-wrong-apk-sig-block-magic.apk", "v3-only-with-rsa-pkcs1-sha512-4096-apk-sig-block-size-mismatch.apk", "v3-stripped.apk", ] if apath[0] == "v" and apath not in excluded: methods = apath.split("-", 1)[0].split("v")[1:] for m, f in m_tests.items(): if m in methods: self.assertTrue(f()) else: self.assertFalse(f()) # Special error cases if apath == "v2-only-apk-sig-block-size-mismatch.apk": with self.assertRaises(apk.BrokenAPKError): a.is_signed_v2() continue elif apath == "v2-only-empty.apk": with self.assertRaises(apk.BrokenAPKError): a.is_signed_v2() continue elif apath == "v3-only-with-rsa-pkcs1-sha512-4096-apk-sig-block-size-mismatch.apk": with self.assertRaises(apk.BrokenAPKError): a.is_signed_v3() continue if a.is_signed_v1(): if apath == "weird-compression-method.apk": with self.assertRaises(NotImplementedError): for c in a.get_signature_names(): a.get_certificate(c) elif apath == "v1-only-with-rsa-1024-cert-not-der.apk": for sig in a.get_signature_names(): c = a.get_certificate(sig) h = c.sha256_fingerprint.replace(" ","").lower() self.assertNotIn(h, certfp.values()) # print([apath, h]) # I do not know, why put this file? der = a.get_certificate_der(sig) apk.show_Certificate(c, True) apk.show_Certificate(c, False) self.assertEqual(hashlib.sha256(der).hexdigest(), h) pass else: for sig in a.get_signature_names(): c = a.get_certificate(sig) h = c.sha256_fingerprint.replace(" ","").lower() self.assertIn(h, certfp.values()) # Check that we get the same signature if we take the DER der = a.get_certificate_der(sig) self.assertEqual(hashlib.sha256(der).hexdigest(), h) if a.is_signed_v2(): if apath == "weird-compression-method.apk": with self.assertRaises(NotImplementedError): a.get_certificates_der_v2() elif apath == "v2-only-with-rsa-pkcs1-sha256-1024-cert-not-der.apk": # FIXME # Not sure what this one should do... but the certificate fingerprint is weird # as the hash over the DER is not the same when using the certificate continue else: for c in a.get_certificates_der_v2(): cert = x509.Certificate.load(c) h = cert.sha256_fingerprint.replace(" ","").lower() self.assertIn(h, certfp.values()) # Check that we get the same signature if we take the DER self.assertEqual(hashlib.sha256(c).hexdigest(), h) if a.is_signed_v3(): print(apath) if apath == "weird-compression-method.apk": with self.assertRaises(NotImplementedError): a.get_certificates_der_v3() elif apath == "v3-only-with-rsa-pkcs1-sha256-3072-sig-does-not-verify.apk" or \ apath == "v3-only-cert-and-public-key-mismatch.apk": cert = x509.Certificate.load(a.get_certificates_der_v3()[0]) h = cert.sha256_fingerprint.replace(" ","").lower() self.assertNotIn(h, certfp.values()) else: for c in a.get_certificates_der_v3(): cert = x509.Certificate.load(c) h = cert.sha256_fingerprint.replace(" ","").lower() self.assertIn(h, certfp.values()) # Check that we get the same signature if we take the DER self.assertEqual(hashlib.sha256(c).hexdigest(), h)
def testApksignAPKs(self): # These APKs are from the apksign testcases and cover # all different signature algorithms as well as some error cases from androguard.core.bytecodes.apk import APK import zipfile from asn1crypto import x509, pem import binascii root = "examples/signing/apksig" # Correct values generated with openssl: certfp = { "dsa-1024.x509.pem": "fee7c19ff9bfb4197b3727b9fd92d95406b1bd96db99ea642f5faac019a389d7", "dsa-2048.x509.pem": "97cce0bab292c2d5afb9de90e1810b41a5d25c006a10d10982896aa12ab35a9e", "dsa-3072.x509.pem": "966a4537058d24098ea213f12d4b24e37ff5a1d8f68deb8a753374881f23e474", "ec-p256.x509.pem": "6a8b96e278e58f62cfe3584022cec1d0527fcb85a9e5d2e1694eb0405be5b599", "ec-p384.x509.pem": "5e7777ada7ee7ce8f9c4d1b07094876e5604617b7988b4c5d5b764a23431afbe", "ec-p521.x509.pem": "69b50381d98bebcd27df6d7df8af8c8b38d0e51e9168a95ab992d1a9da6082da", "rsa-1024.x509.pem": "bc5e64eab1c4b5137c0fbc5ed05850b3a148d1c41775cffa4d96eea90bdd0eb8", "rsa-16384.x509.pem": "f3c6b37909f6df310652fbd7c55ec27d3079dcf695dc6e75e22ba7c4e1c95601", "rsa-2048.x509.pem": "fb5dbd3c669af9fc236c6991e6387b7f11ff0590997f22d0f5c74ff40e04fca8", "rsa-3072.x509.pem": "483934461229a780010bc07cd6eeb0b67025fc4fe255757abbf5c3f2ed249e89", "rsa-4096.x509.pem": "6a46158f87753395a807edcc7640ac99c9125f6b6e025bdbf461ff281e64e685", "rsa-8192.x509.pem": "060d0a24fea9b60d857225873f78838e081795f7ef2d1ea401262bbd75a58234", } will_not_validate_correctly = [ "targetSandboxVersion-2.apk", "targetSandboxVersion-2.apk", "v1-only-with-cr-in-entry-name.apk", "v1-only-with-lf-in-entry-name.apk", "v1-only-with-nul-in-entry-name.apk", "v1-only-with-rsa-1024-cert-not-der2.apk", "v2-only-cert-and-public-key-mismatch.apk", "v2-only-with-dsa-sha256-1024-sig-does-not-verify.apk", ] # Collect possible hashes for certificates # Unfortunately, not all certificates are supplied... for apath in os.listdir(root): if apath in certfp: with open(os.path.join(root, apath), "rb") as fp: cert = x509.Certificate.load(pem.unarmor(fp.read())[2]) h = cert.sha256_fingerprint.replace(" ", "").lower() self.assertEqual(h, certfp[apath]) self.assertIn(h, certfp.values()) for apath in os.listdir(root): if apath.endswith(".apk"): if apath == "v2-only-garbage-between-cd-and-eocd.apk" or \ apath == "v2-only-truncated-cd.apk": # Can not load as APK if sys.version_info.major == 2: # Different name in python2... with self.assertRaises(zipfile.BadZipfile): APK(os.path.join(root, apath)) else: with self.assertRaises(zipfile.BadZipFile): APK(os.path.join(root, apath)) continue elif apath in will_not_validate_correctly: # These APKs are faulty (by design) and will return a not correct fingerprint. # TODO: we need to check if we can prevent such errors... continue a = APK(os.path.join(root, apath)) self.assertIsInstance(a, APK) # Special error cases if apath == "v2-only-apk-sig-block-size-mismatch.apk": with self.assertRaises(AssertionError): a.is_signed_v2() continue elif apath == "v2-only-empty.apk": with self.assertRaises(AssertionError): a.is_signed_v2() continue if a.is_signed_v1(): if apath == "weird-compression-method.apk": with self.assertRaises(NotImplementedError): for c in a.get_signature_names(): a.get_certificate(c) elif apath == "v1-only-with-rsa-1024-cert-not-der.apk": for sig in a.get_signature_names(): c = a.get_certificate(sig) h = c.sha256_fingerprint.replace(" ", "").lower() self.assertNotIn(h, certfp.values()) # print([apath, h]) # I do not know, why put this file? der = a.get_certificate_der(sig) apk.show_Certificate(c, True) apk.show_Certificate(c, False) self.assertEqual( hashlib.sha256(der).hexdigest(), h) pass else: for sig in a.get_signature_names(): c = a.get_certificate(sig) h = c.sha256_fingerprint.replace(" ", "").lower() self.assertIn(h, certfp.values()) # Check that we get the same signature if we take the DER der = a.get_certificate_der(sig) self.assertEqual( hashlib.sha256(der).hexdigest(), h) if a.is_signed_v2(): if apath == "weird-compression-method.apk": with self.assertRaises(NotImplementedError): a.get_certificates_der_v2() elif apath == "v2-only-with-rsa-pkcs1-sha256-1024-cert-not-der.apk": # FIXME # Not sure what this one should do... but the certificate fingerprint is weird # as the hash over the DER is not the same when using the certificate continue else: for c in a.get_certificates_der_v2(): cert = x509.Certificate.load(c) h = cert.sha256_fingerprint.replace(" ", "").lower() self.assertIn(h, certfp.values()) # Check that we get the same signature if we take the DER self.assertEqual(hashlib.sha256(c).hexdigest(), h)
class StaticAnalysis: def __init__(self, apk_path=None): self.apk = None self.apk_path = apk_path self.signatures = None self.compiled_tracker_signature = None self.classes = None self.app_details = None if apk_path is not None: self.load_apk() def _compile_signatures(self): """ Compiles the regex associated to each signature, in order to speed up the trackers detection. :return: A compiled list of signatures. """ self.compiled_tracker_signature = [] try: self.compiled_tracker_signature = [ re.compile(track.code_signature) for track in self.signatures ] except TypeError: print("self.signatures is not iterable") def load_trackers_signatures(self): """ Load trackers signatures from the official Exodus database. :return: a dictionary containing signatures. """ self.signatures = [] exodus_url = "https://reports.exodus-privacy.eu.org/api/trackers" r = requests.get(exodus_url) data = r.json() for e in data['trackers']: self.signatures.append( namedtuple( 'tracker', data['trackers'][e].keys())(*data['trackers'][e].values())) self._compile_signatures() logging.debug('{} trackers signatures loaded'.format( len(self.signatures))) def load_apk(self): """ Load the APK file. """ if self.apk is None: self.apk = APK(self.apk_path) def get_embedded_classes(self): """ Get the list of Java classes embedded into all DEX files. :return: array of Java classes names as string """ if self.classes is not None: return self.classes class_regex = re.compile(r'classes.*\.dex') with TemporaryDirectory() as tmp_dir: with zipfile.ZipFile(self.apk_path, "r") as apk_zip: class_infos = (info for info in apk_zip.infolist() if class_regex.search(info.filename)) for info in class_infos: apk_zip.extract(info, tmp_dir) dexdump = which('dexdump') cmd = '{} {}/classes*.dex | perl -n -e\'/[A-Z]+((?:\w+\/)+\w+)/ && print "$1\n"\'|sort|uniq'.format( dexdump, tmp_dir) try: self.classes = subprocess.check_output( cmd, stderr=subprocess.STDOUT, shell=True, universal_newlines=True).splitlines() logging.debug('{} classes found in {}'.format( len(self.classes), self.apk_path)) return self.classes except subprocess.CalledProcessError: logging.error('Unable to decode {}'.format(self.apk_path)) raise Exception('Unable to decode the APK') def detect_trackers_in_list(self, class_list): """ Detect embedded trackers in the provided classes list. :return: list of embedded trackers """ if self.signatures is None: self.load_trackers_signatures() def _detect_tracker(sig, tracker, class_list): for clazz in class_list: if sig.search(clazz): return tracker return None results = [] args = [(self.compiled_tracker_signature[index], tracker, class_list) for (index, tracker) in enumerate(self.signatures) if len(tracker.code_signature) > 3] for res in itertools.starmap(_detect_tracker, args): if res: results.append(res) trackers = [t for t in results if t is not None] logging.debug('{} trackers detected in {}'.format( len(trackers), self.apk_path)) return trackers def detect_trackers(self, class_list_file=None): """ Detect embedded trackers. :return: list of embedded trackers """ if self.signatures is None: self.load_trackers_signatures() if class_list_file is None: return self.detect_trackers_in_list(self.get_embedded_classes()) else: with open(class_list_file, 'r') as classes_file: classes = classes_file.readlines() return self.detect_trackers_in_list(classes) def get_version(self): """ Get the application version name :return: version name """ self.load_apk() return self.apk.get_androidversion_name() def get_version_code(self): """ Get the application version code :return: version code """ self.load_apk() return self.apk.get_androidversion_code() def get_permissions(self): """ Get application permissions :return: application permissions list """ self.load_apk() return self.apk.get_permissions() def get_app_name(self): """ Get application name :return: application name """ self.load_apk() return self.apk.get_app_name() def get_package(self): """ Get application package :return: application package """ self.load_apk() return self.apk.get_package() def get_libraries(self): """ Get application libraries :return: application libraries list """ self.load_apk() return self.apk.get_libraries() def get_icon_path(self): """ Get the icon path in the ZIP archive :return: icon path in the ZIP archive """ self.load_apk() return self.apk.get_app_icon() def get_application_details(self): """ Get the application details like creator, number of downloads, etc. :param handle: application handle :return: application details dictionary """ self.load_apk() if self.app_details is not None: return self.app_details details = get_details_from_gplaycli(self.get_package()) if details is not None: self.app_details = details return details def _get_icon_from_details(self, path): """ Get icon from applications details dictionary :param path: path where to write the icon file :return: icon path :raises Exception: if unable to find icon """ details = self.get_application_details() if details is not None: for i in details.get('images'): if i.get('imageType') == 4: f = requests.get(i.get('url')) with open(path, mode='wb') as fp: fp.write(f.content) if os.path.isfile(path) and os.path.getsize(path) > 0: return path raise Exception('Unable to download the icon from details') @staticmethod def _render_drawable_to_png(self, bxml, path): ap = axml.AXMLPrinter(bxml) print(ap.get_buff()) def save_icon(self, path): """ Extract the icon from the ZIP archive and save it at the given path :param path: destination path of the icon :return: destination path of the icon, None in case of error """ try: icon = self.get_icon_path() if icon is None: raise Exception('Unable to get icon path') with zipfile.ZipFile(self.apk_path) as z: with open(path, 'wb') as f: f.write(z.read(icon)) with Image.open(path) as _: logging.info('Get icon from APK: success') return path except Exception: logging.warning('Unable to get the icon from the APK') return None # TODO: Set this back once details download is working again # logging.warning('Downloading icon from details') # try: # saved_path = self._get_icon_from_details(path) # logging.debug('Icon downloaded from application details') # return saved_path # except Exception as e: # logging.warning(e) def get_icon_phash(self): """ Get the perceptual hash of the application icon :return: the perceptual hash, empty string in case of error """ with NamedTemporaryFile() as ic: path = self.save_icon(ic.name) if path is None: logging.error('Unable to save the icon') return '' return self.get_phash(ic.name) @staticmethod def get_phash(image_name): """ Get the perceptual hash of the given image :param image_name: name of the image file :return: the perceptual hash, empty string in case of error """ dhash.force_pil() # Force PIL try: image = Image.open(image_name).convert("RGBA") row, col = dhash.dhash_row_col(image, size=PHASH_SIZE) return row << (PHASH_SIZE * PHASH_SIZE) | col except IOError as e: logging.error(e) return '' @staticmethod def get_icon_similarity(phash_origin, phash_candidate): """ Get icons similarity score [0,1.0] :param phash_origin: original icon :param phash_candidate: icon to be compared :return: similarity score [0,1.0] """ diff = dhash.get_num_bits_different(phash_origin, phash_candidate) return 1 - 1. * diff / (PHASH_SIZE * PHASH_SIZE * 2) def get_application_universal_id(self): parts = [self.get_package()] for c in self.get_certificates(): parts.append(c.fingerprint.upper()) return sha1(' '.join(parts).encode('utf-8')).hexdigest().upper() def get_certificates(self): certificates = [] def _my_name_init(self, oid, value, _type=_SENTINEL): if not isinstance(oid, ObjectIdentifier): raise TypeError( "oid argument must be an ObjectIdentifier instance.") if not isinstance(value, six.text_type): raise TypeError("value argument must be a text type.") if len(value) == 0: raise ValueError("Value cannot be an empty string") if _type == _SENTINEL: _type = _NAMEOID_DEFAULT_TYPE.get(oid, _ASN1Type.UTF8String) if not isinstance(_type, _ASN1Type): raise TypeError("_type must be from the _ASN1Type enum") self._oid = oid self._value = value self._type = _type NameAttribute.__init__ = _my_name_init signs = self.apk.get_signature_names() for s in signs: c = self.apk.get_certificate(s) cert = Certificate(c) certificates.append(cert) return certificates def get_apk_size(self): """ Get the APK file size in bytes :return: APK file size """ return os.path.getsize(self.apk_path) def get_sha256(self): """ Get the sha256sum of the APK file :return: hex sha256sum """ BLOCKSIZE = 65536 hasher = sha256() with open(self.apk_path, 'rb') as apk: buf = apk.read(BLOCKSIZE) while len(buf) > 0: hasher.update(buf) buf = apk.read(BLOCKSIZE) return hasher.hexdigest() def save_embedded_classes_in_file(self, file_path): """ Save list of embedded classes in file. :param file_path: file to write """ with open(file_path, 'w+') as f: f.write('\n'.join(self.get_embedded_classes())) def print_apk_infos(self): """ Print APK information """ permissions = self.get_permissions() libraries = self.get_libraries() certificates = self.get_certificates() print("=== Information") print('- APK path: {}'.format(self.apk_path)) print('- APK sum: {}'.format(self.get_sha256())) print('- App version: {}'.format(self.get_version())) print('- App version code: {}'.format(self.get_version_code())) print('- App UID: {}'.format(self.get_application_universal_id())) print('- App name: {}'.format(self.get_app_name())) print('- App package: {}'.format(self.get_package())) print('- App permissions: {}'.format(len(permissions))) for perm in permissions: print(' - {}'.format(perm)) print('- App libraries:') for lib in libraries: print(' - {}'.format(lib)) print('- Certificates: {}'.format(len(certificates))) for cert in certificates: print(' - {}'.format(cert)) def print_embedded_trackers(self): """ Print detected trackers """ trackers = self.detect_trackers() print('=== Found trackers: {}'.format(len(trackers))) for t in trackers: print(' - {}'.format(t.name))
def testApksignAPKs(self): # These APKs are from the apksign testcases and cover # all different signature algorithms as well as some error cases from androguard.core.bytecodes.apk import APK import zipfile from asn1crypto import x509, pem import binascii root = "examples/signing/apksig" # Correct values generated with openssl: certfp = { "dsa-1024.x509.pem": "fee7c19ff9bfb4197b3727b9fd92d95406b1bd96db99ea642f5faac019a389d7", "dsa-2048.x509.pem": "97cce0bab292c2d5afb9de90e1810b41a5d25c006a10d10982896aa12ab35a9e", "dsa-3072.x509.pem": "966a4537058d24098ea213f12d4b24e37ff5a1d8f68deb8a753374881f23e474", "ec-p256.x509.pem": "6a8b96e278e58f62cfe3584022cec1d0527fcb85a9e5d2e1694eb0405be5b599", "ec-p384.x509.pem": "5e7777ada7ee7ce8f9c4d1b07094876e5604617b7988b4c5d5b764a23431afbe", "ec-p521.x509.pem": "69b50381d98bebcd27df6d7df8af8c8b38d0e51e9168a95ab992d1a9da6082da", "rsa-1024.x509.pem": "bc5e64eab1c4b5137c0fbc5ed05850b3a148d1c41775cffa4d96eea90bdd0eb8", "rsa-16384.x509.pem": "f3c6b37909f6df310652fbd7c55ec27d3079dcf695dc6e75e22ba7c4e1c95601", "rsa-2048.x509.pem": "fb5dbd3c669af9fc236c6991e6387b7f11ff0590997f22d0f5c74ff40e04fca8", "rsa-3072.x509.pem": "483934461229a780010bc07cd6eeb0b67025fc4fe255757abbf5c3f2ed249e89", "rsa-4096.x509.pem": "6a46158f87753395a807edcc7640ac99c9125f6b6e025bdbf461ff281e64e685", "rsa-8192.x509.pem": "060d0a24fea9b60d857225873f78838e081795f7ef2d1ea401262bbd75a58234", } will_not_validate_correctly = [ "targetSandboxVersion-2.apk", "targetSandboxVersion-2.apk", "v1-only-with-cr-in-entry-name.apk", "v1-only-with-lf-in-entry-name.apk", "v1-only-with-nul-in-entry-name.apk", "v1-only-with-rsa-1024-cert-not-der2.apk", "v2-only-cert-and-public-key-mismatch.apk", "v2-only-with-dsa-sha256-1024-sig-does-not-verify.apk", ] # Collect possible hashes for certificates # Unfortunately, not all certificates are supplied... for apath in os.listdir(root): if apath in certfp: with open(os.path.join(root, apath), "rb") as fp: cert = x509.Certificate.load(pem.unarmor(fp.read())[2]) h = cert.sha256_fingerprint.replace(" ","").lower() self.assertEqual(h, certfp[apath]) self.assertIn(h, certfp.values()) for apath in os.listdir(root): if apath.endswith(".apk"): if apath == "v2-only-garbage-between-cd-and-eocd.apk" or \ apath == "v2-only-truncated-cd.apk": # Can not load as APK if sys.version_info.major == 2: # Different name in python2... with self.assertRaises(zipfile.BadZipfile): APK(os.path.join(root, apath)) else: with self.assertRaises(zipfile.BadZipFile): APK(os.path.join(root, apath)) continue elif apath in will_not_validate_correctly: # These APKs are faulty (by design) and will return a not correct fingerprint. # TODO: we need to check if we can prevent such errors... continue a = APK(os.path.join(root, apath)) self.assertIsInstance(a, APK) # Special error cases if apath == "v2-only-apk-sig-block-size-mismatch.apk": with self.assertRaises(AssertionError): a.is_signed_v2() continue elif apath == "v2-only-empty.apk": with self.assertRaises(AssertionError): a.is_signed_v2() continue if a.is_signed_v1(): if apath == "weird-compression-method.apk": with self.assertRaises(NotImplementedError): for c in a.get_signature_names(): a.get_certificate(c) elif apath == "v1-only-with-rsa-1024-cert-not-der.apk": for sig in a.get_signature_names(): c = a.get_certificate(sig) h = c.sha256_fingerprint.replace(" ","").lower() self.assertNotIn(h, certfp.values()) # print([apath, h]) # I do not know, why put this file? der = a.get_certificate_der(sig) apk.show_Certificate(c, True) apk.show_Certificate(c, False) self.assertEqual(hashlib.sha256(der).hexdigest(), h) pass else: for sig in a.get_signature_names(): c = a.get_certificate(sig) h = c.sha256_fingerprint.replace(" ","").lower() self.assertIn(h, certfp.values()) # Check that we get the same signature if we take the DER der = a.get_certificate_der(sig) self.assertEqual(hashlib.sha256(der).hexdigest(), h) if a.is_signed_v2(): if apath == "weird-compression-method.apk": with self.assertRaises(NotImplementedError): a.get_certificates_der_v2() elif apath == "v2-only-with-rsa-pkcs1-sha256-1024-cert-not-der.apk": # FIXME # Not sure what this one should do... but the certificate fingerprint is weird # as the hash over the DER is not the same when using the certificate continue else: for c in a.get_certificates_der_v2(): cert = x509.Certificate.load(c) h = cert.sha256_fingerprint.replace(" ","").lower() self.assertIn(h, certfp.values()) # Check that we get the same signature if we take the DER self.assertEqual(hashlib.sha256(c).hexdigest(), h)
def testApksignAPKs(self): # These APKs are from the apksign testcases and cover # all different signature algorithms as well as some error cases from androguard.core.bytecodes.apk import APK import zipfile from asn1crypto import x509, pem import binascii root = "examples/signing/apksig" # Correct values generated with openssl: # In the apksig repo:src/test/resources/com/android/apksig # for f in *.pem; do openssl x509 -in $f -noout -sha256 -fingerprint; done certfp = { 'dsa-1024.x509.pem': 'fee7c19ff9bfb4197b3727b9fd92d95406b1bd96db99ea642f5faac019a389d7', 'dsa-2048.x509.pem': '97cce0bab292c2d5afb9de90e1810b41a5d25c006a10d10982896aa12ab35a9e', 'dsa-3072.x509.pem': '966a4537058d24098ea213f12d4b24e37ff5a1d8f68deb8a753374881f23e474', 'ec-p256.x509.pem': '6a8b96e278e58f62cfe3584022cec1d0527fcb85a9e5d2e1694eb0405be5b599', 'ec-p384.x509.pem': '5e7777ada7ee7ce8f9c4d1b07094876e5604617b7988b4c5d5b764a23431afbe', 'ec-p521.x509.pem': '69b50381d98bebcd27df6d7df8af8c8b38d0e51e9168a95ab992d1a9da6082da', 'rsa-1024_2.x509.pem': 'eba3685e799f59804684abebf0363e14ccb1c213e2b954a22669714ed97f61e9', 'rsa-1024.x509.pem': 'bc5e64eab1c4b5137c0fbc5ed05850b3a148d1c41775cffa4d96eea90bdd0eb8', 'rsa-16384.x509.pem': 'f3c6b37909f6df310652fbd7c55ec27d3079dcf695dc6e75e22ba7c4e1c95601', 'rsa-2048_2.x509.pem': '681b0e56a796350c08647352a4db800cc44b2adc8f4c72fa350bd05d4d50264d', 'rsa-2048_3.x509.pem': 'bb77a72efc60e66501ab75953af735874f82cfe52a70d035186a01b3482180f3', 'rsa-2048.x509.pem': 'fb5dbd3c669af9fc236c6991e6387b7f11ff0590997f22d0f5c74ff40e04fca8', 'rsa-3072.x509.pem': '483934461229a780010bc07cd6eeb0b67025fc4fe255757abbf5c3f2ed249e89', 'rsa-4096.x509.pem': '6a46158f87753395a807edcc7640ac99c9125f6b6e025bdbf461ff281e64e685', 'rsa-8192.x509.pem': '060d0a24fea9b60d857225873f78838e081795f7ef2d1ea401262bbd75a58234', } will_not_validate_correctly = [ "targetSandboxVersion-2.apk", "targetSandboxVersion-2.apk", "v1-only-with-cr-in-entry-name.apk", "v1-only-with-lf-in-entry-name.apk", "v1-only-with-nul-in-entry-name.apk", "v1-only-with-rsa-1024-cert-not-der2.apk", "v2-only-cert-and-public-key-mismatch.apk", "v2-only-with-dsa-sha256-1024-sig-does-not-verify.apk", "debuggable-boolean.apk", "debuggable-resource.apk", ] # Collect possible hashes for certificates # Unfortunately, not all certificates are supplied... for apath in os.listdir(root): if apath in certfp: with open(os.path.join(root, apath), "rb") as fp: cert = x509.Certificate.load(pem.unarmor(fp.read())[2]) h = cert.sha256_fingerprint.replace(" ","").lower() self.assertEqual(h, certfp[apath]) self.assertIn(h, certfp.values()) for apath in os.listdir(root): if apath.endswith(".apk"): if apath == "v2-only-garbage-between-cd-and-eocd.apk" or \ apath == "v2-only-truncated-cd.apk" or \ apath == "v1v2v3-with-rsa-2048-lineage-3-signers-invalid-zip.apk": # Can not load as APK with self.assertRaises(zipfile.BadZipFile): APK(os.path.join(root, apath)) continue elif apath in will_not_validate_correctly: # These APKs are faulty (by design) and will return a not correct fingerprint. # TODO: we need to check if we can prevent such errors... continue a = APK(os.path.join(root, apath)) self.assertIsInstance(a, APK) # Test if the correct method returns True, while others return # False m_tests = {'1': a.is_signed_v1, '2': a.is_signed_v2, '3': a.is_signed_v3} # These APKs will raise an error excluded = [ "v1v2v3-with-rsa-2048-lineage-3-signers-no-sig-block.apk", "v2-only-apk-sig-block-size-mismatch.apk", "v2-only-empty.apk", "v2-only-wrong-apk-sig-block-magic.apk", "v2-stripped.apk", "v2-stripped-with-ignorable-signing-schemes.apk", "v2v3-signed-v3-block-stripped.apk", "v3-only-empty.apk", "v3-only-with-ecdsa-sha512-p384-wrong-apk-sig-block-magic.apk", "v3-only-with-rsa-pkcs1-sha512-4096-apk-sig-block-size-mismatch.apk", "v3-stripped.apk", ] if apath[0] == "v" and apath not in excluded: methods = apath.split("-", 1)[0].split("v")[1:] for m, f in m_tests.items(): if m in methods: self.assertTrue(f()) else: self.assertFalse(f()) # Special error cases if apath == "v2-only-apk-sig-block-size-mismatch.apk": with self.assertRaises(apk.BrokenAPKError): a.is_signed_v2() continue elif apath == "v2-only-empty.apk": with self.assertRaises(apk.BrokenAPKError): a.is_signed_v2() continue elif apath == "v3-only-with-rsa-pkcs1-sha512-4096-apk-sig-block-size-mismatch.apk": with self.assertRaises(apk.BrokenAPKError): a.is_signed_v3() continue if a.is_signed_v1(): if apath == "weird-compression-method.apk": with self.assertRaises(NotImplementedError): for c in a.get_signature_names(): a.get_certificate(c) elif apath == "v1-only-with-rsa-1024-cert-not-der.apk": for sig in a.get_signature_names(): c = a.get_certificate(sig) h = c.sha256_fingerprint.replace(" ","").lower() self.assertNotIn(h, certfp.values()) # print([apath, h]) # I do not know, why put this file? der = a.get_certificate_der(sig) apk.show_Certificate(c, True) apk.show_Certificate(c, False) self.assertEqual(hashlib.sha256(der).hexdigest(), h) pass else: for sig in a.get_signature_names(): c = a.get_certificate(sig) h = c.sha256_fingerprint.replace(" ","").lower() self.assertIn(h, certfp.values()) # Check that we get the same signature if we take the DER der = a.get_certificate_der(sig) self.assertEqual(hashlib.sha256(der).hexdigest(), h) if a.is_signed_v2(): if apath == "weird-compression-method.apk": with self.assertRaises(NotImplementedError): a.get_certificates_der_v2() elif apath == "v2-only-with-rsa-pkcs1-sha256-1024-cert-not-der.apk": # FIXME # Not sure what this one should do... but the certificate fingerprint is weird # as the hash over the DER is not the same when using the certificate continue else: for c in a.get_certificates_der_v2(): cert = x509.Certificate.load(c) h = cert.sha256_fingerprint.replace(" ","").lower() self.assertIn(h, certfp.values()) # Check that we get the same signature if we take the DER self.assertEqual(hashlib.sha256(c).hexdigest(), h) if a.is_signed_v3(): print(apath) if apath == "weird-compression-method.apk": with self.assertRaises(NotImplementedError): a.get_certificates_der_v3() elif apath == "v3-only-with-rsa-pkcs1-sha256-3072-sig-does-not-verify.apk" or \ apath == "v3-only-cert-and-public-key-mismatch.apk": cert = x509.Certificate.load(a.get_certificates_der_v3()[0]) h = cert.sha256_fingerprint.replace(" ","").lower() self.assertNotIn(h, certfp.values()) else: for c in a.get_certificates_der_v3(): cert = x509.Certificate.load(c) h = cert.sha256_fingerprint.replace(" ","").lower() self.assertIn(h, certfp.values()) # Check that we get the same signature if we take the DER self.assertEqual(hashlib.sha256(c).hexdigest(), h)