Example #1
0
def generate_download(apk_dir, md5_hash, download_dir, package):
    """Generating Downloads."""
    logger.info('Generating Downloads')
    try:
        httptools = os.path.join(str(Path.home()), '.httptools')
        logcat = os.path.join(apk_dir, 'logcat.txt')
        xlogcat = os.path.join(apk_dir, 'x_logcat.txt')
        apimon = os.path.join(apk_dir, 'kensa_api_monitor.txt')
        fd_logs = os.path.join(apk_dir, 'kensa_frida_out.txt')
        dumpsys = os.path.join(apk_dir, 'dump.txt')
        sshot = os.path.join(apk_dir, 'screenshots-apk/')
        web = os.path.join(httptools, 'flows', package + '.flow.txt')
        star = os.path.join(apk_dir, package + '.tar')

        dlogcat = os.path.join(download_dir, md5_hash + '-logcat.txt')
        dxlogcat = os.path.join(download_dir, md5_hash + '-x_logcat.txt')
        dapimon = os.path.join(download_dir, md5_hash + '-api_monitor.txt')
        dfd_logs = os.path.join(download_dir, md5_hash + '-frida_out.txt')
        ddumpsys = os.path.join(download_dir, md5_hash + '-dump.txt')
        dsshot = os.path.join(download_dir, md5_hash + '-screenshots-apk/')
        dweb = os.path.join(download_dir, md5_hash + '-web_traffic.txt')
        dstar = os.path.join(download_dir, md5_hash + '-app_data.tar')

        # Delete existing data
        dellist = [
            dlogcat, dxlogcat, dapimon, dfd_logs, ddumpsys, dsshot, dweb, dstar
        ]
        for item in dellist:
            if os.path.isdir(item):
                shutil.rmtree(item)
            elif os.path.isfile(item):
                os.remove(item)
        # Copy new data
        shutil.copyfile(logcat, dlogcat)
        shutil.copyfile(dumpsys, ddumpsys)
        if is_file_exists(xlogcat):
            shutil.copyfile(xlogcat, dxlogcat)
        if is_file_exists(apimon):
            shutil.copyfile(apimon, dapimon)
        if is_file_exists(fd_logs):
            shutil.copyfile(fd_logs, dfd_logs)
        try:
            shutil.copytree(sshot, dsshot)
        except Exception:
            pass
        if is_file_exists(web):
            shutil.copyfile(web, dweb)
        if is_file_exists(star):
            shutil.copyfile(star, dstar)
    except Exception:
        logger.exception('Generating Downloads')
