def extract_manifest(apk_file_path): """Extracts the AndroidManifest.xml file from an APK and write it in a new file. Parameters: ----------- apk_file_path: str APK relative file path """ apk = APK(apk_file_path) app_name = apk.get_app_name() axml = apk.get_android_manifest_axml() version = apk.get_androidversion_name() manifest = minidom.parseString(axml.get_buff()).toprettyxml() # Building filename app_name = app_name.replace(' ', '') version = version.replace(' ', '_') manifest_filename = "{}-{}.xml".format(app_name, version) manifest_path = os.path.join(config.DATA_FOLDER, manifest_filename) with open(manifest_path, 'w', encoding='utf8') as file: file.write(manifest)
def testFrameworkResAPK(self): from androguard.core.bytecodes.apk import APK a = APK("examples/tests/lineageos_nexus5_framework-res.apk") self.assertEqual(a.get_app_name(), 'Android System') self.assertEqual(a.get_package(), 'android')
def get_apk_info(self): apk = APK(self.apk_file) app_icon_file = apk.get_app_icon() app_icon_data = apk.get_file(app_icon_file) size = (256, 256) buffered = BytesIO() im = Image.open(BytesIO(app_icon_data)) im = im.resize(size, Image.ANTIALIAS) im.save(buffered, "PNG") app_icon_b64 = "data:image/png;base64," + base64.b64encode( buffered.getvalue()).decode('utf-8') self.package_name = apk.get_package() self.app_name = apk.get_app_name() self.report_saver.package_name = self.package_name self.report_saver.app_name = self.app_name self.report_saver.version = apk.get_androidversion_code() self.report_saver.app_icon = app_icon_b64 permission_parser = PermissionParser(mode='groups') permission_values = permission_parser.transform( apk.get_permissions()).flatten().tolist() permission_labels = permission_parser.labels() self.report_saver.permissions_actual = { permission_labels[i]: bool(v) for i, v in enumerate(permission_values) }
def get_apk_info(f, root=None): """ 获取apk信息 :param root: :param f: :return: """ if root: apk_path = os.path.join(root, f) else: apk_path = os.path.join(download_dir, f) apk_info = [] try: apk = APK(apk_path) if apk.is_valid_APK(): apk_info.append(f) apk_info.append(get_file_md5(apk_path)) apk_info.append(apk.get_app_name()) apk_info.append(apk.get_package()) apk_info.append(get_cert_md5(apk)) apk_info.append(apk.get_androidversion_name()) except Exception as e: print(f + ' ->>', e) return apk_info
class App(object): def __init__(self, app_path, root_path, app_name): print("Root path:"+root_path) assert app_path is not None self.logger = logging.getLogger(self.__class__.__name__) self.app_path = app_path from androguard.core.bytecodes.apk import APK self.apk = APK(self.app_path) self.package_name = self.apk.get_package() self.main_activity = self.apk.get_main_activity() self.permissions = self.apk.get_permissions() self.activities = self.apk.get_activities() if app_name is not None: self.app_name = app_name else: self.app_name = self.apk.get_app_name() print("Main activity:"+self.main_activity) print("Package name:"+self.package_name) self.output_path=root_path+self.package_name def get_package_name(self): """ get package name of current app :return: """ return self.package_name
def testAPKManifest(self): from androguard.core.bytecodes.apk import APK a = APK("examples/android/TestsAndroguard/bin/TestActivity.apk", testzip=True) self.assertEqual(a.get_app_name(), "TestsAndroguardApplication") self.assertEqual(a.get_app_icon(), "res/drawable-hdpi/icon.png") self.assertEqual(a.get_app_icon(max_dpi=120), "res/drawable-ldpi/icon.png") self.assertEqual(a.get_app_icon(max_dpi=160), "res/drawable-mdpi/icon.png") self.assertEqual(a.get_app_icon(max_dpi=240), "res/drawable-hdpi/icon.png") self.assertIsNone(a.get_app_icon(max_dpi=1)) self.assertEqual(a.get_main_activity(), "tests.androguard.TestActivity") self.assertEqual(a.get_package(), "tests.androguard") self.assertEqual(a.get_androidversion_code(), '1') self.assertEqual(a.get_androidversion_name(), "1.0") self.assertEqual(a.get_min_sdk_version(), "9") self.assertEqual(a.get_target_sdk_version(), "16") self.assertIsNone(a.get_max_sdk_version()) self.assertEqual(a.get_permissions(), []) self.assertEqual(a.get_declared_permissions(), []) self.assertTrue(a.is_valid_APK())
def testFrameworkResAPK(self): from androguard.core.bytecodes.apk import APK a = APK("examples/tests/lineageos_nexus5_framework-res.apk") self.assertEqual(a.get_app_name(), 'Android System') self.assertEqual(a.get_package(), 'android')
def create_perm_vector(apk_file): try: a = APK(apk_file) except: return None apk_file_name = a.get_app_name() perms = a.get_permissions() perm_vector = [] * v for permission in PERMISSIONS: hit = 1 if permission in perms else 0 perm_vector.append(hit) return apk_file_name, perms, list(perm_vector)
def testFallback(self): a = APK("examples/tests/com.teleca.jamendo_35.apk") # Should use the fallback self.assertEqual(a.get_app_name(), "Jamendo") res_parser = a.get_android_resources() res_id = int(a.get_attribute_value('application', 'label')[1:], 16) # Default Mode, no config self.assertEqual(len(res_parser.get_res_configs(res_id)), 2) # With default config, but fallback self.assertEqual(len(res_parser.get_res_configs(res_id, axml.ARSCResTableConfig.default_config())), 1) # With default config but no fallback self.assertEqual(len(res_parser.get_res_configs(res_id, axml.ARSCResTableConfig.default_config(), fallback=False)), 0) # Also test on resolver: self.assertListEqual(list(map(itemgetter(1), res_parser.get_resolved_res_configs(res_id))), ["Jamendo", "Jamendo"]) self.assertListEqual(list(map(itemgetter(1), res_parser.get_resolved_res_configs(res_id, axml.ARSCResTableConfig.default_config()))), ["Jamendo"])
def get_apk_information(_file): ''' get apk information by androguard params: _file: apk file path return: 1. apkname: apk name 2. apkcode: apk version code, eg: 222, 333 3. apkiconpath: get icon path ''' apkinformation = APK(_file) apkname = apkinformation.get_app_name() apkcode = apkinformation.get_androidversion_code() apkiconpath = apkinformation.get_app_icon() # packagename = apkinformation.get_package() return apkname, apkcode, apkiconpath
def testFallback(self): a = APK("examples/tests/com.teleca.jamendo_35.apk") # Should use the fallback self.assertEqual(a.get_app_name(), "Jamendo") res_parser = a.get_android_resources() res_id = int(a.get_element('application', 'label')[1:], 16) # Default Mode, no config self.assertEqual(len(res_parser.get_res_configs(res_id)), 2) # With default config, but fallback self.assertEqual(len(res_parser.get_res_configs(res_id, axml.ARSCResTableConfig.default_config())), 1) # With default config but no fallback self.assertEqual(len(res_parser.get_res_configs(res_id, axml.ARSCResTableConfig.default_config(), fallback=False)), 0) # Also test on resolver: self.assertListEqual(list(map(itemgetter(1), res_parser.get_resolved_res_configs(res_id))), ["Jamendo", "Jamendo"]) self.assertListEqual(list(map(itemgetter(1), res_parser.get_resolved_res_configs(res_id, axml.ARSCResTableConfig.default_config()))), ["Jamendo"])
def testAPKManifest(self): from androguard.core.bytecodes.apk import APK a = APK("examples/android/TestsAndroguard/bin/TestActivity.apk", testzip=True) self.assertEqual(a.get_app_name(), "TestsAndroguardApplication") self.assertEqual(a.get_app_icon(), "res/drawable-hdpi/icon.png") self.assertEqual(a.get_app_icon(max_dpi=120), "res/drawable-ldpi/icon.png") self.assertEqual(a.get_app_icon(max_dpi=160), "res/drawable-mdpi/icon.png") self.assertEqual(a.get_app_icon(max_dpi=240), "res/drawable-hdpi/icon.png") self.assertIsNone(a.get_app_icon(max_dpi=1)) self.assertEqual(a.get_main_activity(), "tests.androguard.TestActivity") self.assertEqual(a.get_package(), "tests.androguard") self.assertEqual(a.get_androidversion_code(), '1') self.assertEqual(a.get_androidversion_name(), "1.0") self.assertEqual(a.get_min_sdk_version(), "9") self.assertEqual(a.get_target_sdk_version(), "16") self.assertIsNone(a.get_max_sdk_version()) self.assertEqual(a.get_permissions(), []) self.assertEqual(a.get_declared_permissions(), []) self.assertTrue(a.is_valid_APK())
def get_apk_info(self): apk = APK(self.apk_file) app_icon_file = apk.get_app_icon() app_icon_data = apk.get_file(app_icon_file) size = (256, 256) buffered = BytesIO() im = Image.open(BytesIO(app_icon_data)) im = im.resize(size, Image.ANTIALIAS) im.save(buffered, "PNG") app_icon_b64 = "data:image/png;base64," + base64.b64encode( buffered.getvalue()).decode('utf-8') self.package_name = apk.get_package() self.app_name = apk.get_app_name() self.report_saver.package_name = self.package_name self.report_saver.app_name = self.app_name self.report_saver.version = apk.get_androidversion_code() self.report_saver.app_icon = app_icon_b64
def processCheck(apkPath,total,already): global rootdir unionFingerOutputPath = rootdir+'unionFingerOutput/' unionOutputPath = rootdir+'unionOutput/' fidoOutputPath = rootdir+'fidoOutput/' fidoPermissionOutputPath = rootdir+'fidoPermissionOutput/' failedPath=rootdir+'failed/' unionfs = r'cn.com.union.fido.ui.finger.FingerActivity' #unionfs = r'cn.com.union.fido.service.AuthenticatorService' unions = r'union.fido' fidos = r'fido' try: a = APK(apkPath) except: print('[{0}/{1}]Analysis Failed {2}'.format(already,total,apkPath)) return activities = a.get_activities() name = a.get_app_name() find = False findstr = [] for activity in activities: if activity==unionfs: find=True findstr.append('[ACTIVITY]'+activity) if find: print('[{0}/{1}]FIND unionFinger in {2}'.format(already,total,apkPath)) copyTo(apkPath,unionFingerOutputPath,findstr,name) return findstr.clear() find=False for activity in activities: if(re.search(unions,activity.lower())): find=True findstr.append('[ACTIVITY]'+activity) permissions = a.get_permissions() for permission in permissions: if re.search(unions,permission.lower()): find=True, findstr.append('[PERMISSION]'+permission) services = a.get_services() for service in services: if re.search(unions,service.lower()): find=True, findstr.append('[SERVICE]'+service) if find: print('[{0}/{1}]FIND union in {2}'.format(already,total,apkPath)) copyTo(apkPath,unionOutputPath,findstr,name) return findstr.clear() find=False for activity in activities: if(re.search(fidos,activity.lower())): find=True findstr.append('[ACTIVITY]'+activity) hasFidoPermission = False fidoPermission=[] for permission in permissions: if re.search(fidos,permission.lower()): find=True, hasFidoPermission=True findstr.append('[PERMISSION]'+permission) fidoPermission.append('[PERMISSION]'+permission) if hasFidoPermission: print('[{0}/{1}]FIND fido permission in {2}'.format(already,total,apkPath)) copyTo(apkPath,fidoPermissionOutputPath,fidoPermission,name) for service in services: if re.search(fidos,service.lower()): find=True, findstr.append('[SERVICE]'+service) if find: print('[{0}/{1}]FIND fido in {2}'.format(already,total,apkPath)) copyTo(apkPath,fidoOutputPath,findstr,name) return print('[{0}/{1}]Nothing FOUND in {2}'.format(already,total,apkPath))
class XAPK: def __init__(self, folder): self.folder = Path(folder) for x in self.folder.glob('*.apk'): self.apk_src = Path(x) break for x in self.folder.glob('*.obb'): self.obb_src = Path(x) break self.apk = APK(self.apk_src) self.manifest = self.make_manifest() self.icon = self.apk.get_file(self.apk.get_app_icon()) def make_manifest(self): apk_size = self.apk_src.stat().st_size if self.obb_src: obb_size = self.obb_src.stat().st_size else: obb_size = 0 total_size = apk_size + obb_size filename = self.apk.get_filename() manifest = {} manifest['xapk_version'] = 1 manifest['package_name'] = self.apk.get_package() manifest['name'] = self.apk.get_app_name() # manifest['locales_name'] = {} # TODO manifest['version_code'] = self.apk.get_androidversion_code() manifest['version_name'] = self.apk.get_androidversion_name() manifest['min_sdk_version'] = self.apk.get_min_sdk_version() manifest['target_sdk_version'] = self.apk.get_target_sdk_version() manifest['permissions'] = self.apk.get_declared_permissions() manifest['total_size'] = total_size manifest['expansions'] = [] if obb_size: main_obb = {} main_obb[ 'file'] = 'Android/obb/{package_name}/main.{version_code}.{package_name}.obb'.format( **manifest) main_obb['install_location'] = 'EXTERNAL_STORAGE' main_obb[ 'install_path'] = 'Android/obb/{package_name}/main.{version_code}.{package_name}.obb'.format( **manifest) manifest['expansions'].push(main_obb) return manifest def save(self): self.name = '{package_name}_v{version_name}.xapk'.format( **self.manifest) zip_path = self.folder.joinpath(self.name) zip_dir = tempfile.mkdtemp() try: print('copying apk to temp directory...') apk_name = '{package_name}.apk'.format(**self.manifest) apk_src = self.apk_src.resolve() apk_dest = PurePath(zip_dir).joinpath(apk_name) shutil.copy2(apk_src, apk_dest) print('apk: OK') if self.manifest.get('expansions'): print('copying obb to temp directory...') obb_name = self.manifest['expansions'][0]['install_path'] obb_src = self.obb_src.resolve() obb_dest = PurePath(zip_dir).joinpath(obb_name) os.makedirs(Path(obb_dest).parent, exist_ok=True) shutil.copy2(obb_src, obb_dest) print('obb: OK') else: print('no obb found') print('creating icon in temp directory...') icon = self.icon icon_dest = PurePath(zip_dir).joinpath('icon.png') with open(icon_dest, 'wb') as iconfile: iconfile.write(icon) print('icon: OK') print('creating manifest in temp directory...') manifest_dest = PurePath(zip_dir).joinpath('manifest.json') with open(manifest_dest, 'w') as manifestfile: s = json.dumps(self.manifest, separators=(':', ',')) manifestfile.write(s) print('manifest: OK') print('creating xapk archive...') with zipfile.ZipFile(zip_path, 'w', compression=zipfile.ZIP_DEFLATED) as zfd: for root, dirs, files in os.walk(zip_dir): for f in files: filename = os.path.join(root, f) zfd.write(filename, os.path.relpath(filename, zip_dir)) print('xapk: OK') finally: print('cleaning up temp directory...') shutil.rmtree(zip_dir) print('cleanup: OK')
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 meta_fetch(apk): app = APK(apk) return app.get_app_name(), app.get_target_sdk_version(), str( round(os.stat(apk).st_size / (1024 * 1024), 2)) + ' MB'
def analyze(path): try: start = process_time() hashfunctions = dict(md5=hashlib.md5, sha1=hashlib.sha1, sha256=hashlib.sha256, sha512=hashlib.sha512) a = APK(path) certs = set( a.get_certificates_der_v3() + a.get_certificates_der_v2() + [a.get_certificate_der(x) for x in a.get_signature_names()]) for cert in certs: x509_cert = x509.Certificate.load(cert) issuer = { 'commonName': None, 'organizationName': None, 'organizationalUnitName': None, 'countryName': None, 'stateOrProvinceName': None, 'localityName': None } subject = { 'commonName': None, 'organizationName': None, 'organizationalUnitName': None, 'countryName': None, 'stateOrProvinceName': None, 'localityName': None } strIssuer = get_certificate_name_string(x509_cert.issuer, short=False) strSubject = get_certificate_name_string(x509_cert.subject, short=False) arrIssuer = strIssuer.split(',') for i in arrIssuer: if i.lstrip().split('=')[0] == 'commonName': issuer['commonName'] = i.lstrip().split('=')[1] elif i.lstrip().split('=')[0] == 'organizationName': issuer['organizationName'] = i.lstrip().split('=')[1] elif i.lstrip().split('=')[0] == 'organizationalUnitName': issuer['organizationalUnitName'] = i.lstrip().split('=')[1] elif i.lstrip().split('=')[0] == 'countryName': issuer['countryName'] = i.lstrip().split('=')[1] elif i.lstrip().split('=')[0] == 'stateOrProvinceName': issuer['stateOrProvinceName'] = i.lstrip().split('=')[1] elif i.lstrip().split('=')[0] == 'localityName': issuer['localityName'] = i.lstrip().split('=')[1] arrSubject = strSubject.split(',') for i in arrSubject: if i.lstrip().split('=')[0] == 'commonName': subject['commonName'] = i.lstrip().split('=')[1] elif i.lstrip().split('=')[0] == 'organizationName': subject['organizationName'] = i.lstrip().split('=')[1] elif i.lstrip().split('=')[0] == 'organizationalUnitName': subject['organizationalUnitName'] = i.lstrip().split( '=')[1] elif i.lstrip().split('=')[0] == 'countryName': subject['countryName'] = i.lstrip().split('=')[1] elif i.lstrip().split('=')[0] == 'stateOrProvinceName': subject['stateOrProvinceName'] = i.lstrip().split('=')[1] elif i.lstrip().split('=')[0] == 'localityName': subject['localityName'] = i.lstrip().split('=')[1] for k, v in hashfunctions.items(): if k == 'md5': md5 = v(cert).hexdigest() elif k == 'sha1': sha1 = v(cert).hexdigest() elif k == 'sha256': sha256 = v(cert).hexdigest() elif k == 'sha512': sha512 = v(cert).hexdigest() md5 = md5 appName = a.get_app_name() fileSize = os.stat(a.get_filename()).st_size sha1 = sha1 sha256 = sha256 sha512 = sha512 timestamp = time.time() dateTime = datetime.fromtimestamp(timestamp) timeOfSubmit = dateTime.strftime("%Y-%m-%d %H:%M:%S") package = a.get_package() androidversionCode = a.get_androidversion_code() androidversionName = a.get_androidversion_name() minSDKVersion = a.get_min_sdk_version() maxSDKVersion = a.get_max_sdk_version() targetSDKVersion = a.get_target_sdk_version() mainActivity = a.get_main_activity() attributes = { 'validFrom': x509_cert['tbs_certificate']['validity'] ['not_before'].native.strftime("%Y-%m-%d %H:%M:%S"), 'validTo': x509_cert['tbs_certificate']['validity'] ['not_after'].native.strftime("%Y-%m-%d %H:%M:%S"), 'serialNumber': hex(x509_cert.serial_number), 'hashAlgorithm': x509_cert.hash_algo, 'signatureAlgorithm': x509_cert.signature_algo } certificateAttributes = json.dumps(attributes) certificateIssuer = json.dumps(issuer) certificateSubject = json.dumps(subject) declaredPermissions = json.dumps(a.get_declared_permissions()) requestedPermissions = json.dumps(a.get_permissions()) activities = json.dumps(a.get_activities()) services = json.dumps(a.get_services()) receivers = json.dumps(a.get_receivers()) providers = json.dumps(a.get_providers()) stop = process_time() analysisTime = stop - start connect = mysql.connect() cursor = connect.cursor() sql = "INSERT INTO tbl_apkinfo (md5, appName, fileSize, analysisTime, sha1, sha256, sha512, firstSubmission, lastSubmission, package, androidversionCode, androidversionName, minSDKVersion, maxSDKVersion, targetSDKVersion, mainActivity, certificateAttributes, certificateIssuer, certificateSubject, declaredPermissions, requestedPermissions, activities, services, providers, receivers) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)" param = (md5, appName, fileSize, analysisTime, sha1, sha256, sha512, timeOfSubmit, timeOfSubmit, package, androidversionCode, androidversionName, minSDKVersion, maxSDKVersion, targetSDKVersion, mainActivity, certificateAttributes, certificateIssuer, certificateSubject, declaredPermissions, requestedPermissions, activities, services, providers, receivers) cursor.execute(sql, param) connect.commit() connect.close() androaxml_main(path, os.path.join(app.config['OUTPUT_PATH'], md5 + '.xml')) return True except: return False
class MyAPK: def __init__(self, name_file, conf, file_log, tag, string_to_find, logger, api_monitor_dict=None, network_dict=None, dynamic_time=0, use_smaliparser=True): self.name_apk = name_file self.name_only_apk = self.name_apk.split("/")[-1].rsplit(".", 1)[0] self.conf = conf self.apk = APK(name_file) self.app_name = self.apk.get_app_name() self.package_name = self.apk.get_package() self.target_sdk = self.apk.get_target_sdk_version() self.dalviks_format = None self.analysis_object = None self.dict_file_with_string = dict( ) # file che contengono la stringa ricercata self.string_to_find = string_to_find # stringa da cercare self.is_contain_permission = False # se contiene i permessi del file conf self.url_loaded = list() # list url that has been loaded # se contiene i file ibridi --> probabilmente app ibrida self.is_contain_file_hybrid = False # pagine_html con iframe se contengono csp [True o False] self.find_csp = dict() # se contiene i metodi all'interno del file conf.json self.is_contains_all_methods = False self.zip = zipfile.ZipFile(self.name_apk) # get zip object from apk self.list_file = self.zip.namelist() # tutti i file all'interno self.html_file = FileAnalysis.find_html_file(self.list_file) self.file_log = file_log # name to file log self.javascript_enabled = False self.internet_enabled = False self.file_vulnerable_frame_confusion = list() self.file_with_string_iframe = list() self.isHybrid = None # dict indexes with name method and get encoded methods where function was called self.method = dict() self.all_url = list() # all url in the apk self.file_download_to_analyze = dict() self.search_tag = tag self.md5_file_to_url = dict( ) # dict with indexes with name and get url remote self.file_config_hybrid = None self.list_origin_access = list() self.logger = logger self.api_monitor_dict = api_monitor_dict self.network_dict = network_dict self.file_hybrid = list() self.javascript_interface = False self.javascript_file = FileAnalysis.find_js_file(self.list_file) self.src_iframe = dict() self.page_xss_vuln = dict() self.is_vulnerable_frame_confusion = False self.http_connection = list() self.http_connection_static = list() self.all_http_connection = list() self.url_dynamic = list() self.use_smaliparser = use_smaliparser self.use_analyze = not use_smaliparser self.method_2_value = dict() self.dynamic_javascript_enabled = False self.analysis_dynamic_done = api_monitor_dict is not None or network_dict is not None self.dynamic_javascript_interface = False self.dynamic_time = dynamic_time # time execution analysis dynamic self.all_url_dynamic = list() self.load_url_dynamic = list() self.app_use_sandbox = False self.file_with_sandbox = dict() # app use sandbox def read(self, filename, binary=True): with open(filename, 'rb' if binary else 'r') as f: return f.read() def check_permission(self, list_permission_to_find): """ check permission hybrid app """ use_permission_checker = True if not use_permission_checker: permission_find = list() for permission_to_check in list_permission_to_find: if permission_to_check in self.apk.get_permissions(): permission_find.append(True) # contenere tutti i permessi if permission_to_check == "android.permission.INTERNET": self.internet_enabled = True # print(permission_to_check) self.logger.logger.info("[Permission declared Start]") for p in self.apk.get_permissions(): self.logger.logger.info(p) self.logger.logger.info("[Permission End]\n") self.is_contain_permission = len(permission_find) == len( list_permission_to_find) else: if "PermissionChecker.jar" in os.listdir("."): dir_permission_checker = "PermissionChecker.jar" else: dir_permission_checker = os.path.join("FCDroid", "PermissionChecker.jar") try: cmd_permission_checker = [ "java", "-jar", dir_permission_checker, self.name_apk ] process = subprocess.Popen(cmd_permission_checker, stdout=subprocess.PIPE) result = process.communicate()[0] # error here result = json.loads(result) # requiredAndUsed = result['requiredAndUsed'] notRequiredButUsed = result['notRequiredButUsed'] declared = result['declared'] # requiredButNotUsed = result['requiredButNotUsed'] list_permission = list(set().union(notRequiredButUsed, declared)) permission_find = list() for permission_to_check in list_permission_to_find: if permission_to_check in list_permission: permission_find.append( True) # contenere tutti i permessi if permission_to_check == "android.permission.INTERNET": self.internet_enabled = True self.logger.logger.info( "[Permission declared and not required but used Start]") for p in list_permission: self.logger.logger.info(p) self.logger.logger.info("[Permission End]\n") self.is_contain_permission = len(permission_find) == len( list_permission_to_find) except Exception as e: permission_find = list() for permission_to_check in list_permission_to_find: if permission_to_check in self.apk.get_permissions(): permission_find.append( True) # contenere tutti i permessi if permission_to_check == "android.permission.INTERNET": self.internet_enabled = True # print(permission_to_check) self.logger.logger.info("[Permission declared Start]") for p in self.apk.get_permissions(): self.logger.logger.info(p) self.logger.logger.info("[Permission End]\n") self.is_contain_permission = len(permission_find) == len( list_permission_to_find) def is_hybird(self): """ function to check se apk is hybrid, 1) if contain file from conf.json (cordova/plugin/phonegap/config) 2) if present permission internet (inutile) """ if self.isHybrid is None: list_file_to_find = self.conf["file_to_check"] list_permission_to_find = self.conf["permissions_to_check"] self.is_contain_file_hybrid, self.file_hybrid = FileAnalysis.check_file_hybrid( self.list_file, list_file_to_find) if self.is_contain_file_hybrid: self.logger.logger.info("Hybrid file found are: " + str(self.file_hybrid)) self.check_permission(list_permission_to_find) self.isHybrid = self.is_contain_permission and self.is_contain_file_hybrid # using apktool FNULL = open(os.devnull, 'w') print(bcolors.WARNING + "[*] Starting apktool " + bcolors.ENDC) self.logger.logger.info("Starting apktool") cmd = [ "apktool", "d", "-o", "temp_dir_" + self.name_only_apk, self.name_apk, "-f" ] subprocess.call(cmd, stdout=FNULL, stderr=subprocess.STDOUT) try: if self.isHybrid: # now can search file in temp_dir if os.path.exists("temp_dir_{0}/res/xml/config.xml".format( self.name_only_apk)): file_xml = open( "temp_dir_{0}/res/xml/config.xml".format( self.name_only_apk)) file_data_xml = str(file_xml.read()) self.file_config_hybrid = file_data_xml # parsing file config self.check_whitelist() except OSError as e: print( bcolors.FAIL + "File config.xml not found, it is necessary to decompile the application first" + bcolors.ENDC) # remove dir self.logger.logger.error( "[ERROR file config.xmls] {0} \n".format(e)) return self.isHybrid def check_whitelist(self): """ function that obtain access origin from file config.xml """ # get xml_object ElementTree if self.file_config_hybrid is not None and self.isHybrid: self.list_origin_access = list() root = ET.fromstring(self.file_config_hybrid) xmlns = "{http://www.w3.org/ns/widgets}" # default namespace # TODO aggiungere altri elementi della whitelist # 1) <allow-navigation href="http://*/*" /> # Controls which URLs the WebView itself can be navigated to. Applies to top-level navigations only. # 2) <allow-intent href="http://*/*" /> # Controls which URLs the app is allowed to ask the system to open. By default, no external URLs are allowed # 3) <access origin="http://google.com" /> # Controls which network requests (images, XHRs, etc) are allowed to be made (via cordova native hooks). for child in root.findall(xmlns + "access"): # print( child.tag, child.attrib.get("origin")) self.list_origin_access.append(child.attrib.get("origin")) self.logger.logger.info("[INIT ACCESS ORIGIN LIST]") for value in self.list_origin_access: self.logger.logger.info("origin: %s", value) self.logger.logger.info("[END ACCESS ORIGIN LIST]\n") def analyze_xss_dom(self, file_name, file_content): """ search static dom xss based on regex """ # TODO se file_name end with js use TaintJS altrimenti usare questo # per usare taint js salvare il contenuto in una dir temporanea e usarlo # da li dentro try: print("file xss dom analyze {0}".format(file_name)) if file_name.endswith(".js"): file_open_temp = "FCDroid/TaintJS/temp_file_to_analyze.js" file_to_write = open(file_open_temp, "w") file_to_write.write(file_content) file_to_write.close() cmd_node = [ "node", "--max-old-space-size=4096", "FCDroid/TaintJS/app.js", file_open_temp ] process = subprocess.Popen(cmd_node, stdout=subprocess.PIPE, stderr=subprocess.PIPE) out, err = process.communicate() out = out.decode('utf-8').strip() err = err.decode('utf-8') os.remove(file_open_temp) # no error if err != '': # no error if out == 'true': # is vuln self.page_xss_vuln[file_name] = True else: page_analyze = XSScanner(file_name, file_content) page_analyze.analyze_page() if len(page_analyze.sink) > 0 or len( page_analyze.source) > 0: self.page_xss_vuln[file_name] = page_analyze else: soup = BeautifulSoup(file_content, 'html.parser') scripts = soup.find_all("script") for script in scripts: value = script.get_text().strip() file_open_temp = "FCDroid/TaintJS/temp_file_to_analyze.js" file_to_write = open(file_open_temp, "w") file_to_write.write(value) file_to_write.close() cmd_node = [ "node", "--max-old-space-size=4096", "FCDroid/TaintJS/app.js", file_open_temp ] process = subprocess.Popen(cmd_node, stdout=subprocess.PIPE, stderr=subprocess.PIPE) out, err = process.communicate() out = out.decode('utf-8').strip() err = err.decode('utf-8') os.remove(file_open_temp) # no error if err != '': # no error if out == 'true': # is vuln self.page_xss_vuln[file_name] = True else: page_analyze = XSScanner(file_name, file_content) page_analyze.analyze_page() if len(page_analyze.sink) > 0 or len( page_analyze.source) > 0: self.page_xss_vuln[file_name] = page_analyze except Exception: page_analyze = XSScanner(file_name, file_content) page_analyze.analyze_page() if len(page_analyze.sink) > 0 or len(page_analyze.source) > 0: self.page_xss_vuln[file_name] = page_analyze def find_string(self, file_to_search, remote=False, debug=False): """ find string inside file of apk(html,xml,ecc..) (not yet decompiled) """ debug = True # print(self.md5_file_to_url.keys()) if remote: self.logger.logger.info("[START REMOTE FILE ANALYZE]") else: self.logger.logger.info("[START FILE ANALYZE]") for file_to_inspect, insideAPK in file_to_search.items(): if not remote and debug: self.logger.logger.info("File: " + file_to_inspect) else: if debug: try: m = hashlib.md5() m.update(file_to_inspect.encode('utf-8')) self.logger.logger.info( "Remote File in: {0}".format(file_to_inspect)) if m.hexdigest() in self.md5_file_to_url.keys(): self.logger.logger.info("URL: {0}".format( self.md5_file_to_url[str(m.hexdigest())])) except KeyError as e: self.logger.logger.warning( "Key error as {0} ".format(e)) file_to_inspect_split = file_to_inspect.split( "?", 1)[0] # remove parameter if remote and not (file_to_inspect_split.endswith(".js") or file_to_inspect_split.endswith(".html")): # add extension html on file # of default wget add this extension file_to_inspect = file_to_inspect + ".html" if insideAPK: data = self.zip.open(file_to_inspect) else: data = open(file_to_inspect, "r") ####################################################################################################### # start xss analysis on this file try: content_file = data.read() thread = threading.Thread(name="xss_" + file_to_inspect, target=self.analyze_xss_dom, args=( file_to_inspect, str(content_file), )) thread.start() ####################################################################################################### file_read = str(content_file) soup = BeautifulSoup(file_read, 'lxml') try: find_iframe, list_row_string, list_src_iframe, find_string_not_tag, file_with_sandbox = FileAnalysis.find_string( self.string_to_find, self.search_tag, file_to_inspect, file_read, soup, self.logger) self.file_with_sandbox = { **self.file_with_sandbox, **file_with_sandbox } # merge dict ####################################################################################################### # TODO insert in method --> String Analysis if find_iframe and self.string_to_find == "iframe": if not find_string_not_tag: self.dict_file_with_string[ file_to_inspect] = list_row_string self.src_iframe[file_to_inspect] = list_src_iframe # TODO search id iframe in file js in script src if not self.search_tag or file_to_inspect_split.endswith( ".js") or find_string_not_tag: self.file_with_string_iframe.append( file_to_inspect) # append file with iframe print(bcolors.FAIL + "Found " + self.string_to_find + " in line " + str(list_row_string) + bcolors.ENDC) self.logger.logger.info( "Found %s file %s in line %s", self.string_to_find, file_to_inspect, str(list_row_string)) else: print(bcolors.FAIL + "Found tag " + self.string_to_find + ", " + str(len(list_row_string)) + " times " + bcolors.ENDC) self.logger.logger.info( "Found in file %s tag %s , %s times", file_to_inspect, self.string_to_find, str(len(list_row_string))) if len(self.src_iframe[file_to_inspect]) > 0: self.logger.logger.info( "Founded this src {0} in iframe tag inside file {1}" .format( str(self.src_iframe[file_to_inspect]), file_to_inspect)) else: self.logger.logger.info( "No src founded in iframe tag inside file {0}" .format(file_to_inspect)) ####################################################################################################### # TODO aggiungere il content e fare conclusioni su di esso e per i file JavaScript find_csp = soup.find( "meta", {"http-equiv": "Content-Security-Policy"}) if find_csp is not None: print(bcolors.OKGREEN + "Find CSP with content: [" + find_csp["content"] + "]" + bcolors.ENDC) self.logger.logger.info( "Find CSP with content: [" + find_csp["content"] + "]") self.find_csp[file_to_inspect] = True # only file html elif not file_to_inspect_split.endswith(".js"): print(bcolors.FAIL + "No CSP found!" + bcolors.ENDC) self.logger.logger.info("No CSP found!") self.find_csp[file_to_inspect] = False elif file_to_inspect_split.endswith(".js"): print(bcolors.FAIL + "It is a JS file, no CSP found!" + bcolors.ENDC) self.logger.logger.info( "It is a JS file, no CSP found!, investigate manually\n" ) self.find_csp[file_to_inspect] = False else: print(bcolors.OKGREEN + "No " + self.string_to_find + " in " + file_to_inspect + bcolors.ENDC) self.logger.logger.info("No " + self.string_to_find + " in " + file_to_inspect + "\n") except zipfile.BadZipfile as e: self.logger.error("Error bad zip file {0}".format(e)) continue except ValueError as e: self.logger.error("Error value error {0}".format(e)) continue except UnicodeDecodeError as e: self.logger.logger.error("Error unicode error {0}".format(e)) continue self.logger.logger.info("[END ANALYZE FILE]") return None def find_method_used(self): """ funzione per ricercare i metodi che sono usati all'interno dell'apk, tanto lenta """ used_jadx = False if used_jadx: # Create DalvikVMFormat Object self.dalvik_format = DalvikVMFormat(self.apk) # Create Analysis Object self.analysis_object = Analysis(self.dalvik_format) # Load the decompiler # Make sure that the jadx executable is found in $PATH # or use the argument jadx="/path/to/jadx" to point to the executable decompiler = DecompilerJADX(self.dalvik_format, self.analysis_object) # propagate decompiler and analysis back to DalvikVMFormat self.dalvik_format.set_decompiler(decompiler) self.dalvik_format.set_vmanalysis(self.analysis_object) # Now you can do stuff like: list_method_analysis = self.analysis_object.get_methods() for method_analys in list_method_analysis: method_name = method_analys.get_method().get_name() # print(method_encoded.get_method().get_source()) self.method[method_name] = list(method_analys.get_xref_from()) elif self.use_analyze: # return apk, list dex , object analysis apk, self.dalvik_format, self.analysis_object = AnalyzeAPK( self.name_apk) for method_analys in self.analysis_object.get_methods(): method_name = method_analys.get_method().get_name() # from method_name get list dove esso viene chiamato self.method[method_name] = list(method_analys.get_xref_from()) elif self.use_smaliparser: # use smali parser, apktool and grep invece di Androguard dir_apk_tool = "temp_dir_" + self.name_only_apk + "/" list_method_to_analyze = self.conf["method_smali_parser"] self.method_2_value, self.all_url = smaliparser.start( dir_apk_tool, list_method_to_analyze) else: # TODO to make faster analysis but not work well self.dalvik_format = DalvikVMFormat(self.apk) for encoded_method in self.dalvik_format.get_methods(): method_analysis = MethodClassAnalysis(encoded_method) method_name = method_analysis.get_method().get_name() # print(method_name) # from method_name get list dove esso viene chiamato self.method[method_name] = list( method_analysis.get_xref_from()) # print(self.method[method_name]) def check_method_conf(self): """ function to check se methods inside conf.json method_to_check is used inside apk """ method_to_find = self.conf["method_to_check"] method_present = dict() try: if self.use_smaliparser: if "setJavaScriptEnabled" in self.method_2_value.keys(): if "0x1" in self.method_2_value["setJavaScriptEnabled"]: self.javascript_enabled = True method_present["setJavaScriptEnabled"] = True if "addJavascriptInterface" in self.method_2_value.keys(): self.javascript_interface = True method_present["addJavascriptInterface"] = True else: for mf in method_to_find: method_present[mf] = False for mapk in self.method.keys(): if mf in mapk: method_present[mf] = True if method_present["setJavaScriptEnabled"]: for value in self.method["setJavaScriptEnabled"]: try: if value[1] is not None: encoded_method = value[1] source_code = FileAnalysis.get_list_source_code( encoded_method) if FileAnalysis.check_method_used_value( source_code, "setJavaScriptEnabled", "1"): # volendo si possono memorizzare tutti i file che lo settano atrue self.javascript_enabled = True break except (TypeError, AttributeError, KeyError) as e: self.logger.logger.error( "Exception during check method used {0}". format(e)) continue print() if self.dynamic_javascript_enabled: self.logger.logger.info( "[JavaScript enabled (check dynamically) :" + str(self.dynamic_javascript_enabled) + "]") else: self.logger.logger.info( "[JavaScript enabled (check static): " + str(self.javascript_enabled) + "]") except Exception as e: self.logger.logger.error( "File conf.json without method setJavaScriptEnabled {0}". format(e)) try: if not self.use_smaliparser: if self.dynamic_javascript_interface: self.logger.logger.info( "[Add interface WebView (check dynamically): " + str(self.dynamic_javascript_interface) + "]") self.javascript_interface = self.dynamic_javascript_interface else: self.logger.logger.info( "[Add interface WebView (check static): " + str(method_present["addJavascriptInterface"]) + "]") self.javascript_interface = method_present[ "addJavascriptInterface"] else: if self.dynamic_javascript_interface: method_present[ "addJavascriptInterface"] = self.dynamic_javascript_interface self.logger.logger.info( "[Add interface WebView (check dynamically): " + str(self.dynamic_javascript_interface) + "]") else: method_present[ "addJavascriptInterface"] = self.javascript_interface self.logger.logger.info( "[Add interface WebView (check static): " + str(self.javascript_interface) + "]") except Exception as e: # nothing self.logger.logger.error( "File conf.json without method addJavascriptInterface {0}\n". format(e)) self.is_contains_all_methods = len(method_present) == len( method_to_find) return self.is_contains_all_methods def find_url_in_apk(self): """ find all url/uri inside apk """ # add url using dynamic analysis if self.api_monitor_dict is not None and self.network_dict is not None: self.add_url_dynamic() ############################################################################## # use smali_parser if self.use_smaliparser: # add url loaded for smali_parsr if "loadUrl" in self.method_2_value.keys(): all_url_loaded = self.method_2_value["loadUrl"] # da queste devo filtrare ottenendo solo quelle http/https temp_url_loaded = list( filter( lambda x: x is not None and (x.startswith("http") or x.startswith("https")), all_url_loaded)) self.url_loaded = list(set().union(self.url_loaded, temp_url_loaded)) else: # ALL string inside apk # use AndroGuard # url regularp expression # url_re = "(http:\/\/|https:\/\/|file:\/\/\/)?[-a-zA-Z0-9@:%._\+~#=]\.[a-z]([-a-zA-Z0-9@:%_\+.~#?&//=]*)" url_re = "^(http:\/\/|https:\/\/)\w+" list_string_analysis = list() # list of string analysis object # se uso aalysis object if self.analysis_object is not None: list_string_analysis = self.analysis_object.find_strings( url_re) # --> gen object else: list_string = self.dalvik_format.get_regex_strings(url_re) # get all string inside apk for string_value in list_string: list_string_analysis.append(StringAnalysis(string_value)) ################################################################################## temp_string_value = list() # string- tuple with classAnalysis e encodeMethod that use the string dict_class_method_analysis = dict() for string_analysis in list_string_analysis: temp_string_value.append( string_analysis.get_value()) # tutte le url dict_class_method_analysis[string_analysis.get_value()] = list( string_analysis.get_xref_from() ) # url e relativo codice dove le ho trovate ################################################################################## # per ogni file, otteniamo una lista di tupla # class analysis e encoded_method for key in dict_class_method_analysis.keys(): for value in dict_class_method_analysis[key]: # class_analysis = value[0] try: if value[1] is not None: encoded_method = value[1] # split the instruction in a list source_code = FileAnalysis.get_list_source_code( encoded_method) if source_code is not None: self.all_url.append(key) # appendo l'url if FileAnalysis.check_method_used_value( source_code, "loadUrl", key): self.url_loaded.append( key ) # appendo url se caricata dentro loadUrl except (TypeError, AttributeError, KeyError) as e: self.logger.logger.error( "Exception during find url in apk {0}".format(e)) continue ####################################################################################################### # debug part if len(self.url_loaded) > 0: # print(self.url_loaded) self.logger.logger.info( "[START URL LOADED INSIDE LOADURL FUNCTION]") self.url_loaded = list(set(self.url_loaded)) for u in self.url_loaded: if u.startswith("http://"): self.http_connection_static.append(u) self.logger.logger.info( "Url inside load function: {0}".format(u)) self.logger.logger.info("[END URL LOADED INSIDE LOADURL FUNCTION]") md5_file_to_url, file_download_to_analyze = utility.download_page_with_wget( self.name_only_apk, self.url_loaded) for key in md5_file_to_url.keys(): if key not in self.md5_file_to_url.keys(): self.md5_file_to_url[key] = md5_file_to_url[key] for key in file_download_to_analyze.keys(): if key not in self.file_download_to_analyze.keys(): self.file_download_to_analyze[ key] = file_download_to_analyze[key] # self.download_page_loaded_with_wget() self.find_string(self.file_download_to_analyze, remote=True) if len(self.all_url) > 0: self.all_url = list(set(self.all_url)) self.logger.logger.info("[START ALL URL INSIDE APK]") for u in self.all_url: if u.startswith("http://"): self.all_http_connection.append(u) self.logger.logger.info("Url inside apk {0}".format(u)) self.logger.logger.info("[END ALL URL INSIDE APK]") html_dir = "temp_html_code/html_downloaded_{0}/".format( self.name_only_apk) # TODO eliminare save_page_html = False if os.path.exists(html_dir) and len( os.listdir(html_dir)) > 0 and save_page_html: # zip -r squash.zip dir1 subprocess.call([ "zip", "-r", "temp_html_code/html_{0}.zip".format( self.name_only_apk), html_dir ], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) # delete dir o provare a zip subprocess.Popen(["rm", "-rf", html_dir], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) # check vulnerability def vulnerable_frame_confusion(self): """ check if app is vulnerable on frame confusion 1) iframe nella stringa di ricerca 2) metodi addJavascriptInterface e setJavaScriptEnabled usati 3) permesso internet 4) almeno un file html con l'iframe all'interno e senza csp """ # se esiste almeno un file con iframe senza csp --> vulnerble # se è false --> vulnerabile csp_in_file_iframe = True app_use_sandbox = True # print("File in dict_file_with_string: {}".format(self.dict_file_with_string.keys())) # print("File in find_csp: {}".format(self.find_csp.keys())) # print("File in file_with_sandbox: {}".format(self.file_with_sandbox.keys())) for file_with_iframe in self.dict_file_with_string.keys(): csp_in_file_iframe = csp_in_file_iframe and self.find_csp[ file_with_iframe] app_use_sandbox = app_use_sandbox and self.file_with_sandbox[ file_with_iframe] if not self.find_csp[ file_with_iframe] or not self.file_with_sandbox[ file_with_iframe]: self.file_vulnerable_frame_confusion.append(file_with_iframe) # print("sandbox in app {}".format(self.app_use_sandbox)) # se vero whitelist implementato male white_list_bug = len( self.list_origin_access) == 0 or "*" in self.list_origin_access self.is_vulnerable_frame_confusion = ( "iframe" in self.string_to_find and self.check_method_conf() and (len(self.dict_file_with_string) > 0 or len(self.file_with_string_iframe) > 0) and self.is_contain_permission and not csp_in_file_iframe and white_list_bug and not self.app_use_sandbox) def add_url_dynamic(self): """ function that aggiunge le url caricate diamicamente attraverso che sono state trovate precendetemente dall'analisi dinamica """ ####################################################################################################### function_load_url = ["loadUrl"] # funzioni che caricano url in Android url_api_monitor = list() for keys in self.api_monitor_dict.keys(): if keys in function_load_url: url_api_monitor = list(set().union( url_api_monitor, self.api_monitor_dict[keys]["args"])) # dynamic interface and javascript enabled if keys == "addJavascriptInterface": self.dynamic_javascript_interface = True # TODO check --> considero javascriptenabled se ho solo l'interface abilitata if keys == "setJavaScriptEnabled" and True in self.api_monitor_dict[ keys]["args"]: self.dynamic_javascript_enabled = True # get all http/https/file in load function self.url_dynamic = filter( lambda x: x.startswith("http://") or x.startswith("https://") or x. startswith("file://"), url_api_monitor) self.load_url_dynamic = self.url_dynamic ####################################################################################################### # TODO mettere la funzione evaluateJavaScript o loadUrl javascript: --> come se fosse un file javascript javascript_load_url = filter(lambda x: x.startswith("javascript:"), url_api_monitor) # method that exec js in recent api javascript_evaluate = list() method_evaluate_js = ["evaluateJavascript"] for keys in self.api_monitor_dict.keys(): if keys in method_evaluate_js: javascript_evaluate = list(set().union( javascript_evaluate, self.api_monitor_dict[keys]["args"])) # now write this code in a file and analyze them javascript_code_exec = list(set().union(javascript_load_url, javascript_evaluate)) name_file = "code_js_loaded_" i = 1 list_file_js_dynamic = dict() dir_write = os.path.join("temp_html_code", "html_downloaded_" + self.name_only_apk) if not os.path.isdir(dir_write): os.makedirs(dir_write) for code in javascript_code_exec: file_js = os.path.join(dir_write, name_file + "{0}.js".format(i)) file = open(file_js, "w") file.write(code) file.close() list_file_js_dynamic[file_js] = False self.javascript_file[file_js] = False self.logger.logger.info("[Start javascript code dynamic]") self.find_string(list_file_js_dynamic) self.logger.logger.info("[End javascript code dynamic]\n") ####################################################################################################### # TODO mettere metodi cordova ####################################################################################################### # ora devo filtrare solo le url che sono http/https url_network = list() for keys in self.network_dict.keys(): # TODO check url_list_new = list() for url in self.network_dict[keys]["url"]: # search ip ip = re.findall(r"[0-9]+(?:\.[0-9]+){3}", url) if ip != None and len(ip) > 0: # change ip with host # get only first element of every list --> every list are max 1 element url_new = url.replace(ip[0], self.network_dict[keys]["host"][0]) url_list_new.append(url_new) else: url_list_new.append(url) # add new url self.network_dict[keys]["url"] = url_list_new url_network = list(set().union(url_network, self.network_dict[keys]["url"])) ########################################################################################################## # remove url google # url effettivamente caricate nell'applicazione self.url_dynamic = list(set().union(self.url_dynamic, url_network)) self.all_url_dynamic = self.url_dynamic url_dynamic_to_remove = list() for url_dyn in self.url_dynamic: for url_to_check in self.conf["url_to_remove"]: if url_to_check in url_dyn: url_dynamic_to_remove.append(url_dyn) # TODO maybe to add url_dynamic_to_remove = list(set(url_dynamic_to_remove)) for url_to_remove in url_dynamic_to_remove: self.url_dynamic.remove(url_to_remove) ####################################################################################################### self.url_loaded = list(set().union(self.url_loaded, self.url_dynamic)) self.all_url = list(set().union(self.all_url, self.url_loaded)) self.logger.logger.info("[Init add url dynamic ]") for u in self.url_dynamic: if u.startswith("http://"): self.http_connection.append(u) if u in self.load_url_dynamic: self.logger.logger.info( "Url dynamic inside loadUrl{0}".format(u)) else: self.logger.logger.info("Url dynamic {0}".format(u)) self.logger.logger.info("[End url dynamic]\n")