Ejemplo n.º 1
0
 def get_apk(self, checksum, package):
     """Download APK from device."""
     try:
         out_dir = os.path.join(settings.UPLD_DIR, checksum + '/')
         if not os.path.exists(out_dir):
             os.makedirs(out_dir)
         out_file = os.path.join(out_dir, f'{checksum}.apk')
         if is_file_exists(out_file):
             return out_file
         out = self.adb_command([
             'pm',
             'path',
             package], True)
         out = out.decode('utf-8').rstrip()
         path = out.split('package:', 1)[1].strip()
         logger.info('Downloading APK')
         self.adb_command([
             'pull',
             path,
             out_file,
         ])
         if is_file_exists(out_file):
             return out_file
     except Exception:
         return False
def view_report(request, checksum, api=False):
    """Dynamic Analysis Report Generation."""
    logger.info('Dynamic Analysis Report Generation')
    try:
        droidmon = {}
        apimon = {}
        if not is_md5(checksum):
            # We need this check since checksum is not validated
            # in REST API
            return print_n_send_error_response(
                request,
                'Invalid Parameters',
                api)
        package = get_package_name(checksum)
        if not package:
            return print_n_send_error_response(
                request,
                'Invalid Parameters',
                api)
        app_dir = os.path.join(settings.UPLD_DIR, checksum + '/')
        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, api)
        fd_log = os.path.join(app_dir, 'mobsf_frida_out.txt')
        droidmon = droidmon_api_analysis(app_dir, package)
        apimon = apimon_analysis(app_dir)
        analysis_result = run_analysis(app_dir, checksum, package)
        generate_download(app_dir, checksum, download_dir, package)
        images = get_screenshots(checksum, download_dir)
        context = {'hash': checksum,
                   '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'],
                   'tls_tests': analysis_result['tls_tests'],
                   'screenshots': images['screenshots'],
                   'activity_tester': images['activities'],
                   'exported_activity_tester': images['exported_activities'],
                   'droidmon': droidmon,
                   'apimon': apimon,
                   'frida_logs': is_file_exists(fd_log),
                   'package': package,
                   'version': settings.MOBSF_VER,
                   'title': 'Dynamic Analysis'}
        template = 'dynamic_analysis/android/dynamic_report.html'
        if api:
            return context
        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, api)
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, 'mobsf_api_monitor.txt')
        fd_logs = os.path.join(apk_dir, 'mobsf_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')
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, 'mobsf_api_monitor.txt')
    fd_logs = os.path.join(apk_dir, 'mobsf_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,  # lgtm [py/path-injection]
                     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,  # lgtm [py/path-injection]
                     mode='r',
                     encoding='utf8',
                     errors='ignore') as flip:
            droidmon_data = flip.read()
    if is_file_exists(apimon):
        with io.open(apimon,  # lgtm [py/path-injection]
                     mode='r',
                     encoding='utf8',
                     errors='ignore') as flip:
            apimon_data = flip.read()
    if is_file_exists(fd_logs):
        with io.open(fd_logs,  # lgtm [py/path-injection]
                     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}
Ejemplo n.º 5
0
def frida_logs(request, api=False):
    try:
        if api:
            apphash = request.POST['hash']
            stream = True
        else:
            apphash = request.GET.get('hash', '')
            stream = request.GET.get('stream', '')
        if not is_md5(apphash):
            return invalid_params(api)
        if stream:
            apk_dir = os.path.join(settings.UPLD_DIR, apphash + '/')
            frida_logs = os.path.join(apk_dir, 'mobsf_frida_out.txt')
            data = {}
            if not is_file_exists(frida_logs):
                data = {'status': 'failed', 'message': 'Data does not exist.'}
                return send_response(data, api)
            with open(frida_logs, 'r', encoding='utf8',
                      errors='ignore') as flip:
                data = {'data': flip.read()}
            return send_response(data, api)
        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.MOBSF_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, api)