Example #2
0
def view_report(request):
    """Dynamic Analysis Report Generation."""
    logger.info('Dynamic Analysis Report Generation')
    try:
        md5_hash = request.GET['hash']
        package = request.GET['package']
        droidmon = {}
        apimon = {}
        if (is_attack_pattern(package) or not is_md5(md5_hash)):
            return print_n_send_error_response(request, 'Invalid Parameters')
        app_dir = os.path.join(settings.UPLD_DIR, md5_hash + '/')
        download_dir = settings.DWD_DIR
        if not is_file_exists(os.path.join(app_dir, 'logcat.txt')):
            msg = ('Dynamic Analysis report is not available '
                   'for this app. Perform Dynamic Analysis '
                   'and generate the report.')
            return print_n_send_error_response(request, msg)
        fd_log = os.path.join(app_dir, 'kensa_frida_out.txt')
        droidmon = droidmon_api_analysis(app_dir, package)
        apimon = apimon_analysis(app_dir)
        analysis_result = run_analysis(app_dir, md5_hash, package)
        generate_download(app_dir, md5_hash, download_dir, package)
        images = get_screenshots(md5_hash, download_dir)
        context = {
            'md5': md5_hash,
            'emails': analysis_result['emails'],
            'urls': analysis_result['urls'],
            'domains': analysis_result['domains'],
            'clipboard': analysis_result['clipboard'],
            'xml': analysis_result['xml'],
            'sqlite': analysis_result['sqlite'],
            'others': analysis_result['other_files'],
            'screenshots': images['screenshots'],
            'acttest': images['activities'],
            'expacttest': images['exported_activities'],
            'droidmon': droidmon,
            'apimon': apimon,
            'fdlog': is_file_exists(fd_log),
            'package': package,
            'version': settings.KENSA_VER,
            'title': 'Dynamic Analysis'
        }
        template = 'dynamic_analysis/android/dynamic_report.html'
        return render(request, template, context)
    except Exception as exp:
        logger.exception('Dynamic Analysis Report Generation')
        err = 'Error Geneating Dynamic Analysis Report. ' + str(exp)
        return print_n_send_error_response(request, err)
    def get_embedded_classes(self):
        """
        Get the list of Java classes from all DEX files.

        :return: list of Java classes
        """
        if self.classes is not None:
            return self.classes
        for dex_file in glob.iglob(os.path.join(self.apk_dir, '*.dex')):
            if (len(settings.BACKSMALI_BINARY) > 0
                    and is_file_exists(settings.BACKSMALI_BINARY)):
                bs_path = settings.BACKSMALI_BINARY
            else:
                bs_path = os.path.join(self.tools_dir, 'baksmali-2.4.0.jar')
            args = [
                settings.JAVA_BINARY, '-jar', bs_path, 'list', 'classes',
                dex_file
            ]
            classes = subprocess.check_output(
                args, universal_newlines=True).splitlines()
            if self.classes is not None:
                self.classes = self.classes + classes
            else:
                self.classes = classes
        return self.classes
Example #4
0
def dex_2_smali(app_dir, tools_dir):
    """Run dex2smali."""
    try:
        logger.info('DEX -> SMALI')
        dexes = get_dex_files(app_dir)
        for dex_path in dexes:
            logger.info('Converting %s to Smali Code',
                        filename_from_path(dex_path))
            if (len(settings.BACKSMALI_BINARY) > 0 and
                    is_file_exists(settings.BACKSMALI_BINARY)):
                bs_path = settings.BACKSMALI_BINARY
            else:
                bs_path = os.path.join(tools_dir, 'baksmali-2.4.0.jar')
            output = os.path.join(app_dir, 'smali_source/')
            smali = [
                settings.JAVA_BINARY,
                '-jar',
                bs_path,
                'd',
                dex_path,
                '-o',
                output,
            ]
            trd = threading.Thread(target=subprocess.call, args=(smali,))
            trd.daemon = True
            trd.start()
    except Exception:
        logger.exception('Converting DEX to SMALI')
Example #5
0
def apimon_analysis(app_dir):
    """API Analysis."""
    api_details = {}
    try:
        location = os.path.join(app_dir, 'kensa_api_monitor.txt')
        if not is_file_exists(location):
            return {}
        logger.info('Frida API Monitor Analysis')
        with open(location, 'r') as flip:
            apis = json.loads('[{}]'.format(
                flip.read()[:-1]))
        for api in apis:
            to_decode = None
            if (api['class'] == 'android.util.Base64'and
                    (api['method'] == 'encodeToString')):
                to_decode = api['returnValue'].replace('"', '')
            elif (api['class'] == 'android.util.Base64' and
                  api['method'] == 'decode'):
                to_decode = api['arguments'][0]
            try:
                if to_decode:
                    api['decoded'] = decode_base64(to_decode)
            except Exception:
                pass
            api['icon'] = get_icon_map(api['name'])
            if api['name'] in api_details:
                api_details[api['name']].append(api)
            else:
                api_details[api['name']] = [api]
    except Exception:
        logger.exception('API Monitor Analysis')
    return api_details
