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}
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)
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')
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)
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
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
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)
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)
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)
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)