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 instrument(request, api=False): """Instrument app with frida.""" data = {} try: logger.info('Starting Instrumentation') md5_hash = request.POST['hash'] default_hooks = request.POST['default_hooks'] auxiliary_hooks = request.POST['auxiliary_hooks'] code = request.POST['frida_code'] # Fill extras extras = {} class_name = request.POST.get('class_name') if class_name: extras['class_name'] = class_name.strip() class_search = request.POST.get('class_search') if class_search: extras['class_search'] = class_search.strip() cls_trace = request.POST.get('class_trace') if cls_trace: extras['class_trace'] = cls_trace.strip() if (is_attack_pattern(default_hooks) or not is_md5(md5_hash)): return invalid_params(api) package = get_package_name(md5_hash) if not package: return invalid_params(api) frida_obj = Frida(md5_hash, package, default_hooks.split(','), auxiliary_hooks.split(','), extras, code) trd = threading.Thread(target=frida_obj.connect) trd.daemon = True trd.start() data = {'status': 'ok'} except Exception as exp: logger.exception('Instrumentation failed') data = {'status': 'failed', 'message': str(exp)} return send_response(data, api)
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') 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') 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 download_data(request, api=False): """Download Application Data from Device.""" logger.info('Downloading app data') data = {} try: env = Environment() md5_hash = request.POST['hash'] if not is_md5(md5_hash): return invalid_params(api) package = get_package_name(md5_hash) if not package: data = { 'status': 'failed', 'message': 'App details not found in database' } return send_response(data, api) apk_dir = os.path.join(settings.UPLD_DIR, md5_hash + '/') stop_httptools(settings.PROXY_PORT) files_loc = '/data/local/' logger.info('Archiving files created by app') env.adb_command([ 'tar', '-cvf', files_loc + package + '.tar', '/data/data/' + package + '/' ], True) logger.info('Downloading Archive') env.adb_command( ['pull', files_loc + package + '.tar', apk_dir + package + '.tar']) logger.info('Stopping ADB server') env.adb_command(['kill-server']) data = {'status': 'ok'} except Exception as exp: logger.exception('Downloading application data') data = {'status': 'failed', 'message': str(exp)} return send_response(data, 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'], '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, 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 get_component(request): """Get Android Component.""" data = {} try: env = Environment() comp = request.POST['component'] bin_hash = request.POST['hash'] if is_attack_pattern(comp) or not is_md5(bin_hash): return invalid_params() comp = env.android_component(bin_hash, comp) data = {'status': 'ok', 'message': comp} except Exception as exp: logger.exception('Getting Android Component') data = {'status': 'failed', 'message': str(exp)} return send_response(data)
def activity_tester(request, api=False): """Exported & non exported activity Tester.""" data = {} try: env = Environment() test = request.POST['test'] md5_hash = request.POST['hash'] if not is_md5(md5_hash): return invalid_params(api) app_dir = os.path.join(settings.UPLD_DIR, md5_hash + '/') screen_dir = os.path.join(app_dir, 'screenshots-apk/') if not os.path.exists(screen_dir): os.makedirs(screen_dir) static_android_db = StaticAnalyzerAndroid.objects.get(MD5=md5_hash) package = static_android_db.PACKAGE_NAME iden = '' if test == 'exported': iden = 'Exported ' logger.info('Exported activity tester') activities = python_list(static_android_db.EXPORTED_ACTIVITIES) else: logger.info('Activity tester') activities = python_list(static_android_db.ACTIVITIES) logger.info('Fetching %sactivities for %s', iden, package) if not activities: msg = 'No {}Activites found'.format(iden) logger.info(msg) data = {'status': 'failed', 'message': msg} return send_response(data, api) act_no = 0 logger.info('Starting %sActivity Tester...', iden) logger.info('%s %sActivities Identified', str(len(activities)), iden) for activity in activities: act_no += 1 logger.info('Launching %sActivity - %s. %s', iden, str(act_no), activity) if test == 'exported': file_iden = 'expact' else: file_iden = 'act' outfile = ('{}{}-{}.png'.format(screen_dir, file_iden, act_no)) env.launch_n_capture(package, activity, outfile) data = {'status': 'ok'} except Exception as exp: logger.exception('%sActivity tester', iden) data = {'status': 'failed', 'message': str(exp)} return send_response(data, api)
def collect_logs(request, api=False): """Collecting Data and Cleanup.""" logger.info('Collecting Data and Cleaning Up') data = {} try: env = Environment() md5_hash = request.POST['hash'] if not is_md5(md5_hash): return invalid_params(api) package = get_package_name(md5_hash) if not package: data = { 'status': 'failed', 'message': 'App details not found in database' } return send_response(data, api) apk_dir = os.path.join(settings.UPLD_DIR, md5_hash + '/') lout = os.path.join(apk_dir, 'logcat.txt') dout = os.path.join(apk_dir, 'dump.txt') logger.info('Downloading logcat logs') logcat = env.adb_command(['logcat', '-d', package + ':V', '*:*']) with open(lout, 'wb') as flip: flip.write(logcat) logger.info('Downloading dumpsys logs') dumpsys = env.adb_command(['dumpsys'], True) with open(dout, 'wb') as flip: flip.write(dumpsys) if env.get_android_version() < 5: download_xposed_log(apk_dir) env.adb_command(['am', 'force-stop', package], True) logger.info('Stopping app') # Unset Global Proxy env.unset_global_proxy() data = {'status': 'ok'} except Exception as exp: logger.exception('Data Collection & Clean Up failed') data = {'status': 'failed', 'message': str(exp)} return send_response(data, api)
def take_screenshot(request, api=False): """Take Screenshot.""" logger.info('Taking screenshot') data = {} try: env = Environment() bin_hash = request.POST['hash'] if not is_md5(bin_hash): return invalid_params(api) data = {} rand_int = random.randint(1, 1000000) screen_dir = os.path.join(settings.UPLD_DIR, bin_hash + '/screenshots-apk/') if not os.path.exists(screen_dir): os.makedirs(screen_dir) outile = '{}screenshot-{}.png'.format(screen_dir, str(rand_int)) env.screen_shot(outile) logger.info('Screenshot captured') data = {'status': 'ok'} except Exception as exp: logger.exception('Taking screenshot') data = {'status': 'failed', 'message': str(exp)} return send_response(data, api)
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 MobSF/settings.py') 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)