Example #6
0
def frida_logs(request):
    try:
        apphash = request.GET.get('hash', '')
        stream = request.GET.get('stream', '')
        if not is_md5(apphash):
            return invalid_params()
        if stream:
            apk_dir = os.path.join(settings.UPLD_DIR, apphash + '/')
            frida_logs = os.path.join(apk_dir, 'kensa_frida_out.txt')
            data = {}
            if is_file_exists(frida_logs):
                with open(frida_logs, 'r') as flip:
                    data = {'data': flip.read()}
                return json_response(data)
        logger.info('Frida Logs live streaming')
        template = 'dynamic_analysis/android/frida_logs.html'
        return render(request,
                      template,
                      {'hash': apphash,
                       'package': request.GET.get('package', ''),
                       'version': settings.KENSA_VER,
                       'title': 'Live Frida logs'})
    except Exception:
        logger.exception('Frida log streaming')
        err = 'Error in Frida log streaming'
        return print_n_send_error_response(request, err)
Example #7
0
def live_api(request):
    try:
        apphash = request.GET.get('hash', '')
        stream = request.GET.get('stream', '')
        if not is_md5(apphash):
            return invalid_params()
        if stream:
            apk_dir = os.path.join(settings.UPLD_DIR, apphash + '/')
            apimon_file = os.path.join(apk_dir, 'kensa_api_monitor.txt')
            data = {}
            if is_file_exists(apimon_file):
                with open(apimon_file, 'r') as flip:
                    api_list = json.loads('[{}]'.format(
                        flip.read()[:-1]))
                data = {'data': api_list}
                return json_response(data)
        logger.info('Starting API monitor streaming')
        template = 'dynamic_analysis/android/live_api.html'
        return render(request,
                      template,
                      {'hash': apphash,
                       'package': request.GET.get('package', ''),
                       'version': settings.KENSA_VER,
                       'title': 'Live API Monitor'})
    except Exception:
        logger.exception('API monitor streaming')
        err = 'Error in API monitor streaming'
        return print_n_send_error_response(request, err)
def binary_analysis(src, tools_dir, app_dir, executable_name):
    """Binary Analysis of IPA."""
    try:
        binary_analysis_dict = {}
        binary_findings = {}
        logger.info('Starting Binary Analysis')
        dirs = os.listdir(src)
        dot_app_dir = ''
        for dir_ in dirs:
            if dir_.endswith('.app'):
                dot_app_dir = dir_
                break
        # Bin Dir - Dir/Payload/x.app/
        bin_dir = os.path.join(src, dot_app_dir)
        if (executable_name
                and is_file_exists(os.path.join(bin_dir, executable_name))):
            bin_name = executable_name
        else:
            bin_name = dot_app_dir.replace('.app', '')
        # Bin Path - Dir/Payload/x.app/x
        bin_path = os.path.join(bin_dir, bin_name)
        binary_analysis_dict['libs'] = []
        binary_analysis_dict['bin_res'] = {}
        binary_analysis_dict['strings'] = []
        if not is_file_exists(bin_path):
            logger.warning('Kensa Cannot find binary in %s', bin_path)
            logger.warning('Skipping Otool, Classdump and Strings')
        else:
            bin_info = get_bin_info(bin_path)
            object_data = otool_analysis(tools_dir, bin_name, bin_path,
                                         bin_dir)
            bin_type = detect_bin_type(object_data['libs'])
            cdump = get_class_dump(tools_dir, bin_path, app_dir, bin_type)
            binary_rule_matcher(binary_findings,
                                object_data['bindata'] + cdump,
                                ipa_rules.IPA_RULES)
            strings_in_ipa = strings_on_ipa(bin_path)
            binary_analysis_dict['libs'] = object_data['libs']
            binary_analysis_dict['bin_res'] = binary_findings
            binary_analysis_dict['strings'] = strings_in_ipa
            binary_analysis_dict['macho'] = bin_info
            binary_analysis_dict['bin_type'] = bin_type
        return binary_analysis_dict
    except Exception:
        logger.exception('iOS Binary Analysis')
