From e3ce0cfb77b8acb70c2a143970ebb566664d23d7 Mon Sep 17 00:00:00 2001 From: Ajin Abraham Date: Wed, 13 Nov 2024 20:10:40 -0800 Subject: [PATCH] fix untar permission errors, update ssl pinning scripts, add intent trace and update intent dumper --- .../android/default/ssl_pinning_bypass.js | 77 +++++- .../android/others/detect-ssl-pinning.js | 20 ++ .../android/others/dump-intent.js | 110 ++++++-- .../android/others/ssl-pinning-bypass.js | 125 ++++----- .../android/others/trace-intent.js | 78 ++++++ mobsf/DynamicAnalyzer/views/common/shared.py | 34 ++- mobsf/MobSF/utils.py | 29 +- mobsf/StaticAnalyzer/views/android/apk.py | 259 +++++++++--------- .../views/android/static_analyzer.py | 1 + poetry.lock | 22 +- 10 files changed, 506 insertions(+), 249 deletions(-) create mode 100644 mobsf/DynamicAnalyzer/tools/frida_scripts/android/others/detect-ssl-pinning.js create mode 100644 mobsf/DynamicAnalyzer/tools/frida_scripts/android/others/trace-intent.js diff --git a/mobsf/DynamicAnalyzer/tools/frida_scripts/android/default/ssl_pinning_bypass.js b/mobsf/DynamicAnalyzer/tools/frida_scripts/android/default/ssl_pinning_bypass.js index 7793712af1..bea88e96d7 100644 --- a/mobsf/DynamicAnalyzer/tools/frida_scripts/android/default/ssl_pinning_bypass.js +++ b/mobsf/DynamicAnalyzer/tools/frida_scripts/android/default/ssl_pinning_bypass.js @@ -241,15 +241,84 @@ Java.perform(function() { } catch (err) { send('[SSL Pinning Bypass] Cronet not found'); } - /* Certificate Transparency Bypass - Ajin Abraham - opensecurity.in */ - try{ + /* Boye AbstractVerifier */ + try { + Java.use("ch.boye.httpclientandroidlib.conn.ssl.AbstractVerifier").verify.implementation = function(host, ssl) { + send("[SSL Pinning Bypass] Bypassing Boye AbstractVerifier" + host); + }; + } catch (err) { + send("[SSL Pinning Bypass] Boye AbstractVerifier not found"); + } + /* Appmattus */ + try { + /* Certificate Transparency Bypass Ajin Abraham - opensecurity.in */ Java.use('com.babylon.certificatetransparency.CTInterceptorBuilder').includeHost.overload('java.lang.String').implementation = function(host) { send('[SSL Pinning Bypass] Bypassing Certificate Transparency check'); return this.includeHost('nonexistent.domain'); }; + } catch (err) { + send('[SSL Pinning Bypass] babylon certificatetransparency.CTInterceptorBuilder not found'); + } + try { + Java.use("com.appmattus.certificatetransparency.internal.verifier.CertificateTransparencyInterceptor")["intercept"].implementation = function(a) { + send("[SSL Pinning Bypass] Appmattus Certificate Transparency"); + return a.proceed(a.request()); + }; + } catch (err) { + send("[SSL Pinning Bypass] Appmattus CertificateTransparencyInterceptor not found"); + } + try{ + bypassOkHttp3CertificateTransparency(); } catch (err) { send('[SSL Pinning Bypass] certificatetransparency.CTInterceptorBuilder not found'); } - }, 0); + + +function bypassOkHttp3CertificateTransparency() { + // https://gist.github.com/m-rey/f2a235123908ca42395b6d3c5fe1128e + Java.perform(function () { + var CertificateTransparencyInterceptor = Java.use('com.appmattus.certificatetransparency.internal.verifier.CertificateTransparencyInterceptor'); + var OkHttpClientBuilder = Java.use('okhttp3.OkHttpClient$Builder'); + + CertificateTransparencyInterceptor.intercept.implementation = function (chain) { + var request = chain.request(); + var url = request.url(); + var host = url.host(); + + // Dynamically access the VerificationResult classes + var VerificationResult = Java.use('com.appmattus.certificatetransparency.VerificationResult'); + var VerificationResultSuccessInsecureConnection = Java.use('com.appmattus.certificatetransparency.VerificationResult$Success$InsecureConnection'); + var VerificationResultFailureNoCertificates = Java.use('com.appmattus.certificatetransparency.VerificationResult$Failure$NoCertificates'); + + // Create instances of the desired VerificationResult classes + var success = VerificationResultSuccessInsecureConnection.$new(host); + var failureNoCertificates = VerificationResultFailureNoCertificates.$new(); + + // Bypass certificate transparency verification + var certs = chain.connection().handshake().peerCertificates(); + if (certs.length === 0) { + send('[SSL Pinning Bypass] Certificate transparency bypassed.'); + return failureNoCertificates; + } + + try { + // Proceed with the original request + return chain.proceed(request); + } catch (e) { + // Catch SSLPeerUnverifiedException and return intercepted response + if (e.toString().includes('SSLPeerUnverifiedException')) { + send('[SSL Pinning Bypass] Certificate transparency failed.'); + return failureNoCertificates; + } + throw e; + } + }; + + OkHttpClientBuilder.build.implementation = function () { + // Intercept the OkHttpClient creation + var client = this.build(); + return client; + }; +}); +} \ No newline at end of file diff --git a/mobsf/DynamicAnalyzer/tools/frida_scripts/android/others/detect-ssl-pinning.js b/mobsf/DynamicAnalyzer/tools/frida_scripts/android/others/detect-ssl-pinning.js new file mode 100644 index 0000000000..d5efa7a525 --- /dev/null +++ b/mobsf/DynamicAnalyzer/tools/frida_scripts/android/others/detect-ssl-pinning.js @@ -0,0 +1,20 @@ +try { + var UnverifiedCertError = Java.use('javax.net.ssl.SSLPeerUnverifiedException'); + UnverifiedCertError.$init.implementation = function(str) { + send('Unexpected SSLPeerUnverifiedException occurred'); + try { + var stackTrace = Java.use('java.lang.Thread').currentThread().getStackTrace(); + var exceptionStackIndex = stackTrace.findIndex(stack => stack.getClassName() === "javax.net.ssl.SSLPeerUnverifiedException"); + var callingFunctionStack = stackTrace[exceptionStackIndex + 1]; + var className = callingFunctionStack.getClassName(); + var methodName = callingFunctionStack.getMethodName(); + var callingClass = Java.use(className); + var callingMethod = callingClass[methodName]; + send('SSL exception caused: ' + className + '.' + methodName + '. Patch this method to bypass pinning.'); + if (callingMethod.implementation) { + return; + } + } catch (e) {} + return this.$init(str); + }; +} catch (err) {} diff --git a/mobsf/DynamicAnalyzer/tools/frida_scripts/android/others/dump-intent.js b/mobsf/DynamicAnalyzer/tools/frida_scripts/android/others/dump-intent.js index 189c6dffbf..2fd465e47f 100644 --- a/mobsf/DynamicAnalyzer/tools/frida_scripts/android/others/dump-intent.js +++ b/mobsf/DynamicAnalyzer/tools/frida_scripts/android/others/dump-intent.js @@ -1,21 +1,93 @@ -// https://gist.github.com/bet4it/b62ac2d5bd45b8cb699905fa498baf5e Java.perform(function () { - var act = Java.use("android.app.Activity"); - act.getIntent.overload().implementation = function () { - var intent = this.getIntent() - var cp = intent.getComponent() - send("[Intent Dumper] Starting " + cp.getPackageName() + "/" + cp.getClassName()) - var ext = intent.getExtras(); - if (ext) { - var keys = ext.keySet() - var iterator = keys.iterator() - while (iterator.hasNext()) { - var k = iterator.next().toString() - var v = ext.get(k) - send("\t" + v.getClass().getName()) - send("\t" + k + ' : ' + v.toString()) - } + var Activity = Java.use("android.app.Activity"); + + Activity.getIntent.overload().implementation = function () { + var intent = this.getIntent(); + var component = intent.getComponent(); + + send("[Intent Dumper] Captured Intent for Activity:"); + + // Component (target package and class) + if (component) { + send(" Component:"); + send(" Package: " + component.getPackageName()); + send(" Class: " + component.getClassName()); + } else { + send(" Component: None"); } - return intent; - }; - }) \ No newline at end of file + + // Action + var action = intent.getAction(); + send(" Action: " + (action ? action : "None")); + + // Data URI + var dataUri = intent.getDataString(); + send(" Data URI: " + (dataUri ? dataUri : "None")); + + // Flags + var flags = intent.getFlags(); + send(" Flags: " + flags); + + // Dumping extras in the Intent + var extras = intent.getExtras(); + if (extras) { + send(" Extras:"); + var iterator = extras.keySet().iterator(); + while (iterator.hasNext()) { + var key = iterator.next(); + var value = extras.get(key); + if (value !== null) { + send(" " + key + " (" + value.getClass().getName() + "): " + valueToString(value)); + } + } + } else { + send(" Extras: None"); + } + + return intent; + }; + + // Helper function to convert intent extras to a readable string + function valueToString(value) { + var valueType = value.getClass().getName(); + + if (valueType === "android.os.Bundle") { + return bundleToString(Java.cast(value, Java.use("android.os.Bundle"))); + } else if (valueType === "java.lang.String") { + return '"' + value + '"'; + } else if (valueType === "java.lang.Integer" || valueType === "java.lang.Float" || valueType === "java.lang.Boolean") { + return value.toString(); + } else if (valueType === "java.util.ArrayList") { + return arrayListToString(Java.cast(value, Java.use("java.util.ArrayList"))); + } else { + send("Unsupported extra type for key. Type: " + valueType); + return value.toString(); + } + } + + // Function to handle nested Bundles + function bundleToString(bundle) { + var result = "{"; + var iterator = bundle.keySet().iterator(); + while (iterator.hasNext()) { + var key = iterator.next(); + var value = bundle.get(key); + result += key + ": " + (value !== null ? valueToString(value) : "null") + ", "; + } + result = result.slice(0, -2); // Remove trailing comma and space + result += "}"; + return result; + } + + // Function to handle ArrayLists (if any) + function arrayListToString(arrayList) { + var result = "["; + for (var i = 0; i < arrayList.size(); i++) { + var item = arrayList.get(i); + result += valueToString(item) + ", "; + } + result = result.slice(0, -2); // Remove trailing comma and space + result += "]"; + return result; + } +}); diff --git a/mobsf/DynamicAnalyzer/tools/frida_scripts/android/others/ssl-pinning-bypass.js b/mobsf/DynamicAnalyzer/tools/frida_scripts/android/others/ssl-pinning-bypass.js index 42f380076c..0b97998426 100644 --- a/mobsf/DynamicAnalyzer/tools/frida_scripts/android/others/ssl-pinning-bypass.js +++ b/mobsf/DynamicAnalyzer/tools/frida_scripts/android/others/ssl-pinning-bypass.js @@ -720,69 +720,70 @@ function dynamicPatching() { return null; } } - try { - var UnverifiedCertError = Java.use('javax.net.ssl.SSLPeerUnverifiedException'); - UnverifiedCertError.$init.implementation = function(str) { - console.log('[!] Unexpected SSLPeerUnverifiedException occurred, trying to patch it dynamically...!'); - try { - var stackTrace = Java.use('java.lang.Thread').currentThread().getStackTrace(); - var exceptionStackIndex = stackTrace.findIndex(stack => stack.getClassName() === "javax.net.ssl.SSLPeerUnverifiedException"); - var callingFunctionStack = stackTrace[exceptionStackIndex + 1]; - var className = callingFunctionStack.getClassName(); - var methodName = callingFunctionStack.getMethodName(); - var callingClass = Java.use(className); - var callingMethod = callingClass[methodName]; - console.log('[!] Attempting to bypass uncommon SSL Pinning method on: ' + className + '.' + methodName + '!'); - if (callingMethod.implementation) { - return; - } - var returnTypeName = callingMethod.returnType.type; - callingMethod.implementation = function() { - rudimentaryFix(returnTypeName); - }; - } catch (e) { - if (String(e).includes(".overload")) { - var splittedList = String(e).split(".overload"); - for (let i = 2; i < splittedList.length; i++) { - var extractedOverload = splittedList[i].trim().split("(")[1].slice(0, -1).replaceAll("'", ""); - if (extractedOverload.includes(",")) { - var argList = extractedOverload.split(", "); - console.log('[!] Attempting overload of ' + className + '.' + methodName + ' with arguments: ' + extractedOverload + '!'); - if (argList.length == 2) { - callingMethod.overload(argList[0], argList[1]).implementation = function(a, b) { - rudimentaryFix(returnTypeName); - } - } else if (argNum == 3) { - callingMethod.overload(argList[0], argList[1], argList[2]).implementation = function(a, b, c) { - rudimentaryFix(returnTypeName); - } - } else if (argNum == 4) { - callingMethod.overload(argList[0], argList[1], argList[2], argList[3]).implementation = function(a, b, c, d) { - rudimentaryFix(returnTypeName); - } - } else if (argNum == 5) { - callingMethod.overload(argList[0], argList[1], argList[2], argList[3], argList[4]).implementation = function(a, b, c, d, e) { - rudimentaryFix(returnTypeName); - } - } else if (argNum == 6) { - callingMethod.overload(argList[0], argList[1], argList[2], argList[3], argList[4], argList[5]).implementation = function(a, b, c, d, e, f) { - rudimentaryFix(returnTypeName); - } - } - } else { - callingMethod.overload(extractedOverload).implementation = function(a) { - rudimentaryFix(returnTypeName); - } - } - } - } else { - console.log('[-] Failed to dynamically patch SSLPeerUnverifiedException ' + e + '!'); - } - } - return this.$init(str); - }; - } catch (err) {} + // try { + // var UnverifiedCertError = Java.use('javax.net.ssl.SSLPeerUnverifiedException'); + // UnverifiedCertError.$init.implementation = function(str) { + // console.log('[!] Unexpected SSLPeerUnverifiedException occurred, trying to patch it dynamically...!'); + // try { + // var stackTrace = Java.use('java.lang.Thread').currentThread().getStackTrace(); + // var exceptionStackIndex = stackTrace.findIndex(stack => stack.getClassName() === "javax.net.ssl.SSLPeerUnverifiedException"); + // var callingFunctionStack = stackTrace[exceptionStackIndex + 1]; + // var className = callingFunctionStack.getClassName(); + // var methodName = callingFunctionStack.getMethodName(); + // var callingClass = Java.use(className); + // var callingMethod = callingClass[methodName]; + // console.log('[!] Attempting to bypass uncommon SSL Pinning method on: ' + className + '.' + methodName + '!'); + // if (callingMethod.implementation) { + // return; + // } + // var returnTypeName = callingMethod.returnType.type; + // callingMethod.implementation = function() { + // rudimentaryFix(returnTypeName); + // }; + // } catch (e) { + // if (String(e).includes(".overload")) { + // var splittedList = String(e).split(".overload"); + // for (let i = 2; i < splittedList.length; i++) { + // var extractedOverload = splittedList[i].trim().split("(")[1].slice(0, -1).replaceAll("'", ""); + // if (extractedOverload.includes(",")) { + // var argList = extractedOverload.split(", "); + // console.log('[!] Attempting overload of ' + className + '.' + methodName + ' with arguments: ' + extractedOverload + '!'); + // if (argList.length == 2) { + // callingMethod.overload(argList[0], argList[1]).implementation = function(a, b) { + // rudimentaryFix(returnTypeName); + // } + // } else if (argNum == 3) { + // callingMethod.overload(argList[0], argList[1], argList[2]).implementation = function(a, b, c) { + // rudimentaryFix(returnTypeName); + // } + // } else if (argNum == 4) { + // callingMethod.overload(argList[0], argList[1], argList[2], argList[3]).implementation = function(a, b, c, d) { + // rudimentaryFix(returnTypeName); + // } + // } else if (argNum == 5) { + // callingMethod.overload(argList[0], argList[1], argList[2], argList[3], argList[4]).implementation = function(a, b, c, d, e) { + // rudimentaryFix(returnTypeName); + // } + // } else if (argNum == 6) { + // callingMethod.overload(argList[0], argList[1], argList[2], argList[3], argList[4], argList[5]).implementation = function(a, b, c, d, e, f) { + // rudimentaryFix(returnTypeName); + // } + // } + // } else { + // callingMethod.overload(extractedOverload).implementation = function(a) { + // rudimentaryFix(returnTypeName); + // } + // } + // } + // } else { + // console.log('[-] Failed to dynamically patch SSLPeerUnverifiedException ' + e + '!'); + // } + // } + // return this.$init(str); + // }; + // } catch (err) {} } + setTimeout(function() { Java.perform(function() { var X509TrustManager = Java.use('javax.net.ssl.X509TrustManager'); diff --git a/mobsf/DynamicAnalyzer/tools/frida_scripts/android/others/trace-intent.js b/mobsf/DynamicAnalyzer/tools/frida_scripts/android/others/trace-intent.js new file mode 100644 index 0000000000..5495153105 --- /dev/null +++ b/mobsf/DynamicAnalyzer/tools/frida_scripts/android/others/trace-intent.js @@ -0,0 +1,78 @@ +Java.perform(function () { + // Hook the startActivity method in the Activity class + var Activity = Java.use("android.app.Activity"); + + Activity.startActivity.overload("android.content.Intent").implementation = function (intent) { + send("Intercepted startActivity with Intent:"); + + // Dump the Intent details + dumpIntent(intent); + + // Call the original startActivity method to ensure normal behavior + this.startActivity(intent); + }; + + // Function to dump intent details + function dumpIntent(intent) { + // Action + var action = intent.getAction(); + send(" Action: " + (action ? action : "None")); + + // Data URI + var dataUri = intent.getDataString(); + send(" Data URI: " + (dataUri ? dataUri : "None")); + + // Component (target package and class) + var component = intent.getComponent(); + if (component) { + send(" Component:"); + send(" Package: " + component.getPackageName()); + send(" Class: " + component.getClassName()); + } else { + send(" Component: None"); + } + + // Flags + var flags = intent.getFlags(); + send(" Flags: " + flags); + + // Extras + var extras = intent.getExtras(); + if (extras) { + send(" Extras:"); + var iterator = extras.keySet().iterator(); + while (iterator.hasNext()) { + var key = iterator.next(); + var value = extras.get(key); + if (value !== null) { + send(" " + key + ": " + valueToString(value)); + } + } + } else { + send(" Extras: None"); + } + } + + // Helper function to convert intent extras to string for logging + function valueToString(value) { + // Check if the value is a Bundle and handle it accordingly + if (value.getClass().getName() === "android.os.Bundle") { + return bundleToString(Java.cast(value, Java.use("android.os.Bundle"))); + } + return value.toString(); + } + + // Function to handle nested Bundles (if any) + function bundleToString(bundle) { + var result = "{"; + var iterator = bundle.keySet().iterator(); + while (iterator.hasNext()) { + var key = iterator.next(); + var value = bundle.get(key); + result += key + ": " + (value !== null ? value.toString() : "null") + ", "; + } + result = result.slice(0, -2); // Remove trailing comma and space + result += "}"; + return result; + } +}); diff --git a/mobsf/DynamicAnalyzer/views/common/shared.py b/mobsf/DynamicAnalyzer/views/common/shared.py index bfc8d9875b..a87edfb2e7 100644 --- a/mobsf/DynamicAnalyzer/views/common/shared.py +++ b/mobsf/DynamicAnalyzer/views/common/shared.py @@ -3,6 +3,7 @@ import logging import os import re +import errno import json import tarfile import shutil @@ -54,18 +55,35 @@ def safe_paths(tar_meta): yield fh +def onerror(func, path, exc_info): + _, exc, _ = exc_info + if exc.errno == errno.EACCES: # Permission error + try: + os.chmod(path, 0o777) + func(path) + except Exception: + pass + elif exc.errno == errno.ENOTEMPTY: # Directory not empty + try: + func(path) + except Exception: + pass + else: + raise + + def untar_files(tar_loc, untar_dir): """Untar files.""" logger.info('Extracting Tar files') - # Extract Device Data - if not tar_loc.exists(): - return False - if untar_dir.exists(): - # fix for permission errors - shutil.rmtree(untar_dir) - else: - os.makedirs(untar_dir) try: + # Extract Device Data + if not tar_loc.exists(): + return False + if untar_dir.exists(): + # fix for permission errors + shutil.rmtree(untar_dir, onerror=onerror) + else: + os.makedirs(untar_dir) with tarfile.open(tar_loc.as_posix(), errorlevel=1) as tar: def is_within_directory(directory, target): diff --git a/mobsf/MobSF/utils.py b/mobsf/MobSF/utils.py index 58f85602c0..e4b82e0e66 100755 --- a/mobsf/MobSF/utils.py +++ b/mobsf/MobSF/utils.py @@ -22,6 +22,10 @@ import threading from urllib.parse import urlparse from pathlib import Path +from concurrent.futures import ( + ThreadPoolExecutor, + TimeoutError as ThreadPoolTimeoutError, +) from packaging.version import Version @@ -948,24 +952,13 @@ class TaskTimeoutError(Exception): def run_with_timeout(func, limit, *args, **kwargs): - def run_func(result, *args, **kwargs): - result.append(func(*args, **kwargs)) - - result = [] - thread = threading.Thread( - target=run_func, - args=(result, *args), - kwargs=kwargs) - thread.start() - thread.join(limit) - - if thread.is_alive(): - msg = (f'function <{func.__name__}> ' - f'timed out after {limit} seconds') - raise TaskTimeoutError(msg) - if result: - return result[0] - return None + with ThreadPoolExecutor(max_workers=1) as executor: + future = executor.submit(func, *args, **kwargs) + try: + return future.result(timeout=limit) + except ThreadPoolTimeoutError: + msg = f'function <{func.__name__}> timed out after {limit} seconds' + raise TaskTimeoutError(msg) def set_permissions(path): diff --git a/mobsf/StaticAnalyzer/views/android/apk.py b/mobsf/StaticAnalyzer/views/android/apk.py index e3b77af257..6867205b8b 100644 --- a/mobsf/StaticAnalyzer/views/android/apk.py +++ b/mobsf/StaticAnalyzer/views/android/apk.py @@ -134,132 +134,122 @@ def print_scan_subject(checksum, app_dic, man_data): append_scan_status(checksum, msg) -def apk_analysis(request, app_dic, rescan, api): - """APK Analysis.""" - checksum = app_dic['md5'] - db_entry = StaticAnalyzerAndroid.objects.filter(MD5=checksum) - if db_entry.exists() and not rescan: - context = get_context_from_db_entry(db_entry) - else: - if not has_permission(request, Permissions.SCAN, api): - return print_n_send_error_response( - request, - 'Permission Denied', - False) - # ANALYSIS BEGINS - append_scan_status(checksum, 'init') - initialize_app_dic(checksum, app_dic, 'apk') - msg = 'Extracting APK' - logger.info(msg) +def apk_analysis_task(checksum, app_dic, rescan): + append_scan_status(checksum, 'init') + initialize_app_dic(checksum, app_dic, 'apk') + msg = 'Extracting APK' + logger.info(msg) + append_scan_status(checksum, msg) + app_dic['files'] = unzip( + checksum, + 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' + logger.error(msg) append_scan_status(checksum, msg) - app_dic['files'] = unzip( - checksum, - 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' - logger.error(msg) - append_scan_status(checksum, msg) - return print_n_send_error_response( - request, - msg, - api) - app_dic['zipped'] = 'apk' - app_dic['certz'] = get_hardcoded_cert_keystore( - checksum, - app_dic['files']) - # Parse APK with Androguard - andro_apk = parse_apk( - checksum, - app_dic['app_path']) - # Manifest Data - man_data, man_analysis = get_manifest_data( - checksum, - app_dic, - andro_apk) - # Get App name - app_dic['real_name'] = get_app_name( - andro_apk, - app_dic['app_dir'], - True) - # Print scan subject - print_scan_subject(checksum, app_dic, man_data) - app_dic['playstore'] = get_app_details( - checksum, - man_data['packagename']) - # Malware Permission check - mal_perms = permissions.check_malware_permission( - checksum, - man_data['perm']) - man_analysis['malware_permissions'] = mal_perms - # Get icon - # apktool should run before this - get_icon_apk(andro_apk, app_dic) - elf_dict = library_analysis( - checksum, - app_dic['app_dir'], - 'elf') - cert_dic = cert_info( - andro_apk, - app_dic, - man_data) - apkid_results = apkid.apkid_analysis( - checksum, - app_dic['app_path']) - trackers = Trackers.Trackers( - checksum, - app_dic['app_dir'], - app_dic['tools_dir']).get_trackers() - apk_2_java( - checksum, - app_dic['app_path'], - app_dic['app_dir'], - settings.DOWNLOADED_TOOLS_DIR) - dex_2_smali( - checksum, - app_dic['app_dir'], - app_dic['tools_dir']) - code_an_dic = code_analysis( - checksum, - app_dic['app_dir'], - app_dic['zipped'], - app_dic['manifest_file'], - man_data['perm']) - behaviour_an = behaviour_analysis.analyze( - checksum, - app_dic['app_dir'], - app_dic['zipped']) - # Get the strings and metadata - get_strings_metadata( - checksum, - andro_apk, - app_dic['app_dir'], - elf_dict['elf_strings'], - app_dic['zipped'], - ['.java'], - code_an_dic) - # Firebase DB Check - code_an_dic['firebase'] = firebase_analysis( - checksum, - code_an_dic) - # Domain Extraction and Malware Check - code_an_dic['domains'] = MalwareDomainCheck().scan( - checksum, - code_an_dic['urls_list']) - context = save_get_ctx( - app_dic, - man_data, - man_analysis, - code_an_dic, - cert_dic, - elf_dict['elf_analysis'], - apkid_results, - behaviour_an, - trackers, - rescan, - ) + return None, msg + app_dic['zipped'] = 'apk' + app_dic['certz'] = get_hardcoded_cert_keystore( + checksum, + app_dic['files']) + # Parse APK with Androguard + andro_apk = parse_apk( + checksum, + app_dic['app_path']) + # Manifest Data + man_data, man_analysis = get_manifest_data( + checksum, + app_dic, + andro_apk) + # Get App name + app_dic['real_name'] = get_app_name( + andro_apk, + app_dic['app_dir'], + True) + # Print scan subject + print_scan_subject(checksum, app_dic, man_data) + app_dic['playstore'] = get_app_details( + checksum, + man_data['packagename']) + # Malware Permission check + mal_perms = permissions.check_malware_permission( + checksum, + man_data['perm']) + man_analysis['malware_permissions'] = mal_perms + # Get icon + # apktool should run before this + get_icon_apk(andro_apk, app_dic) + elf_dict = library_analysis( + checksum, + app_dic['app_dir'], + 'elf') + cert_dic = cert_info( + andro_apk, + app_dic, + man_data) + apkid_results = apkid.apkid_analysis( + checksum, + app_dic['app_path']) + trackers = Trackers.Trackers( + checksum, + app_dic['app_dir'], + app_dic['tools_dir']).get_trackers() + apk_2_java( + checksum, + app_dic['app_path'], + app_dic['app_dir'], + settings.DOWNLOADED_TOOLS_DIR) + dex_2_smali( + checksum, + app_dic['app_dir'], + app_dic['tools_dir']) + code_an_dic = code_analysis( + checksum, + app_dic['app_dir'], + app_dic['zipped'], + app_dic['manifest_file'], + man_data['perm']) + behaviour_an = behaviour_analysis.analyze( + checksum, + app_dic['app_dir'], + app_dic['zipped']) + # Get the strings and metadata + get_strings_metadata( + checksum, + andro_apk, + app_dic['app_dir'], + elf_dict['elf_strings'], + app_dic['zipped'], + ['.java'], + code_an_dic) + # Firebase DB Check + code_an_dic['firebase'] = firebase_analysis( + checksum, + code_an_dic) + # Domain Extraction and Malware Check + code_an_dic['domains'] = MalwareDomainCheck().scan( + checksum, + code_an_dic['urls_list']) + context = save_get_ctx( + app_dic, + man_data, + man_analysis, + code_an_dic, + cert_dic, + elf_dict['elf_analysis'], + apkid_results, + behaviour_an, + trackers, + rescan, + ) + return context, None + + +def generate_dynamic_context(request, app_dic, checksum, context, api): + """Generate Response.""" context['appsec'] = get_android_dashboard(context, True) context['average_cvss'] = get_avg_cvss(context['code_analysis']) logcat_file = Path(app_dic['app_dir']) / 'logcat.txt' @@ -267,12 +257,27 @@ def apk_analysis(request, app_dic, rescan, api): context['virus_total'] = None if settings.VT_ENABLED: vt = VirusTotal.VirusTotal(checksum) - context['virus_total'] = vt.get_result( - app_dic['app_path']) + context['virus_total'] = vt.get_result(app_dic['app_path']) template = 'static_analysis/android_binary_analysis.html' return context if api else render(request, template, context) +def apk_analysis(request, app_dic, rescan, api): + """APK Analysis.""" + checksum = app_dic['md5'] + db_entry = StaticAnalyzerAndroid.objects.filter(MD5=checksum) + if db_entry.exists() and not rescan: + context = get_context_from_db_entry(db_entry) + return generate_dynamic_context(request, app_dic, checksum, context, api) + else: + if not has_permission(request, Permissions.SCAN, api): + return print_n_send_error_response(request, 'Permission Denied', False) + context, err = apk_analysis_task(checksum, app_dic, rescan) + if err: + return print_n_send_error_response(request, err, api) + return generate_dynamic_context(request, app_dic, checksum, context, api) + + def src_analysis(request, app_dic, rescan, api): """Source Code Analysis.""" checksum = app_dic['md5'] diff --git a/mobsf/StaticAnalyzer/views/android/static_analyzer.py b/mobsf/StaticAnalyzer/views/android/static_analyzer.py index 9621cdd79e..4b5f26a73a 100755 --- a/mobsf/StaticAnalyzer/views/android/static_analyzer.py +++ b/mobsf/StaticAnalyzer/views/android/static_analyzer.py @@ -108,6 +108,7 @@ def static_analyzer(request, checksum, api=False): if not handle_aab(app_dic): raise Exception('Invalid AAB File') typ = APK_TYPE + # Route to respective analysis if typ == APK_TYPE: return apk_analysis(request, app_dic, rescan, api) elif typ == 'jar': diff --git a/poetry.lock b/poetry.lock index 9c110b3efd..dae3e3586b 100644 --- a/poetry.lock +++ b/poetry.lock @@ -856,13 +856,13 @@ files = [ [[package]] name = "googleapis-common-protos" -version = "1.65.0" +version = "1.66.0" description = "Common protobufs used in Google APIs" optional = false python-versions = ">=3.7" files = [ - {file = "googleapis_common_protos-1.65.0-py2.py3-none-any.whl", hash = "sha256:2972e6c496f435b92590fd54045060867f3fe9be2c82ab148fc8885035479a63"}, - {file = "googleapis_common_protos-1.65.0.tar.gz", hash = "sha256:334a29d07cddc3aa01dee4988f9afd9b2916ee2ff49d6b757155dc0d197852c0"}, + {file = "googleapis_common_protos-1.66.0-py2.py3-none-any.whl", hash = "sha256:d7abcd75fabb2e0ec9f74466401f6c119a0b498e27370e9be4c94cb7e382b8ed"}, + {file = "googleapis_common_protos-1.66.0.tar.gz", hash = "sha256:c3e7b33d15fdca5374cc0a7346dd92ffa847425cc4ea941d970f13680052ec8c"}, ] [package.dependencies] @@ -1099,13 +1099,13 @@ pyasn1 = ">=0.4.6" [[package]] name = "libsast" -version = "3.1.0" +version = "3.1.1" description = "A generic SAST library built on top of semgrep and regex" optional = false python-versions = "<4.0,>=3.8" files = [ - {file = "libsast-3.1.0-py3-none-any.whl", hash = "sha256:1d4e35bba35d886e4d58d79363bc6d4e26a7e28f095da01968d016af64960804"}, - {file = "libsast-3.1.0.tar.gz", hash = "sha256:030f87f404cd9901e24c55adb9e501874aae8af0e7efcf2cf113fb2c64293b84"}, + {file = "libsast-3.1.1-py3-none-any.whl", hash = "sha256:34e1fc8de2cb58c83bd54ad2ea5f8a3ebca7ec761d93e3494beafefe2b58c97a"}, + {file = "libsast-3.1.1.tar.gz", hash = "sha256:0356fe014e9ea608a10ce6ffd0b8de20a9c24f2287c149fac7a074f13efd8bf0"}, ] [package.dependencies] @@ -1805,12 +1805,12 @@ files = [ [[package]] name = "peewee" -version = "3.17.7" +version = "3.17.8" description = "a little orm" optional = false python-versions = "*" files = [ - {file = "peewee-3.17.7.tar.gz", hash = "sha256:6aefc700bd530fc6ac23fa19c9c5b47041751d92985b799169c8e318e97eabaa"}, + {file = "peewee-3.17.8.tar.gz", hash = "sha256:ce1d05db3438830b989a1b9d0d0aa4e7f6134d5f6fd57686eeaa26a3e6485a8c"}, ] [[package]] @@ -2630,13 +2630,13 @@ tests = ["coverage[toml] (>=5.0.2)", "pytest"] [[package]] name = "setuptools" -version = "75.4.0" +version = "75.5.0" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = false python-versions = ">=3.9" files = [ - {file = "setuptools-75.4.0-py3-none-any.whl", hash = "sha256:b3c5d862f98500b06ffdf7cc4499b48c46c317d8d56cb30b5c8bce4d88f5c216"}, - {file = "setuptools-75.4.0.tar.gz", hash = "sha256:1dc484f5cf56fd3fe7216d7b8df820802e7246cfb534a1db2aa64f14fcb9cdcb"}, + {file = "setuptools-75.5.0-py3-none-any.whl", hash = "sha256:87cb777c3b96d638ca02031192d40390e0ad97737e27b6b4fa831bea86f2f829"}, + {file = "setuptools-75.5.0.tar.gz", hash = "sha256:5c4ccb41111392671f02bb5f8436dfc5a9a7185e80500531b133f5775c4163ef"}, ] [package.extras]