Ejemplo n.º 6
0
def live_api(request, api=False):
    try:
        if api:
            apphash = request.POST['hash']
            stream = True
        else:
            apphash = request.GET.get('hash', '')
            stream = request.GET.get('stream', '')
        if not is_md5(apphash):
            return invalid_params(api)
        if stream:
            apk_dir = os.path.join(settings.UPLD_DIR, apphash + '/')
            apimon_file = os.path.join(apk_dir, 'mobsf_api_monitor.txt')
            data = {}
            if not is_file_exists(apimon_file):
                data = {'status': 'failed', 'message': 'Data does not exist.'}
                return send_response(data, api)
            with open(apimon_file, 'r', encoding='utf8',
                      errors='ignore') as flip:
                api_list = json.loads('[{}]'.format(flip.read()[:-1]))
            data = {'data': api_list}
            return send_response(data, api)
        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.MOBSF_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, api)
    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.5.2.jar')
            args = [
                find_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
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):
            # ignore WinError3 in Windows
            shutil.rmtree(output, ignore_errors=True)

        if (len(settings.JADX_BINARY) > 0
                and is_file_exists(settings.JADX_BINARY)):
            jadx = settings.JADX_BINARY
        elif platform.system() == 'Windows':
            jadx = os.path.join(tools_dir, 'jadx/bin/jadx.bat')
        else:
            jadx = os.path.join(tools_dir, 'jadx/bin/jadx')
        # Set execute 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')
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.5.2.jar')
            output = os.path.join(app_dir, 'smali_source/')
            smali = [
                find_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')
Ejemplo n.º 10
0
 def install_mobsf_ca(self, action):
     """Install or Remove MobSF Root CA."""
     mobsf_ca = get_ca_file()
     ca_file = None
     if is_file_exists(mobsf_ca):
         ca_construct = '{}.0'
         pem = open(mobsf_ca, 'rb')
         ca_obj = crypto.load_certificate(crypto.FILETYPE_PEM, pem.read())
         md = md5(ca_obj.get_subject().der()).digest()
         ret = (md[0] | (md[1] << 8) | (md[2] << 16) | md[3] << 24)
         ca_file_hash = hex(ret).lstrip('0x')
         ca_file = os.path.join('/system/etc/security/cacerts/',
                                ca_construct.format(ca_file_hash))
         pem.close()
     else:
         logger.warning('mitmproxy root CA is not generated yet.')
         return
     if action == 'install':
         logger.info('Installing MobSF RootCA')
         self.adb_command(['push',
                           mobsf_ca,
                           ca_file])
         self.adb_command(['chmod',
                           '644',
                           ca_file], True)
     elif action == 'remove':
         logger.info('Removing MobSF RootCA')
         self.adb_command(['rm',
                           ca_file], True)
Ejemplo n.º 11
0
def apimon_analysis(app_dir):
    """API Analysis."""
    api_details = {}
    try:
        location = os.path.join(app_dir, 'mobsf_api_monitor.txt')
        if not is_file_exists(location):
            return {}
        logger.info('Frida API Monitor Analysis')
        with open(location, 'r', encoding='utf8', errors='ignore') 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).decode(
                        'utf-8', 'ignore')
            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
def get_ca_file():
    """Get CA Dir."""
    from mitmproxy import ctx
    ca_dir = Path(ctx.mitmproxy.options.CONF_DIR).expanduser()
    ca_file = os.path.join(str(ca_dir), 'mitmproxy-ca-cert.pem')
    if not is_file_exists(ca_file):
        create_ca()
    return ca_file
Ejemplo n.º 13
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, members=safe_paths(tar))
    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,  # lgtm [py/path-injection]
                                '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
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='utf8',
                  errors='ignore') 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']).decode('utf-8', 'ignore')
                        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