Example #9
0
def get_app_files(apk_dir, md5_hash, package):
    """Get files from device."""
    logger.info('Getting app files')
    all_files = {'xml': [], 'sqlite': [], 'others': []}
    # Extract Device Data
    tar_loc = os.path.join(apk_dir, package + '.tar')
    untar_dir = os.path.join(apk_dir, 'DYNAMIC_DeviceData/')
    if not is_file_exists(tar_loc):
        return all_files
    if os.path.exists(untar_dir):
        # fix for permission errors
        shutil.rmtree(untar_dir)
    try:
        with tarfile.open(tar_loc, errorlevel=1) as tar:
            tar.extractall(untar_dir)
    except FileExistsError:
        pass
    except Exception:
        logger.exception('Tar extraction failed')
    # Do Static Analysis on Data from Device
    try:
        if not os.path.exists(untar_dir):
            os.makedirs(untar_dir)
        for dir_name, _, files in os.walk(untar_dir):
            for jfile in files:
                file_path = os.path.join(untar_dir, dir_name, jfile)
                fileparam = file_path.replace(untar_dir, '')
                if is_pipe_or_link(file_path):
                    continue
                if jfile == 'lib':
                    pass
                else:
                    if jfile.endswith('.xml'):
                        all_files['xml'].append({
                            'type': 'xml',
                            'file': fileparam
                        })
                    else:
                        with open(file_path, 'r',
                                  encoding='ISO-8859-1') as flip:
                            file_cnt_sig = flip.read(6)
                        if file_cnt_sig == 'SQLite':
                            all_files['sqlite'].append({
                                'type': 'db',
                                'file': fileparam
                            })
                        elif not jfile.endswith('.DS_Store'):
                            all_files['others'].append({
                                'type': 'others',
                                'file': fileparam
                            })
    except Exception:
        logger.exception('Getting app files')
    return all_files
Example #10
0
def droidmon_api_analysis(app_dir, package):
    """API Analysis."""
    try:
        dat = ''
        hooked_apis = get_hooked_apis()
        api_details = {}
        hooks = []
        location = os.path.join(app_dir, 'x_logcat.txt')
        if not is_file_exists(location):
            return {}
        logger.info('Xposed Droidmon API Analysis')
        with open(location, 'r', encoding='utf-8') as flip:
            dat = flip.readlines()
        res_id = 'Droidmon-apimonitor-' + package + ':'
        for line in dat:
            if res_id not in line:
                continue
            _, value = line.split(res_id, 1)
            try:
                apis = json.loads(value, strict=False)
                call_data = {}
                call_data['class'] = apis['class']
                call_data['method'] = apis['method']
                if apis.get('return'):
                    call_data['return'] = apis['return']
                if apis.get('args'):
                    call_data['args'] = apis['args']
                for api, details in hooked_apis.items():
                    if re.findall(details['regex'], apis['class']):
                        call_data['api'] = api
                        # Decode Base64
                        if ('decode' in apis['method']
                                and api == 'api_base64'):
                            call_data['decoded'] = base64_decode(
                                call_data['args'])
                        hooks.append(call_data)
            except Exception:
                pass
        for hook in hooks:
            iden = hook['api']
            api_details[iden] = {
                'name': hooked_apis[iden]['name'],
                'icon': hooked_apis[iden]['icon'],
                'calls': [],
            }
        for hook in hooks:
            iden = hook['api']
            if hook not in api_details[iden]['calls']:
                api_details[iden]['calls'].append(hook)
    except Exception:
        logger.exception('Droidmon API Analysis')
    return api_details
