예제 #1
0
    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 testAdaptiveIcon(self):
        # See https://developer.android.com/guide/practices/ui_guidelines/icon_design_adaptive.html
        from androguard.core.bytecodes.apk import APK
        from androguard.core.bytecodes.axml import AXMLPrinter

        a = APK("examples/tests/com.android.example.text.styling.apk")

        self.assertEqual(a.get_app_icon(), "res/mipmap-anydpi-v26/ic_launcher.xml")
        x = AXMLPrinter(a.get_file(a.get_app_icon())).get_xml().decode("UTF-8")
        self.assertIn("adaptive-icon", x)

        # * ldpi (low) ~120dpi
        # * mdpi (medium) ~160dpi
        # * hdpi (high) ~240dpi
        # * xhdpi (extra-high) ~320dpi
        # * xxhdpi (extra-extra-high) ~480dpi
        # * xxxhdpi (extra-extra-extra-high) ~640dpi
        self.assertIsNone(a.get_app_icon(max_dpi=120))  # No LDPI icon
        self.assertIn("mdpi", a.get_app_icon(max_dpi=160))
        self.assertIn("hdpi", a.get_app_icon(max_dpi=240))
        self.assertIn("xhdpi", a.get_app_icon(max_dpi=320))
        self.assertIn("xxhdpi", a.get_app_icon(max_dpi=480))
        self.assertIn("xxxhdpi", a.get_app_icon(max_dpi=640))

        self.assertIn(".png", a.get_app_icon(max_dpi=65533))
        self.assertIn(".xml", a.get_app_icon(max_dpi=65534))
예제 #3
0
    def testAdaptiveIcon(self):
        # See https://developer.android.com/guide/practices/ui_guidelines/icon_design_adaptive.html
        from androguard.core.bytecodes.apk import APK
        from androguard.core.bytecodes.axml import AXMLPrinter

        a = APK("examples/tests/com.android.example.text.styling.apk")

        self.assertEqual(a.get_app_icon(), "res/mipmap-anydpi-v26/ic_launcher.xml")
        x = AXMLPrinter(a.get_file(a.get_app_icon())).get_xml().decode("UTF-8")
        self.assertIn("adaptive-icon", x)

        # * ldpi (low) ~120dpi
        # * mdpi (medium) ~160dpi
        # * hdpi (high) ~240dpi
        # * xhdpi (extra-high) ~320dpi
        # * xxhdpi (extra-extra-high) ~480dpi
        # * xxxhdpi (extra-extra-extra-high) ~640dpi
        self.assertIsNone(a.get_app_icon(max_dpi=120))  # No LDPI icon
        self.assertIn("mdpi", a.get_app_icon(max_dpi=160))
        self.assertIn("hdpi", a.get_app_icon(max_dpi=240))
        self.assertIn("xhdpi", a.get_app_icon(max_dpi=320))
        self.assertIn("xxhdpi", a.get_app_icon(max_dpi=480))
        self.assertIn("xxxhdpi", a.get_app_icon(max_dpi=640))

        self.assertIn(".png", a.get_app_icon(max_dpi=65533))
        self.assertIn(".xml", a.get_app_icon(max_dpi=65534))
예제 #4
0
    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)
        }
예제 #5
0
    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())
예제 #6
0
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
예제 #7
0
    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
예제 #8
0
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')
예제 #9
0
def analyze_apk(task, scan_id):
    # Start the APK analysis
    global APK_PATH
    global DECOMPILE_PATH
    try:
        scan = Scan.objects.get(pk=scan_id)
        APK_PATH = settings.BASE_DIR + scan.apk.url
        DECOMPILE_PATH = os.path.splitext(APK_PATH)[0]
        scan.status = 'In Progress'
        scan.progress = 3
        scan.save()
        task.update_state(state = 'STARTED',
                meta = {'current': scan.progress, 'total': 100, 'status': scan.status})
        logger.debug(scan.status)
        a = APK(APK_PATH)
        scan = set_hash_app(scan)
        scan.status = 'Getting info of apk'
        scan.progress = 5
        scan.save()
        task.update_state(state = 'STARTED',
                meta = {'current': scan.progress, 'total': 100, 'status': scan.status})
        logger.debug(scan.status)
        scan = get_info_apk(a, scan)
        scan.status = 'Getting info of certificates'
        scan.progress = 10
        scan.save()
        task.update_state(state = 'STARTED',
                meta = {'current': scan.progress, 'total': 100, 'status': scan.status})
        logger.debug(scan.status)
        certificates = get_info_certificate(a, scan)
        if (settings.VIRUSTOTAL_ENABLED):
            scan.status = 'Getting info of VT'
            scan.progress = 15
            scan.save()
            task.update_state(state = 'STARTED',
                meta = {'current': scan.progress, 'total': 100, 'status': scan.status})
            logger.debug(scan.status)
            report = get_report_virus_total(scan, scan.sha256)
            if (not report and settings.VIRUSTOTAL_UPLOAD):
                scan.status = 'Upload to VT'
                scan.save()
                upload_virus_total(scan, APK_PATH, scan.sha256)
        scan.status = 'Decompiling'
        scan.progress = 20
        scan.save()
        task.update_state(state = 'STARTED',
                meta = {'current': scan.progress, 'total': 100, 'status': scan.status})
        logger.debug(scan.status)
        decompile_jadx()
        if (a.get_app_icon()):
            update_icon(scan, DECOMPILE_PATH + '/resources/' + a.get_app_icon())
        scan.status = 'Finding vulnerabilities'
        scan.progress = 40
        task.update_state(state = 'STARTED',
                meta = {'current': scan.progress, 'total': 100, 'status': scan.status})
        scan.save()
        logger.debug(scan.status)
        findings = get_tree_dir(scan)
        scan.status = 'Finished'
        scan.progress = 100
        scan.finished_on = datetime.now()
        scan.save()
        task.update_state(state = 'STARTED',
                meta = {'current': scan.progress, 'total': 100, 'status': scan.status})
        logger.debug(scan.status)
    except Exception as e:
        scan.progress = 100
        scan.status = "Error"
        scan.finished_on = datetime.now()
        scan.save()
        task.update_state(state = 'STARTED',
                meta = {'current': scan.progress, 'total': 100, 'status': scan.status})
        logger.error(e)
예제 #10
0
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))