Ejemplo n.º 15
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)
Ejemplo n.º 16
0
def get_script(request, api=False):
    """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):
                data = {
                    'status': 'failed',
                    'message': 'Path traversal detected.'
                }
                return send_response(data, api)
            if is_file_exists(script_file):
                script_ct.append(Path(script_file).read_text())
        data['content'] = '\n'.join(script_ct)
    except Exception:
        pass
    return send_response(data, api)
Ejemplo n.º 17
0
def run(request, api=False):
    """View iOS Files."""
    try:
        logger.info('View iOS Source File')
        exp = 'Error Description'
        file_format = None
        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)
        base = Path(settings.UPLD_DIR) / md5_hash
        if mode == 'ipa':
            src1 = base / 'payload'
            src2 = base / 'Payload'
            if src1.exists():
                src = src1
            elif src2.exists():
                src = src2
            else:
                raise Exception('MobSF cannot find Payload directory')
        elif mode == 'ios':
            src = base
        sfile = src / fil
        sfile = sfile.as_posix()
        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,  # lgtm [py/path-injection]
                        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,
            'data': dat,
            'sqlite': sql_dump,
            'version': settings.MOBSF_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)
Ejemplo n.º 18
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)
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': [],
            'bundle_version_name': '',
        }
        plist_file = None
        plist_files = []
        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.endswith('.plist')):
                        plist_files.append(os.path.join(dirpath, name))
                        if name == 'Info.plist':
                            plist_file = os.path.join(dirpath, name)
        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')
            plist_files = [plist_file]

        # Skip Plist Analysis if there is no Info.plist
        if not plist_file or not is_file_exists(plist_file):
            logger.warning(
                'Cannot find Info.plist file. Skipping Plist Analysis.')
            return plist_info

        # Generic Plist Analysis
        plist_obj = {}
        with open(plist_file, 'rb') as fp:
            plist_obj = load(fp)
        plist_info['plist_xml'] = dumps(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', [])
        logger.info('Checking Permissions')
        logger.info('Checking for Insecure Connections')
        for plist_file_ in plist_files:
            plist_obj_ = {}
            with open(plist_file_, 'rb') as fp:
                plist_obj_ = load(fp)
            # 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 static_analyzer(request, api=False):
    """Do static analysis on an request and save to db."""
    try:
        rescan = False
        if api:
            typ = request.POST['scan_type']
            checksum = request.POST['hash']
            filename = request.POST['file_name']
            re_scan = request.POST.get('re_scan', 0)
        else:
            typ = request.GET['type']
            checksum = request.GET['checksum']
            filename = request.GET['name']
            re_scan = request.GET.get('rescan', 0)
        if re_scan == '1':
            rescan = True
        # Input validation
        app_dic = {}
        match = re.match('^[0-9a-f]{32}$', checksum)
        if (match and filename.lower().endswith(
            ('.apk', '.xapk', '.zip', '.apks'))
                and typ in ['zip', 'apk', 'xapk', 'apks']):
            app_dic['dir'] = Path(settings.BASE_DIR)  # BASE DIR
            app_dic['app_name'] = filename  # APP ORIGINAL NAME
            app_dic['md5'] = checksum  # MD5
            # APP DIRECTORY
            app_dic['app_dir'] = Path(settings.UPLD_DIR) / checksum
            app_dic['tools_dir'] = app_dic['dir'] / 'StaticAnalyzer' / 'tools'
            app_dic['tools_dir'] = app_dic['tools_dir'].as_posix()
            logger.info('Starting Analysis on: %s', app_dic['app_name'])
            if typ == 'xapk':
                # Handle XAPK
                # Base APK will have the MD5 of XAPK
                if not handle_xapk(app_dic):
                    raise Exception('Invalid XAPK File')
                typ = 'apk'
            elif typ == 'apks':
                # Handle Split APK
                if not handle_split_apk(app_dic):
                    raise Exception('Invalid Split APK File')
                typ = 'apk'
            if typ == 'apk':
                app_dic['app_file'] = app_dic['md5'] + '.apk'  # NEW FILENAME
                app_dic['app_path'] = (app_dic['app_dir'] /
                                       app_dic['app_file']).as_posix()
                app_dic['app_dir'] = app_dic['app_dir'].as_posix() + '/'
                # Check if in DB
                # pylint: disable=E1101
                db_entry = StaticAnalyzerAndroid.objects.filter(
                    MD5=app_dic['md5'])
                if db_entry.exists() and not rescan:
                    context = get_context_from_db_entry(db_entry)
                else:
                    # ANALYSIS BEGINS
                    app_dic['size'] = str(file_size(
                        app_dic['app_path'])) + 'MB'  # FILE SIZE
                    app_dic['sha1'], app_dic['sha256'] = hash_gen(
                        app_dic['app_path'])
                    app_dic['files'] = unzip(app_dic['app_path'],
                                             app_dic['app_dir'])
                    logger.info('APK Extracted')
                    if not app_dic['files']:
                        # Can't Analyze APK, bail out.
                        msg = 'APK file is invalid or corrupt'
                        if api:
                            return print_n_send_error_response(
                                request, msg, True)
                        else:
                            return print_n_send_error_response(
                                request, msg, False)
                    app_dic['certz'] = get_hardcoded_cert_keystore(
                        app_dic['files'])
                    # Manifest XML
                    mani_file, mani_xml = get_manifest(
                        app_dic['app_path'],
                        app_dic['app_dir'],
                        app_dic['tools_dir'],
                        '',
                        True,
                    )
                    app_dic['manifest_file'] = mani_file
                    app_dic['parsed_xml'] = mani_xml

                    # get app_name
                    app_dic['real_name'] = get_app_name(
                        app_dic['app_path'],
                        app_dic['app_dir'],
                        app_dic['tools_dir'],
                        True,
                    )

                    # Get icon
                    res_path = os.path.join(app_dic['app_dir'], 'res')
                    app_dic['icon_hidden'] = True
                    # Even if the icon is hidden, try to guess it by the
                    # default paths
                    app_dic['icon_found'] = False
                    app_dic['icon_path'] = ''
                    # TODO: Check for possible different names for resource
                    # folder?
                    if os.path.exists(res_path):
                        icon_dic = get_icon(app_dic['app_path'], res_path)
                        if icon_dic:
                            app_dic['icon_hidden'] = icon_dic['hidden']
                            app_dic['icon_found'] = bool(icon_dic['path'])
                            app_dic['icon_path'] = icon_dic['path']

                    # Set Manifest link
                    app_dic['mani'] = ('../manifest_view/?md5=' +
                                       app_dic['md5'] + '&type=apk&bin=1')
                    man_data_dic = manifest_data(app_dic['parsed_xml'])
                    app_dic['playstore'] = get_app_details(
                        man_data_dic['packagename'])
                    man_an_dic = manifest_analysis(
                        app_dic['parsed_xml'],
                        man_data_dic,
                        '',
                        app_dic['app_dir'],
                    )
                    elf_dict = elf_analysis(app_dic['app_dir'])
                    cert_dic = cert_info(app_dic['app_dir'],
                                         app_dic['app_file'])
                    apkid_results = apkid_analysis(app_dic['app_dir'],
                                                   app_dic['app_path'],
                                                   app_dic['app_name'])
                    tracker = Trackers.Trackers(app_dic['app_dir'],
                                                app_dic['tools_dir'])
                    tracker_res = tracker.get_trackers()

                    apk_2_java(app_dic['app_path'], app_dic['app_dir'],
                               app_dic['tools_dir'])

                    dex_2_smali(app_dic['app_dir'], app_dic['tools_dir'])

                    code_an_dic = code_analysis(app_dic['app_dir'], 'apk',
                                                app_dic['manifest_file'])

                    quark_results = quark_analysis(app_dic['app_dir'],
                                                   app_dic['app_path'])

                    # Get the strings from android resource and shared objects
                    string_res = strings_from_apk(app_dic['app_file'],
                                                  app_dic['app_dir'],
                                                  elf_dict['elf_strings'])
                    if string_res:
                        app_dic['strings'] = string_res['strings']
                        app_dic['secrets'] = string_res['secrets']
                        code_an_dic['urls_list'].extend(
                            string_res['urls_list'])
                        code_an_dic['urls'].extend(string_res['url_nf'])
                        code_an_dic['emails'].extend(string_res['emails_nf'])
                    else:
                        app_dic['strings'] = []
                        app_dic['secrets'] = []
                    # Firebase DB Check
                    code_an_dic['firebase'] = firebase_analysis(
                        list(set(code_an_dic['urls_list'])))
                    # Domain Extraction and Malware Check
                    logger.info(
                        'Performing Malware Check on extracted Domains')
                    code_an_dic['domains'] = MalwareDomainCheck().scan(
                        list(set(code_an_dic['urls_list'])))
                    # Copy App icon
                    copy_icon(app_dic['md5'], app_dic['icon_path'])
                    app_dic['zipped'] = 'apk'

                    logger.info('Connecting to Database')
                    try:
                        # SAVE TO DB
                        if rescan:
                            logger.info('Updating Database...')
                            save_or_update(
                                'update',
                                app_dic,
                                man_data_dic,
                                man_an_dic,
                                code_an_dic,
                                cert_dic,
                                elf_dict['elf_analysis'],
                                apkid_results,
                                quark_results,
                                tracker_res,
                            )
                            update_scan_timestamp(app_dic['md5'])
                        else:
                            logger.info('Saving to Database')
                            save_or_update(
                                'save',
                                app_dic,
                                man_data_dic,
                                man_an_dic,
                                code_an_dic,
                                cert_dic,
                                elf_dict['elf_analysis'],
                                apkid_results,
                                quark_results,
                                tracker_res,
                            )
                    except Exception:
                        logger.exception('Saving to Database Failed')
                    context = get_context_from_analysis(
                        app_dic,
                        man_data_dic,
                        man_an_dic,
                        code_an_dic,
                        cert_dic,
                        elf_dict['elf_analysis'],
                        apkid_results,
                        quark_results,
                        tracker_res,
                    )
                context['average_cvss'], context['security_score'] = score(
                    context['code_analysis'])
                context['dynamic_analysis_done'] = is_file_exists(
                    os.path.join(app_dic['app_dir'], 'logcat.txt'))

                context['virus_total'] = None
                if settings.VT_ENABLED:
                    vt = VirusTotal.VirusTotal()
                    context['virus_total'] = vt.get_result(
                        app_dic['app_path'], app_dic['md5'])
                template = 'static_analysis/android_binary_analysis.html'
                if api:
                    return context
                else:
                    return render(request, template, context)
            elif typ == 'zip':
                ret = ('/static_analyzer_ios/?name=' + app_dic['app_name'] +
                       '&type=ios&checksum=' + app_dic['md5'])
                # Check if in DB
                # pylint: disable=E1101
                cert_dic = {
                    'certificate_info': '',
                    'certificate_status': '',
                    'description': '',
                }
                app_dic['strings'] = []
                app_dic['secrets'] = []
                app_dic['zipped'] = ''
                # Above fields are only available for APK and not ZIP
                app_dic['app_file'] = app_dic['md5'] + '.zip'  # NEW FILENAME
                app_dic['app_path'] = (app_dic['app_dir'] /
                                       app_dic['app_file']).as_posix()
                app_dic['app_dir'] = app_dic['app_dir'].as_posix() + '/'
                db_entry = StaticAnalyzerAndroid.objects.filter(
                    MD5=app_dic['md5'])
                ios_db_entry = StaticAnalyzerIOS.objects.filter(
                    MD5=app_dic['md5'])
                if db_entry.exists() and not rescan:
                    context = get_context_from_db_entry(db_entry)
                elif ios_db_entry.exists() and not rescan:
                    if api:
                        return {'type': 'ios'}
                    else:
                        return HttpResponseRedirect(ret)
                else:
                    logger.info('Extracting ZIP')
                    app_dic['files'] = unzip(app_dic['app_path'],
                                             app_dic['app_dir'])
                    # Check if Valid Directory Structure and get ZIP Type
                    pro_type, valid = valid_source_code(app_dic['app_dir'])
                    logger.info('Source code type - %s', pro_type)
                    if valid and pro_type == 'ios':
                        logger.info('Redirecting to iOS Source Code Analyzer')
                        if api:
                            return {'type': 'ios'}
                        else:
                            ret += f'&rescan={str(int(rescan))}'
                            return HttpResponseRedirect(ret)
                    app_dic['certz'] = get_hardcoded_cert_keystore(
                        app_dic['files'])
                    app_dic['zipped'] = pro_type
                    if valid and (pro_type in ['eclipse', 'studio']):
                        # ANALYSIS BEGINS
                        app_dic['size'] = str(file_size(
                            app_dic['app_path'])) + 'MB'  # FILE SIZE
                        app_dic['sha1'], app_dic['sha256'] = hash_gen(
                            app_dic['app_path'])

                        # Manifest XML
                        mani_file, mani_xml = get_manifest(
                            '',
                            app_dic['app_dir'],
                            app_dic['tools_dir'],
                            pro_type,
                            False,
                        )
                        app_dic['manifest_file'] = mani_file
                        app_dic['parsed_xml'] = mani_xml

                        # get app_name
                        app_dic['real_name'] = get_app_name(
                            app_dic['app_path'],
                            app_dic['app_dir'],
                            app_dic['tools_dir'],
                            False,
                        )

                        # Set manifest view link
                        app_dic['mani'] = ('../manifest_view/?md5=' +
                                           app_dic['md5'] + '&type=' +
                                           pro_type + '&bin=0')

                        man_data_dic = manifest_data(app_dic['parsed_xml'])
                        app_dic['playstore'] = get_app_details(
                            man_data_dic['packagename'])
                        man_an_dic = manifest_analysis(
                            app_dic['parsed_xml'],
                            man_data_dic,
                            pro_type,
                            app_dic['app_dir'],
                        )
                        # Get icon
                        eclipse_res_path = os.path.join(
                            app_dic['app_dir'], 'res')
                        studio_res_path = os.path.join(app_dic['app_dir'],
                                                       'app', 'src', 'main',
                                                       'res')
                        if os.path.exists(eclipse_res_path):
                            res_path = eclipse_res_path
                        elif os.path.exists(studio_res_path):
                            res_path = studio_res_path
                        else:
                            res_path = ''

                        app_dic['icon_hidden'] = man_an_dic['icon_hidden']
                        app_dic['icon_found'] = False
                        app_dic['icon_path'] = ''
                        if res_path:
                            app_dic['icon_path'] = find_icon_path_zip(
                                res_path, man_data_dic['icons'])
                            if app_dic['icon_path']:
                                app_dic['icon_found'] = True

                        if app_dic['icon_path']:
                            if os.path.exists(app_dic['icon_path']):
                                shutil.copy2(
                                    app_dic['icon_path'],
                                    os.path.join(settings.DWD_DIR,
                                                 app_dic['md5'] + '-icon.png'))

                        code_an_dic = code_analysis(app_dic['app_dir'],
                                                    pro_type,
                                                    app_dic['manifest_file'])
                        # Firebase DB Check
                        code_an_dic['firebase'] = firebase_analysis(
                            list(set(code_an_dic['urls_list'])))
                        # Domain Extraction and Malware Check
                        logger.info(
                            'Performing Malware Check on extracted Domains')
                        code_an_dic['domains'] = MalwareDomainCheck().scan(
                            list(set(code_an_dic['urls_list'])))
                        logger.info('Connecting to Database')
                        try:
                            # SAVE TO DB
                            if rescan:
                                logger.info('Updating Database...')
                                save_or_update(
                                    'update',
                                    app_dic,
                                    man_data_dic,
                                    man_an_dic,
                                    code_an_dic,
                                    cert_dic,
                                    [],
                                    {},
                                    [],
                                    {},
                                )
                                update_scan_timestamp(app_dic['md5'])
                            else:
                                logger.info('Saving to Database')
                                save_or_update(
                                    'save',
                                    app_dic,
                                    man_data_dic,
                                    man_an_dic,
                                    code_an_dic,
                                    cert_dic,
                                    [],
                                    {},
                                    [],
                                    {},
                                )
                        except Exception:
                            logger.exception('Saving to Database Failed')
                        context = get_context_from_analysis(
                            app_dic,
                            man_data_dic,
                            man_an_dic,
                            code_an_dic,
                            cert_dic,
                            [],
                            {},
                            [],
                            {},
                        )
                    else:
                        msg = 'This ZIP Format is not supported'
                        if api:
                            return print_n_send_error_response(
                                request, msg, True)
                        else:
                            print_n_send_error_response(request, msg, False)
                            ctx = {
                                'title': 'Invalid ZIP archive',
                                'version': settings.MOBSF_VER,
                            }
                            template = 'general/zip.html'
                            return render(request, template, ctx)
                context['average_cvss'], context['security_score'] = score(
                    context['code_analysis'])
                template = 'static_analysis/android_source_analysis.html'
                if api:
                    return context
                else:
                    return render(request, template, context)
            else:
                err = ('Only APK,IPA and Zipped '
                       'Android/iOS Source code supported now!')
                logger.error(err)
        else:
            msg = 'Hash match failed or Invalid file extension or file type'
            if api:
                return print_n_send_error_response(request, msg, True)
            else:
                return print_n_send_error_response(request, msg, False)

    except Exception as excep:
        logger.exception('Error Performing Static Analysis')
        msg = str(excep)
        exp = excep.__doc__
        if api:
            return print_n_send_error_response(request, msg, True, exp)
        else:
            return print_n_send_error_response(request, msg, False, exp)