Example #11
0
def get_log_data(apk_dir, package):
    """Get Data for analysis."""
    logcat_data = []
    droidmon_data = ''
    apimon_data = ''
    frida_logs = ''
    web_data = ''
    traffic = ''
    httptools = os.path.join(str(Path.home()), '.httptools')
    web = os.path.join(httptools, 'flows', package + '.flow.txt')
    logcat = os.path.join(apk_dir, 'logcat.txt')
    xlogcat = os.path.join(apk_dir, 'x_logcat.txt')
    apimon = os.path.join(apk_dir, 'kensa_api_monitor.txt')
    fd_logs = os.path.join(apk_dir, 'kensa_frida_out.txt')
    if is_file_exists(web):
        with io.open(web, mode='r', encoding='utf8', errors='ignore') as flip:
            web_data = flip.read()
    if is_file_exists(logcat):
        with io.open(logcat, mode='r', encoding='utf8',
                     errors='ignore') as flip:
            logcat_data = flip.readlines()
            traffic = ''.join(logcat_data)
    if is_file_exists(xlogcat):
        with io.open(xlogcat, mode='r', encoding='utf8',
                     errors='ignore') as flip:
            droidmon_data = flip.read()
    if is_file_exists(apimon):
        with io.open(apimon, mode='r', encoding='utf8',
                     errors='ignore') as flip:
            apimon_data = flip.read()
    if is_file_exists(fd_logs):
        with io.open(fd_logs, mode='r', encoding='utf8',
                     errors='ignore') as flip:
            frida_logs = flip.read()
    traffic = (web_data + traffic + droidmon_data + apimon_data + frida_logs)
    return {'logcat': logcat_data, 'traffic': traffic}
def classdump_mac(clsdmp_bin, tools_dir, ipa_bin):
    """Run Classdump for Objective-C/Swift."""
    if clsdmp_bin == 'class-dump-swift':
        logger.info('Running class-dump-swift against binary')
        external = settings.CLASSDUMP_SWIFT_BINARY
    else:
        logger.info('Running class-dump against binary')
        external = settings.CLASSDUMP_BINARY
    if (len(external) > 0 and is_file_exists(external)):
        class_dump_bin = external
    else:
        class_dump_bin = os.path.join(tools_dir, clsdmp_bin)
    # Execute permission check
    if not os.access(class_dump_bin, os.X_OK):
        os.chmod(class_dump_bin, stat.S_IEXEC)
    return subprocess.check_output([class_dump_bin, ipa_bin])
Example #13
0
def delete_scan(request, api=False):
    """Delete Scan from DB and remove the scan related files."""
    try:
        if request.method == 'POST':
            if api:
                md5_hash = request.POST['hash']
            else:
                md5_hash = request.POST['md5']
            data = {'deleted': 'scan hash not found'}
            if re.match('[0-9a-f]{32}', md5_hash):
                # Delete DB Entries
                scan = RecentScansDB.objects.filter(MD5=md5_hash)
                if scan.exists():
                    RecentScansDB.objects.filter(MD5=md5_hash).delete()
                    StaticAnalyzerAndroid.objects.filter(MD5=md5_hash).delete()
                    StaticAnalyzerIOS.objects.filter(MD5=md5_hash).delete()
                    StaticAnalyzerWindows.objects.filter(MD5=md5_hash).delete()
                    # Delete Upload Dir Contents
                    app_upload_dir = os.path.join(settings.UPLD_DIR, md5_hash)
                    if is_dir_exists(app_upload_dir):
                        shutil.rmtree(app_upload_dir)
                    # Delete Download Dir Contents
                    dw_dir = settings.DWD_DIR
                    for item in os.listdir(dw_dir):
                        item_path = os.path.join(dw_dir, item)
                        valid_item = item.startswith(md5_hash + '-')
                        # Delete all related files
                        if is_file_exists(item_path) and valid_item:
                            os.remove(item_path)
                        # Delete related directories
                        if is_dir_exists(item_path) and valid_item:
                            shutil.rmtree(item_path)
                    data = {'deleted': 'yes'}
            if api:
                return data
            else:
                ctype = 'application/json; charset=utf-8'
                return HttpResponse(json.dumps(data), content_type=ctype)
    except Exception as exp:
        msg = str(exp)
        exp_doc = exp.__doc__
        if api:
            return print_n_send_error_response(request, msg, True, exp_doc)
        else:
            return print_n_send_error_response(request, msg, False, exp_doc)
