def run(request): """Generate downloads for apk, java and smali.""" try: logger.info('Generating Downloads') md5 = request.GET['hash'] file_type = request.GET['file_type'] match = re.match('^[0-9a-f]{32}$', md5) if not match and file_type not in ['apk', 'smali', 'java']: logger.exception('Invalid options') return print_n_send_error_response(request, 'Invalid options') app_dir = os.path.join(settings.UPLD_DIR, md5) file_name = '' if file_type == 'java': # For Java file_name = md5 + '-java' directory = os.path.join(app_dir, 'java_source/') dwd_dir = os.path.join(settings.DWD_DIR, file_name) shutil.make_archive(dwd_dir, 'zip', directory) file_name = file_name + '.zip' elif file_type == 'smali': # For Smali file_name = md5 + '-smali' directory = os.path.join(app_dir, 'smali_source/') dwd_dir = os.path.join(settings.DWD_DIR, file_name) shutil.make_archive(dwd_dir, 'zip', directory) file_name = file_name + '.zip' elif file_type == 'apk': file_name = md5 + '.apk' src = os.path.join(app_dir, file_name) dst = os.path.join(settings.DWD_DIR, file_name) shutil.copy2(src, dst) return redirect('/download/' + file_name) except Exception: logger.exception('Generating Downloads') return print_n_send_error_response(request, 'Generating Downloads')
def logcat(request, api=False): logger.info('Starting Logcat streaming') try: pkg = request.GET.get('package') if pkg: if not strict_package_check(pkg): return print_n_send_error_response(request, 'Invalid package name', api) template = 'dynamic_analysis/android/logcat.html' return render(request, template, {'package': pkg}) if api: app_pkg = request.POST['package'] else: app_pkg = request.GET.get('app_package') if app_pkg: if not strict_package_check(app_pkg): return print_n_send_error_response(request, 'Invalid package name', api) adb = os.environ['MOBSF_ADB'] g = proc.Group() g.run([adb, 'logcat', app_pkg + ':V', '*:*']) def read_process(): while g.is_pending(): lines = g.readlines() for _, line in lines: yield 'data:{}\n\n'.format(line) return StreamingHttpResponse(read_process(), content_type='text/event-stream') return print_n_send_error_response(request, 'Invalid parameters', api) except Exception: logger.exception('Logcat Streaming') err = 'Error in Logcat streaming' return print_n_send_error_response(request, err, api)
def appsec_dashboard(request, checksum, api=False): """Provide data for appsec dashboard.""" try: android_static_db = StaticAnalyzerAndroid.objects.filter( MD5=checksum) ios_static_db = StaticAnalyzerIOS.objects.filter( MD5=checksum) if android_static_db.exists(): context = get_android_dashboard(android_static_db) elif ios_static_db.exists(): context = get_ios_dashboard(ios_static_db) else: if api: return {'not_found': 'Report not found or supported'} else: msg = 'Report not found or supported' return print_n_send_error_response(request, msg) context['version'] = settings.MOBSF_VER context['title'] = 'AppSec Scorecard' context['efr01'] = True if settings.EFR_01 == '1' else False if api: return context else: return render( request, 'static_analysis/appsec_dashboard.html', context) except Exception as exp: logger.exception('Error Generating Application Security Dashboard') msg = str(exp) exp = exp.__doc__ if api: return print_n_send_error_response(request, msg, True, exp) else: return print_n_send_error_response(request, msg, False, exp)
def dynamic_analysis(request, api=False): """Android Dynamic Analysis Entry point.""" try: scan_apps = [] device_packages = {} and_ver = None and_sdk = None apks = StaticAnalyzerAndroid.objects.filter( APP_TYPE='apk') for apk in reversed(apks): temp_dict = { 'ICON_FOUND': apk.ICON_FOUND, 'MD5': apk.MD5, 'APP_NAME': apk.APP_NAME, 'VERSION_NAME': apk.VERSION_NAME, 'FILE_NAME': apk.FILE_NAME, 'PACKAGE_NAME': apk.PACKAGE_NAME, } scan_apps.append(temp_dict) try: identifier = get_device() except Exception: msg = ('Is Android VM running? MobSF cannot' ' find android instance identifier.' ' Please run an android instance and refresh' ' this page. If this error persists,' ' set ANALYZER_IDENTIFIER in ' f'{get_config_loc()}') return print_n_send_error_response(request, msg, api) try: if identifier: env = Environment(identifier) device_packages = env.get_device_packages() pkg_file = Path(settings.DWD_DIR) / 'packages.json' with pkg_file.open('w', encoding='utf-8') as target: dump(device_packages, target) and_ver = env.get_android_version() and_sdk = env.get_android_sdk() except Exception: pass context = {'apps': scan_apps, 'identifier': identifier, 'android_version': and_ver, 'android_sdk': and_sdk, 'proxy_ip': get_proxy_ip(identifier), 'proxy_port': settings.PROXY_PORT, 'settings_loc': get_config_loc(), 'device_packages': device_packages, 'title': 'MobSF Dynamic Analysis', 'version': settings.MOBSF_VER} if api: return context template = 'dynamic_analysis/dynamic_analysis.html' return render(request, template, context) except Exception as exp: logger.exception('Dynamic Analysis') return print_n_send_error_response(request, exp, api)
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 view_file(request, api=False): """View File.""" logger.info('Viewing File') try: typ = '' rtyp = '' dat = '' sql_dump = {} if api: fil = request.POST['file'] md5_hash = request.POST['hash'] typ = request.POST['type'] else: fil = request.GET['file'] md5_hash = request.GET['hash'] typ = request.GET['type'] if not is_md5(md5_hash): return print_n_send_error_response(request, 'Invalid Parameters', api) src = os.path.join(settings.UPLD_DIR, md5_hash, 'DYNAMIC_DeviceData/') sfile = os.path.join(src, fil) if not is_safe_path(src, sfile) or is_path_traversal(fil): err = 'Path Traversal Attack Detected' return print_n_send_error_response(request, err, api) with io.open( sfile, # lgtm [py/path-injection] mode='r', encoding='ISO-8859-1') as flip: dat = flip.read() if fil.endswith('.xml') and typ == 'xml': rtyp = 'xml' elif typ == 'db': dat = None sql_dump = read_sqlite(sfile) rtyp = 'asciidoc' elif typ == 'others': rtyp = 'asciidoc' else: err = 'File type not supported' return print_n_send_error_response(request, err, api) fil = escape(ntpath.basename(fil)) context = { 'title': fil, 'file': fil, 'data': dat, 'sqlite': sql_dump, 'type': rtyp, 'version': settings.MOBSF_VER, } template = 'general/view.html' if api: return context return render(request, template, context) except Exception: logger.exception('Viewing File') return print_n_send_error_response(request, 'Error Viewing File', api)
def compare_apps(request, hash1: str, hash2: str, api=False): if hash1 == hash2: error_msg = 'Results with same hash cannot be compared' return print_n_send_error_response(request, error_msg, api) # Second Validation for REST API if not (is_md5(hash1) and is_md5(hash2)): error_msg = 'Invalid hashes' return print_n_send_error_response(request, error_msg, api) logger.info('Starting App compare for %s and %s', hash1, hash2) return generic_compare(request, hash1, hash2, api)
def run(request): """Find filename/content in source files (ajax response).""" try: match = re.match('^[0-9a-f]{32}$', request.POST['md5']) if not match: raise ValueError('Invalid MD5 hash') md5 = request.POST['md5'] query = request.POST['q'] code = request.POST['code'] search_type = request.POST['search_type'] if search_type not in ['content', 'filename']: return print_n_send_error_response(request, 'Unknown search type', True) matches = set() base = Path(settings.UPLD_DIR) / md5 if code == 'smali': src = base / 'smali_source' else: try: src = find_java_source_folder(base)[0] except StopIteration: msg = 'Invalid Directory Structure' return print_n_send_error_response(request, msg, True) exts = ['.java', '.kt', '.smali'] files = [p for p in src.rglob('*') if p.suffix in exts] for fname in files: file_path = fname.as_posix() rpath = file_path.replace(src.as_posix(), '') rpath = rpath[1:] if search_type == 'content': dat = fname.read_text('utf-8', 'ignore') if query.lower() in dat.lower(): matches.add(escape(rpath)) elif search_type == 'filename' and \ query.lower() in fname.name.lower(): matches.add(escape(rpath)) flz = len(matches) context = { 'title': 'Search Results', 'matches': list(matches), 'term': query, 'found': str(flz), 'search_type': search_type, 'version': settings.MOBSF_VER, } return JsonResponse(json.dumps(context), safe=False) except Exception: logger.exception('Searching Failed') return print_n_send_error_response( request, 'Searching Failed', True)
def run(request, api=False): """View the source of a file.""" try: logger.info('View Android Source File') exp = 'Error Description' if api: fil = request.POST['file'] md5 = request.POST['hash'] typ = request.POST['type'] viewsource_form = ViewSourceAndroidApiForm(request.POST) else: fil = request.GET['file'] md5 = request.GET['md5'] typ = request.GET['type'] viewsource_form = ViewSourceAndroidForm(request.GET) if not viewsource_form.is_valid(): err = FormUtil.errors_message(viewsource_form) return print_n_send_error_response(request, err, api, exp) base = Path(settings.UPLD_DIR) / md5 if typ == 'smali': src = base / 'smali_source' syntax = 'smali' else: try: src, syntax, _ = find_java_source_folder(base) except StopIteration: msg = 'Invalid Directory Structure' return print_n_send_error_response(request, msg, api) sfile = src / fil if not is_safe_path(src, sfile.as_posix()): msg = 'Path Traversal Detected!' return print_n_send_error_response(request, msg, api) context = { 'title': escape(ntpath.basename(fil)), 'file': escape(ntpath.basename(fil)), 'data': sfile.read_text('utf-8', 'ignore'), 'type': syntax, 'sqlite': {}, '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__ return print_n_send_error_response(request, msg, api, exp)
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 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 dynamic_analysis(request, api=False): """Android Dynamic Analysis Entry point.""" try: scan_apps = [] apks = StaticAnalyzerAndroid.objects.filter( APP_TYPE='apk').order_by('-id') for apk in apks: temp_dict = { 'ICON_FOUND': apk.ICON_FOUND, 'MD5': apk.MD5, 'APP_NAME': apk.APP_NAME, 'VERSION_NAME': apk.VERSION_NAME, 'FILE_NAME': apk.FILE_NAME, 'PACKAGE_NAME': apk.PACKAGE_NAME, } scan_apps.append(temp_dict) try: identifier = get_device() except Exception: msg = ('Is Android VM running? MobSF cannot' ' find android instance identifier.' ' Please run an android instance and refresh' ' this page. If this error persists,' ' set ANALYZER_IDENTIFIER in ' f'{get_config_loc()}') return print_n_send_error_response(request, msg, api) proxy_ip = get_proxy_ip(identifier) context = { 'apps': scan_apps, 'identifier': identifier, 'proxy_ip': proxy_ip, 'proxy_port': settings.PROXY_PORT, 'settings_loc': get_config_loc(), 'title': 'MobSF Dynamic Analysis', 'version': settings.MOBSF_VER } if api: return context template = 'dynamic_analysis/dynamic_analysis.html' return render(request, template, context) except Exception as exp: logger.exception('Dynamic Analysis') return print_n_send_error_response(request, exp, api)
def search(request): """Search Scan by MD5 Route.""" md5 = request.GET['md5'] if re.match('[0-9a-f]{32}', md5): db_obj = RecentScansDB.objects.filter(MD5=md5) if db_obj.exists(): return HttpResponseRedirect('/' + db_obj[0].URL) else: return HttpResponseRedirect('/not_found/') return print_n_send_error_response(request, 'Invalid Scan Hash')
def trigger_static_analysis(request, checksum): """On device APK Static Analysis.""" try: identifier = None if not is_md5(checksum): return print_n_send_error_response( request, 'Invalid MD5') package = get_package_name(checksum) if not package: return print_n_send_error_response( request, 'Cannot get package name from checksum') try: identifier = get_device() except Exception: pass if not identifier: err = 'Cannot connect to Android Runtime' return print_n_send_error_response(request, err) env = Environment(identifier) apk_file = env.get_apk(checksum, package) if not apk_file: err = 'Failed to download APK file' return print_n_send_error_response(request, err) data = { 'analyzer': 'static_analyzer', 'status': 'success', 'hash': checksum, 'scan_type': 'apk', 'file_name': f'{package}.apk', } add_to_recent_scan(data) return HttpResponseRedirect( f'/static_analyzer/?name=' # lgtm [py/url-redirection] f'{package}.apk&checksum={checksum}' f'&type=apk') except Exception: msg = 'On device APK Static Analysis' logger.exception(msg) return print_n_send_error_response(request, msg)
def search(request): """Search Scan by MD5 Route.""" md5 = request.GET['md5'] if re.match('[0-9a-f]{32}', md5): db_obj = RecentScansDB.objects.filter(MD5=md5) if db_obj.exists(): e = db_obj[0] url = (f'/{e.ANALYZER }/?name={e.FILE_NAME}&' f'checksum={e.MD5}&type={e.SCAN_TYPE}') return HttpResponseRedirect(url) else: return HttpResponseRedirect('/not_found/') return print_n_send_error_response(request, 'Invalid Scan Hash')
def download(request): """Download from mobsf.MobSF Route.""" msg = 'Error Downloading File ' if request.method == 'GET': allowed_exts = settings.ALLOWED_EXTENSIONS filename = request.path.replace('/download/', '', 1) # Security Checks if '../' in filename: msg = 'Path Traversal Attack Detected' return print_n_send_error_response(request, msg) ext = os.path.splitext(filename)[1] if ext in allowed_exts: dwd_file = os.path.join(settings.DWD_DIR, filename) if os.path.isfile(dwd_file): wrapper = FileWrapper(open(dwd_file, 'rb')) response = HttpResponse(wrapper, content_type=allowed_exts[ext]) response['Content-Length'] = os.path.getsize(dwd_file) return response if ('screen/screen.png' not in filename and '-icon.png' not in filename): msg += filename return print_n_send_error_response(request, msg) return HttpResponse('')
def run(request): """Source Tree - Java/Smali view.""" try: logger.info('Listing Source files') match = re.match('^[0-9a-f]{32}$', request.GET['md5']) if not match: return print_n_send_error_response(request, 'Scan hash not found') md5 = request.GET['md5'] typ = request.GET['type'] base = Path(settings.UPLD_DIR) / md5 if typ == 'smali': src = base / 'smali_source' else: try: src = find_java_source_folder(base)[0] except StopIteration: return print_n_send_error_response( request, 'Invalid Directory Structure') tree_index = tree_index_maker(src, len(src.as_posix())) context = { 'subfiles': tree_index, 'title': f'{typ.capitalize()} Source', 'hash': md5, 'source_type': typ, 'version': settings.MOBSF_VER, 'api_key': api_key(), } template = 'static_analysis/source_tree.html' return render(request, template, context) except Exception: logger.exception('Getting Source Files') return print_n_send_error_response( request, 'Error Getting Source Files')
def httptools_start(request): """Start httprools UI.""" logger.info('Starting httptools Web UI') try: stop_httptools(settings.PROXY_PORT) start_httptools_ui(settings.PROXY_PORT) time.sleep(3) logger.info('httptools UI started') if request.GET['project']: project = request.GET['project'] else: project = '' url = ('http://localhost:{}' '/dashboard/{}'.format(str(settings.PROXY_PORT), project)) return HttpResponseRedirect(url) # lgtm [py/reflective-xss] except Exception: logger.exception('Starting httptools Web UI') err = 'Error Starting httptools UI' return print_n_send_error_response(request, err)
def httptools_start(request): """Start httprools UI.""" logger.info('Starting httptools Web UI') try: httptools_url = get_http_tools_url(request) stop_httptools(httptools_url) start_httptools_ui(settings.PROXY_PORT) time.sleep(3) logger.info('httptools UI started') if request.GET['project']: project = request.GET['project'] else: project = '' url = f'{httptools_url}/dashboard/{project}' return HttpResponseRedirect(url) # lgtm [py/reflective-xss] except Exception: logger.exception('Starting httptools Web UI') err = 'Error Starting httptools UI' return print_n_send_error_response(request, err)
def download(request): """Download from mobsf.MobSF Route.""" if request.method == 'GET': root = settings.DWD_DIR allowed_exts = settings.ALLOWED_EXTENSIONS filename = request.path.replace('/download/', '', 1) dwd_file = os.path.join(root, filename) # Security Checks if '../' in filename or not is_safe_path(root, dwd_file): msg = 'Path Traversal Attack Detected' return print_n_send_error_response(request, msg) ext = os.path.splitext(filename)[1] if ext in allowed_exts: if os.path.isfile(dwd_file): wrapper = FileWrapper(open(dwd_file, 'rb')) # lgtm [py/path-injection] response = HttpResponse(wrapper, content_type=allowed_exts[ext]) response['Content-Length'] = os.path.getsize(dwd_file) return response if filename.endswith(('screen/screen.png', '-icon.png')): return HttpResponse('') return HttpResponse(status=404)
def staticanalyzer_windows(request, api=False): """Analyse a windows app.""" try: # Input validation logger.info('Windows Static Analysis Started') rescan = False app_dic = {} # Dict to store the binary attributes if api: typ = request.POST['scan_type'] re_scan = request.POST.get('re_scan', 0) checksum = request.POST['hash'] filename = request.POST['file_name'] else: typ = request.GET['type'] re_scan = request.GET.get('rescan', 0) checksum = request.GET['checksum'] filename = request.GET['name'] if re_scan == '1': rescan = True md5_regex = re.match('^[0-9a-f]{32}$', checksum) if (md5_regex) and (typ in ['appx']): app_dic['app_name'] = filename # APP ORIGINAL NAME app_dic['md5'] = checksum app_dic['app_dir'] = os.path.join( settings.UPLD_DIR, app_dic['md5'] + '/') app_dic['tools_dir'] = os.path.join( settings.BASE_DIR, 'StaticAnalyzer/tools/windows/') if typ == 'appx': # DB db_entry = StaticAnalyzerWindows.objects.filter( MD5=app_dic['md5'], ) if db_entry.exists() and not rescan: logger.info( 'Analysis is already Done.' ' Fetching data from the DB...') context = get_context_from_db_entry(db_entry) else: logger.info('Windows Binary Analysis Started') app_dic['app_path'] = os.path.join( app_dic['app_dir'], app_dic['md5'] + '.appx') # ANALYSIS BEGINS app_dic['size'] = str( file_size(app_dic['app_path'])) + 'MB' # Generate hashes app_dic['sha1'], app_dic[ 'sha256'] = hash_gen(app_dic['app_path']) # EXTRACT APPX logger.info('Extracting APPX') app_dic['files'] = unzip( app_dic['app_path'], app_dic['app_dir']) xml_dic = _parse_xml(app_dic['app_dir']) bin_an_dic = _binary_analysis(app_dic) # Saving to db logger.info('Connecting to DB') if rescan: logger.info('Updating Database...') save_or_update('update', app_dic, xml_dic, bin_an_dic) update_scan_timestamp(app_dic['md5']) else: logger.info('Saving to Database') save_or_update('save', app_dic, xml_dic, bin_an_dic) context = get_context_from_analysis(app_dic, xml_dic, bin_an_dic) context['virus_total'] = None if settings.VT_ENABLED: vt = VirusTotal.VirusTotal() context['virus_total'] = vt.get_result( os.path.join(app_dic['app_dir'], app_dic[ 'md5']) + '.appx', app_dic['md5']) template = 'static_analysis/windows_binary_analysis.html' if api: return context else: return render(request, template, context) else: msg = 'File type not supported' if api: return print_n_send_error_response(request, msg, True) else: return print_n_send_error_response(request, msg, False) else: msg = 'Hash match failed or Invalid file extension' if api: return print_n_send_error_response(request, msg, True) else: return print_n_send_error_response(request, msg, False) except Exception as exception: logger.exception('Error Performing Static Analysis') msg = str(exception) exp_doc = exception.__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 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 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)
def dynamic_analyzer(request, checksum, api=False): """Android Dynamic Analyzer Environment.""" logger.info('Creating Dynamic Analysis Environment') try: no_device = False 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) try: identifier = get_device() except Exception: no_device = True if no_device or not identifier: msg = ('Is the android instance running? MobSF cannot' ' find android instance identifier. ' 'Please run an android instance and refresh' ' this page. If this error persists,' ' set ANALYZER_IDENTIFIER in ' f'{get_config_loc()}') return print_n_send_error_response(request, msg, api) env = Environment(identifier) if not env.connect_n_mount(): msg = 'Cannot Connect to ' + identifier return print_n_send_error_response(request, msg, api) version = env.get_android_version() logger.info('Android Version identified as %s', version) xposed_first_run = False if not env.is_mobsfyied(version): msg = ('This Android instance is not MobSfyed/Outdated.\n' 'MobSFying the android runtime environment') logger.warning(msg) if not env.mobsfy_init(): return print_n_send_error_response( request, 'Failed to MobSFy the instance', api) if version < 5: xposed_first_run = True if xposed_first_run: msg = ('Have you MobSFyed the instance before' ' attempting Dynamic Analysis?' ' Install Framework for Xposed.' ' Restart the device and enable' ' all Xposed modules. And finally' ' restart the device once again.') return print_n_send_error_response(request, msg, api) # Clean up previous analysis env.dz_cleanup(checksum) # Configure Web Proxy env.configure_proxy(package) # Supported in Android 5+ env.enable_adb_reverse_tcp(version) # Apply Global Proxy to device env.set_global_proxy(version) # Start Clipboard monitor env.start_clipmon() # Get Screen Resolution screen_width, screen_height = env.get_screen_res() apk_path = Path(settings.UPLD_DIR) / checksum / f'{checksum}.apk' # Install APK status, output = env.install_apk(apk_path.as_posix(), package) if not status: # Unset Proxy env.unset_global_proxy() msg = (f'This APK cannot be installed. Is this APK ' f'compatible the Android VM/Emulator?\n{output}') return print_n_send_error_response(request, msg, api) logger.info('Testing Environment is Ready!') context = { 'screen_witdth': screen_width, 'screen_height': screen_height, 'package': package, 'hash': checksum, 'android_version': version, 'version': settings.MOBSF_VER, 'title': 'Dynamic Analyzer' } template = 'dynamic_analysis/android/dynamic_analyzer.html' if api: return context return render(request, template, context) except Exception: logger.exception('Dynamic Analyzer') return print_n_send_error_response(request, 'Dynamic Analysis Failed.', api)
def static_analyzer_ios(request, api=False): """Module that performs iOS IPA/ZIP Static Analysis.""" try: logger.info('iOS Static Analysis Started') rescan = False if api: file_type = request.POST['scan_type'] checksum = request.POST['hash'] re_scan = request.POST.get('re_scan', 0) filename = request.POST['file_name'] else: file_type = request.GET['type'] checksum = request.GET['checksum'] re_scan = request.GET.get('rescan', 0) filename = request.GET['name'] if re_scan == '1': rescan = True md5_match = re.match('^[0-9a-f]{32}$', checksum) if ((md5_match) and (filename.lower().endswith('.ipa') or filename.lower().endswith('.zip')) and (file_type in ['ipa', 'ios'])): app_dict = {} app_dict['directory'] = Path(settings.BASE_DIR) # BASE DIR app_dict['file_name'] = filename # APP ORIGINAL NAME app_dict['md5_hash'] = checksum # MD5 app_dir = Path(settings.UPLD_DIR) / checksum tools_dir = app_dict[ 'directory'] / 'StaticAnalyzer' / 'tools' / 'ios' tools_dir = tools_dir.as_posix() if file_type == 'ipa': app_dict[ 'app_file'] = app_dict['md5_hash'] + '.ipa' # NEW FILENAME app_dict['app_path'] = app_dir / app_dict['app_file'] app_dict['app_path'] = app_dict['app_path'].as_posix() # DB ipa_db = StaticAnalyzerIOS.objects.filter( MD5=app_dict['md5_hash']) if ipa_db.exists() and not rescan: context = get_context_from_db_entry(ipa_db) else: logger.info('iOS Binary (IPA) Analysis Started') app_dict['app_dir'] = app_dir.as_posix() + '/' app_dict['size'] = str(file_size( app_dict['app_path'])) + 'MB' # FILE SIZE app_dict['sha1'], app_dict['sha256'] = hash_gen( app_dict['app_path']) # SHA1 & SHA256 HASHES logger.info('Extracting IPA') # EXTRACT IPA unzip(app_dict['app_path'], app_dict['app_dir']) # Identify Payload directory dirs = app_dir.glob('**/*') for _dir in dirs: if 'payload' in _dir.as_posix().lower(): app_dict['bin_dir'] = app_dict['app_dir'] / _dir break else: msg = ('IPA is malformed! ' 'MobSF cannot find Payload directory') if api: return print_n_send_error_response( request, msg, True) else: return print_n_send_error_response( request, msg, False) app_dict['bin_dir'] = app_dict['bin_dir'].as_posix() + '/' # Get Files all_files = ios_list_files(app_dict['bin_dir'], app_dict['md5_hash'], True, 'ipa') infoplist_dict = plist_analysis(app_dict['bin_dir'], False) app_dict['appstore'] = app_search(infoplist_dict.get('id')) app_dict['secrets'] = get_plist_secrets( infoplist_dict['plist_xml']) bin_analysis_dict = binary_analysis( app_dict['bin_dir'], tools_dir, app_dict['app_dir'], infoplist_dict.get('bin')) # Get Icon app_dict['icon_found'] = get_icon( app_dict['md5_hash'], app_dict['bin_dir'], infoplist_dict.get('bin')) # IPA URL and Email Extract recon = extract_urls_n_email(app_dict['bin_dir'], all_files['files_long'], bin_analysis_dict['strings']) code_dict = { 'api': {}, 'code_anal': {}, 'urlnfile': recon['urlnfile'], 'domains': recon['domains'], 'emailnfile': recon['emailnfile'], 'firebase': firebase_analysis(recon['urls_list']), } # Saving to DB logger.info('Connecting to DB') if rescan: logger.info('Updating Database...') save_or_update('update', app_dict, infoplist_dict, code_dict, bin_analysis_dict, all_files) update_scan_timestamp(app_dict['md5_hash']) else: logger.info('Saving to Database') save_or_update('save', app_dict, infoplist_dict, code_dict, bin_analysis_dict, all_files) context = get_context_from_analysis( app_dict, infoplist_dict, code_dict, bin_analysis_dict, all_files) context['virus_total'] = None if settings.VT_ENABLED: vt = VirusTotal.VirusTotal() context['virus_total'] = vt.get_result( app_dict['app_path'], app_dict['md5_hash']) context['average_cvss'], context['security_score'] = score( context['binary_analysis']) template = 'static_analysis/ios_binary_analysis.html' if api: return context else: return render(request, template, context) elif file_type == 'ios': ios_zip_db = StaticAnalyzerIOS.objects.filter( MD5=app_dict['md5_hash']) if ios_zip_db.exists() and not rescan: context = get_context_from_db_entry(ios_zip_db) else: logger.info('iOS Source Code Analysis Started') app_dict['app_file'] = app_dict[ 'md5_hash'] + '.zip' # NEW FILENAME app_dict['app_path'] = app_dir / app_dict['app_file'] app_dict['app_path'] = app_dict['app_path'].as_posix() app_dict['app_dir'] = app_dir.as_posix() + '/' # ANALYSIS BEGINS - Already Unzipped app_dict['size'] = str(file_size( app_dict['app_path'])) + 'MB' # FILE SIZE app_dict['sha1'], app_dict['sha256'] = hash_gen( app_dict['app_path']) # SHA1 & SHA256 HASHES all_files = ios_list_files(app_dict['app_dir'], app_dict['md5_hash'], False, 'ios') infoplist_dict = plist_analysis(app_dict['app_dir'], True) app_dict['appstore'] = app_search(infoplist_dict.get('id')) app_dict['secrets'] = get_plist_secrets( infoplist_dict['plist_xml']) code_analysis_dic = ios_source_analysis( app_dict['app_dir']) # Get App Icon app_dict['icon_found'] = get_icon_source( app_dict['md5_hash'], app_dict['app_dir']) # Firebase DB Check code_analysis_dic['firebase'] = firebase_analysis( list(set(code_analysis_dic['urls_list']))) fake_bin_dict = { 'checksec': {}, 'libraries': [], 'bin_code_analysis': {}, 'strings': [], 'bin_info': {}, 'bin_type': code_analysis_dic['source_type'], } # Saving to DB logger.info('Connecting to DB') if rescan: logger.info('Updating Database...') save_or_update('update', app_dict, infoplist_dict, code_analysis_dic, fake_bin_dict, all_files) update_scan_timestamp(app_dict['md5_hash']) else: logger.info('Saving to Database') save_or_update('save', app_dict, infoplist_dict, code_analysis_dic, fake_bin_dict, all_files) context = get_context_from_analysis( app_dict, infoplist_dict, code_analysis_dic, fake_bin_dict, all_files) context['average_cvss'], context['security_score'] = score( context['code_analysis']) template = 'static_analysis/ios_source_analysis.html' if api: return context else: return render(request, template, context) else: msg = 'File Type not supported!' if api: return print_n_send_error_response(request, msg, True) else: return print_n_send_error_response(request, msg, False) 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 exp: logger.exception('Error Performing Static Analysis') 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 generic_compare(request, first_hash: str, second_hash: str, api: bool = False): # This context consists of specific lists and analysis # that is done on the classic ones # it will be filled during the different diff analysis context = { 'title': 'Compare report', 'version': settings.MOBSF_VER, 'first_app': {}, 'second_app': {}, 'urls': {}, 'android_api': {}, 'permissions': {}, 'browsable_activities': {}, 'common_browsable_activities': {}, 'apkid': {}, } static_fields = [ 'md5', 'file_name', 'size', 'icon_found', 'icon_hidden', 'activities', 'services', 'providers', 'receivers', 'exported_count', 'apkid' ] # For now - support only android db_entry = StaticAnalyzerAndroid.objects.filter(MD5=first_hash) db_entry2 = StaticAnalyzerAndroid.objects.filter(MD5=second_hash) if not (db_entry.exists() and db_entry2.exists()): return print_n_send_error_response( request, 'Currently you can only diff/compare android apps. ' 'One of the app has not completed static analysis or' ' they are not both android APK/ZIP.', api, ) # First fetch the already done analysis on each of the apps # We don't want to return this whole context back to the user # because its a lot of data we don't use # it should help the performance I guess first_app = deepcopy(get_context_from_db_entry(db_entry)) second_app = deepcopy(get_context_from_db_entry(db_entry2)) # Second, fill the common static parts that # are missing in the classic analysis for curr_app, db_context in [('first_app', first_app), ('second_app', second_app)]: # format informative title context[curr_app]['name_ver'] = '{0} - {1}'.format( db_context['package_name'], db_context['version_name']) # Fill all the static information for static_attr in static_fields: context[curr_app][static_attr] = db_context[static_attr] # Get only the subject of the cert subject_regex = re.compile(r'Subject: .*') match = subject_regex.search( db_context['certificate_analysis']['certificate_info']) if match: context[curr_app]['cert_subject'] = match.group() else: context[curr_app]['cert_subject'] = 'No subject' # Some preparations so we have some sort of same structures # (urls are lists inside the list which mess things up...) tmp_list = [] for url_obj in db_context['urls']: for url in url_obj['urls']: # urls can mess up the table because they can be really long, # so let's cut them # escape url url = escape(url) tmp_url = url[:70] while len(url) > 70: url = url[70:] tmp_url += '<br />' tmp_url += url[:70] tmp_list.append(tmp_url) db_context['urls'] = list(set(deepcopy(tmp_list))) tmp_list.clear() # apkid check - we do it here just because # its really ugly inside the template # it has a dedicated function because the result # is more complicated then the others... diff_apkid(context) # Third, calculate some diffs for section, is_tuples in [ ('permissions', True), ('android_api', True), ('browsable_activities', True), ('urls', False), ]: if is_tuples: context[section]['common'] = [ (x, y) for (x, y) in first_app[section].items() if x in second_app[section].keys() ] # Only first context[section]['only_first'] = [ (x, y) for (x, y) in first_app[section].items() if x not in second_app[section].keys() ] # Only Second context[section]['only_second'] = [ (x, y) for (x, y) in second_app[section].items() if x not in first_app[section].keys() ] else: context[section]['common'] = [ x for x in first_app[section] if x in second_app[section] ] context[section]['only_first'] = [ x for x in first_app[section] if x not in second_app[section] ] context[section]['only_second'] = [ x for x in second_app[section] if x not in first_app[section] ] diff_browsable_activities(context, first_app, second_app) template = 'static_analysis/compare.html' if api: return context else: return render(request, template, context)
def pdf(request, api=False, jsonres=False): try: if api: checksum = request.POST['hash'] else: checksum = request.GET['md5'] hash_match = re.match('^[0-9a-f]{32}$', checksum) if not hash_match: if api: return {'error': 'Invalid scan hash'} else: return HttpResponse(json.dumps({'md5': 'Invalid scan hash'}), content_type=ctype, status=500) # Do Lookups android_static_db = StaticAnalyzerAndroid.objects.filter(MD5=checksum) ios_static_db = StaticAnalyzerIOS.objects.filter(MD5=checksum) win_static_db = StaticAnalyzerWindows.objects.filter(MD5=checksum) if android_static_db.exists(): context, template = handle_pdf_android(android_static_db) elif ios_static_db.exists(): context, template = handle_pdf_ios(ios_static_db) elif win_static_db.exists(): context, template = handle_pdf_win(win_static_db) else: if api: return {'report': 'Report not Found'} else: return HttpResponse(json.dumps({'report': 'Report not Found'}), content_type=ctype, status=500) # Do VT Scan only on binaries context['virus_total'] = None ext = os.path.splitext(context['file_name'].lower())[1] if settings.VT_ENABLED and ext != '.zip': app_bin = os.path.join(settings.UPLD_DIR, checksum + '/', checksum + ext) vt = VirusTotal.VirusTotal() context['virus_total'] = vt.get_result(app_bin, checksum) # Get Local Base URL proto = 'file://' host_os = 'nix' if platform.system() == 'Windows': proto = 'file:///' host_os = 'windows' context['base_url'] = proto + settings.BASE_DIR context['dwd_dir'] = proto + settings.DWD_DIR context['host_os'] = host_os context['timestamp'] = RecentScansDB.objects.get( MD5=checksum).TIMESTAMP try: if api and jsonres: return {'report_dat': context} else: options = { 'page-size': 'Letter', 'quiet': '', 'enable-local-file-access': '', 'no-collate': '', 'margin-top': '0.50in', 'margin-right': '0.50in', 'margin-bottom': '0.50in', 'margin-left': '0.50in', 'encoding': 'UTF-8', 'custom-header': [ ('Accept-Encoding', 'gzip'), ], 'no-outline': None, } # Added proxy support to wkhtmltopdf proxies, _ = upstream_proxy('https') if proxies['https']: options['proxy'] = proxies['https'] html = template.render(context) pdf_dat = pdfkit.from_string(html, False, options=options) if api: return {'pdf_dat': pdf_dat} return HttpResponse(pdf_dat, content_type='application/pdf') except Exception as exp: logger.exception('Error Generating PDF Report') if api: return { 'error': 'Cannot Generate PDF/JSON', 'err_details': str(exp) } else: err = { 'pdf_error': 'Cannot Generate PDF', 'err_details': str(exp) } return HttpResponse( json.dumps(err), # lgtm [py/stack-trace-exposure] content_type=ctype, status=500) except Exception as exp: logger.exception('Error Generating PDF Report') msg = str(exp) exp = exp.__doc__ if api: return print_n_send_error_response(request, msg, True, exp) else: return print_n_send_error_response(request, msg, False, exp)