Example #14
0
def get_script(request):
    """Get frida scripts from others."""
    data = {'status': 'ok', 'content': ''}
    try:
        scripts = request.POST.getlist('scripts[]')
        others = os.path.join(settings.TOOLS_DIR,
                              'frida_scripts',
                              'others')
        script_ct = []
        for script in scripts:
            script_file = os.path.join(others, script + '.js')
            if not is_safe_path(others, script_file):
                return json_response(data)
            if is_file_exists(script_file):
                script_ct.append(Path(script_file).read_text())
        data['content'] = '\n'.join(script_ct)
    except Exception:
        pass
    return json_response(data)
Example #15
0
def apk_2_java(app_path, app_dir, tools_dir):
    """Run jadx."""
    try:
        logger.info('APK -> JAVA')
        args = []
        output = os.path.join(app_dir, 'java_source/')
        logger.info('Decompiling to Java with jadx')

        if os.path.exists(output):
            shutil.rmtree(output)

        if (len(settings.JADX_BINARY) > 0 and
                is_file_exists(settings.JADX_BINARY)):
            jadx = settings.JADX_BINARY
        else:
            if platform.system() == 'Windows':
                jadx = os.path.join(tools_dir, 'jadx/bin/jadx.bat')
            else:
                jadx = os.path.join(tools_dir, 'jadx/bin/jadx')
                # Set write permission, if JADX is not executable
                if not os.access(jadx, os.X_OK):
                    os.chmod(jadx, stat.S_IEXEC)
            args = [
                jadx,
                '-ds',
                output,
                '-q',
                '-r',
                '--show-bad-code',
                app_path,
            ]
            fnull = open(os.devnull, 'w')
            subprocess.call(args,
                            stdout=fnull,
                            stderr=subprocess.STDOUT)
    except Exception:
        logger.exception('Decompiling to JAVA')
Example #16
0
def run(request, api=False):
    """View iOS Files."""
    try:
        logger.info('View iOS Source File')
        exp = 'Error Description'
        file_format = 'cpp'
        if api:
            fil = request.POST['file']
            md5_hash = request.POST['hash']
            mode = request.POST['type']
            viewsource_form = ViewSourceIOSApiForm(request.POST)
        else:
            fil = request.GET['file']
            md5_hash = request.GET['md5']
            mode = request.GET['type']
            viewsource_form = ViewSourceIOSForm(request.GET)
        typ = set_ext_api(fil)
        if not viewsource_form.is_valid():
            err = FormUtil.errors_message(viewsource_form)
            if api:
                return err
            return print_n_send_error_response(request, err, False, exp)
        if mode == 'ipa':
            src = os.path.join(settings.UPLD_DIR, md5_hash + '/Payload/')
        elif mode == 'ios':
            src = os.path.join(settings.UPLD_DIR, md5_hash + '/')
        sfile = os.path.join(src, fil)
        if not is_safe_path(src, sfile):
            msg = 'Path Traversal Detected!'
            if api:
                return {'error': 'Path Traversal Detected!'}
            return print_n_send_error_response(request, msg, False, exp)
        dat = ''
        sql_dump = {}
        if typ == 'm':
            file_format = 'cpp'
            with io.open(sfile, mode='r', encoding='utf8',
                         errors='ignore') as flip:
                dat = flip.read()
        elif typ == 'xml':
            file_format = 'xml'
            with io.open(sfile, mode='r', encoding='utf8',
                         errors='ignore') as flip:
                dat = flip.read()
        elif typ == 'plist':
            file_format = 'json'
            dat = biplist.readPlist(sfile)
            try:
                dat = json.dumps(dat, indent=4, sort_keys=True)
            except Exception:
                pass
        elif typ == 'db':
            file_format = 'asciidoc'
            sql_dump = read_sqlite(sfile)
        elif typ == 'txt' and fil == 'classdump.txt':
            file_format = 'cpp'
            app_dir = os.path.join(settings.UPLD_DIR, md5_hash + '/')
            cls_dump_file = os.path.join(app_dir, 'classdump.txt')
            if is_file_exists(cls_dump_file):
                with io.open(cls_dump_file,
                             mode='r',
                             encoding='utf8',
                             errors='ignore') as flip:
                    dat = flip.read()
            else:
                dat = 'Class Dump result not Found'
        elif typ == 'txt':
            file_format = 'text'
            with io.open(sfile, mode='r', encoding='utf8',
                         errors='ignore') as flip:
                dat = flip.read()
        else:
            if api:
                return {'error': 'Invalid Parameters'}
            return HttpResponseRedirect('/error/')
        context = {
            'title': escape(ntpath.basename(fil)),
            'file': escape(ntpath.basename(fil)),
            'type': file_format,
            'dat': dat,
            'sql': sql_dump,
            'version': settings.KENSA_VER,
        }
        template = 'general/view.html'
        if api:
            return context
        return render(request, template, context)
    except Exception as exp:
        logger.exception('Error Viewing Source')
        msg = str(exp)
        exp = exp.__doc__
        if api:
            return print_n_send_error_response(request, msg, True, exp)
        return print_n_send_error_response(request, msg, False, exp)
def plist_analysis(src, is_source):
    """Plist Analysis."""
    try:
        logger.info('iOS Info.plist Analysis Started')
        plist_info = {
            'bin_name': '',
            'bin': '',
            'id': '',
            'version': '',
            'build': '',
            'sdk': '',
            'pltfm': '',
            'min': '',
            'plist_xml': '',
            'permissions': [],
            'inseccon': [],
            'bundle_name': '',
            'build_version_name': '',
            'bundle_url_types': [],
            'bundle_supported_platforms': [],
        }
        plist_file = None
        if is_source:
            logger.info('Finding Info.plist in iOS Source')
            for dirpath, _dirnames, files in os.walk(src):
                for name in files:
                    if (not any(x in dirpath for x in ['__MACOSX', 'Pods'])
                            and name == 'Info.plist'):
                        plist_file = os.path.join(dirpath, name)
                        break
        else:
            logger.info('Finding Info.plist in iOS Binary')
            dirs = os.listdir(src)
            dot_app_dir = ''
            for dir_ in dirs:
                if dir_.endswith('.app'):
                    dot_app_dir = dir_
                    break
            bin_dir = os.path.join(src, dot_app_dir)  # Full Dir/Payload/x.app
            plist_file = os.path.join(bin_dir, 'Info.plist')
        if not is_file_exists(plist_file):
            logger.warning(
                'Cannot find Info.plist file. Skipping Plist Analysis.')
        else:
            # Generic Plist Analysis
            plist_obj = plistlib.readPlist(plist_file)
            plist_info['plist_xml'] = plistlib.writePlistToBytes(
                plist_obj).decode('utf-8', 'ignore')
            plist_info['bin_name'] = (plist_obj.get('CFBundleDisplayName', '')
                                      or plist_obj.get('CFBundleName', ''))
            if not plist_info['bin_name'] and not is_source:
                # For iOS IPA
                plist_info['bin_name'] = dot_app_dir.replace('.app', '')
            plist_info['bin'] = plist_obj.get('CFBundleExecutable', '')
            plist_info['id'] = plist_obj.get('CFBundleIdentifier', '')
            plist_info['build'] = plist_obj.get('CFBundleVersion', '')
            plist_info['sdk'] = plist_obj.get('DTSDKName', '')
            plist_info['pltfm'] = plist_obj.get('DTPlatformVersion', '')
            plist_info['min'] = plist_obj.get('MinimumOSVersion', '')
            plist_info['bundle_name'] = plist_obj.get('CFBundleName', '')
            plist_info['bundle_version_name'] = plist_obj.get(
                'CFBundleShortVersionString', '')
            plist_info['bundle_url_types'] = plist_obj.get(
                'CFBundleURLTypes', [])
            plist_info['bundle_supported_platforms'] = plist_obj.get(
                'CFBundleSupportedPlatforms', [])
            # Check for app-permissions
            plist_info['permissions'] = check_permissions(plist_obj)
            # Check for ats misconfigurations
            plist_info['inseccon'] = check_transport_security(plist_obj)
        return plist_info
    except Exception:
        logger.exception('Reading from Info.plist')
def get_otool_out(tools_dir, cmd_type, bin_path, bin_dir):
    """Get otool args by OS and type."""
    if (len(settings.OTOOL_BINARY) > 0
            and is_file_exists(settings.OTOOL_BINARY)):
        otool_bin = settings.OTOOL_BINARY
    else:
        otool_bin = 'otool'
    if (len(settings.JTOOL_BINARY) > 0
            and is_file_exists(settings.JTOOL_BINARY)):
        jtool_bin = settings.JTOOL_BINARY
    else:
        jtool_bin = os.path.join(tools_dir, 'jtool.ELF64')
    jtool2_bin = os.path.join(tools_dir, 'jtool2.ELF64')
    # jtool execute permission check
    for toolbin in [jtool_bin, jtool2_bin]:
        if not os.access(toolbin, os.X_OK):
            os.chmod(toolbin, stat.S_IEXEC)
    plat = platform.system()
    if cmd_type == 'libs':
        if plat == 'Darwin':
            args = [otool_bin, '-L', bin_path]
            args2 = args
        elif plat == 'Linux':
            args = [jtool_bin, '-arch', 'arm', '-L', '-v', bin_path]
            args2 = [jtool2_bin, '-L', '-v', '-q', bin_path]
        else:
            # Platform Not Supported
            return None
        try:
            libs = subprocess.check_output(args2).decode('utf-8', 'ignore')
        except Exception:
            libs = subprocess.check_output(args).decode('utf-8', 'ignore')
        libs = smart_text(escape(libs.replace(bin_dir + '/', '')))
        return libs.split('\n')
    elif cmd_type == 'header':
        if plat == 'Darwin':
            args = [otool_bin, '-hv', bin_path]
            args2 = args
        elif plat == 'Linux':
            args = [jtool_bin, '-arch', 'arm', '-h', '-v', bin_path]
            args2 = [jtool2_bin, '-h', '-v', '-q', bin_path]
        else:
            # Platform Not Supported
            return None
        try:
            return subprocess.check_output(args2)
        except Exception:
            return subprocess.check_output(args)
    elif cmd_type == 'symbols':
        if plat == 'Darwin':
            args = [otool_bin, '-Iv', bin_path]
            args2 = args
            return subprocess.check_output(args)
        elif plat == 'Linux':
            args = [jtool_bin, '-arch', 'arm', '-S', bin_path]
            arg2 = [jtool2_bin, '-S', bin_path]
            try:
                with open(os.devnull, 'w') as devnull:
                    return subprocess.check_output(arg2, stderr=devnull)
            except Exception:
                return subprocess.check_output(args)
        else:
            # Platform Not Supported
            return None
    elif cmd_type == 'classdump':
        # Handle Classdump in Linux
        # Add timeout to handle ULEB128 malformed
        return [jtool_bin, '-arch', 'arm', '-d', 'objc', '-v', bin_path]
    return None
Example #19
0
 def clean_up(self):
     if is_file_exists(self.api_mon):
         os.remove(self.api_mon)
     if is_file_exists(self.frida_log):
         os.remove(self.frida_log)