From d3b02143fe700f5ec1568516e15387496c65bff7 Mon Sep 17 00:00:00 2001 From: Ajin Abraham Date: Thu, 7 Dec 2023 02:14:45 +0000 Subject: [PATCH] [HOTFIX] RPC hook suggestions + Bug Fix (#2301) * String compare script improvements * Fix iOS Frida script bugs * Added RPC helpers for hook suggestion (TODO:Expose to UI) * Code QA --- .../android/auxiliary/string_compare.js | 40 +++- .../android/default/dump_clipboard.js | 60 +++--- .../android/others/stop-app-terminate.js | 74 ++++---- .../ios/default/jailbreak_bypass.js | 178 +++++++++--------- .../tools/frida_scripts/ios/dump/data-dir.js | 2 +- .../tools/frida_scripts/ios/dump/nslog.js | 9 +- .../frida_scripts/ios/dump/nsuserdefaults.js | 2 +- .../views/android/frida_core.py | 135 ++++++++----- .../views/android/tests_frida.py | 38 ---- mobsf/DynamicAnalyzer/views/common/frida.py | 51 +++++ .../views/ios/corellium_apis.py | 21 ++- .../views/ios/corellium_instance.py | 6 +- mobsf/DynamicAnalyzer/views/ios/frida_core.py | 70 ++++++- mobsf/MobSF/init.py | 2 +- mobsf/MobSF/urls.py | 2 +- mobsf/MobSF/utils.py | 2 + mobsf/MobSF/views/api/api_dynamic_analysis.py | 2 +- mobsf/static/others/css/terminal.css | 4 - mobsf/static/terminal/terminal.css | 79 -------- .../android/dynamic_analyzer.html | 11 +- .../dynamic_analysis/android/frida_logs.html | 2 +- .../ios/dynamic_analysis.html | 5 +- .../ios/dynamic_analyzer.html | 11 +- pyproject.toml | 2 +- 24 files changed, 446 insertions(+), 362 deletions(-) delete mode 100644 mobsf/static/terminal/terminal.css diff --git a/mobsf/DynamicAnalyzer/tools/frida_scripts/android/auxiliary/string_compare.js b/mobsf/DynamicAnalyzer/tools/frida_scripts/android/auxiliary/string_compare.js index 81f9aa98ca..69a859e699 100644 --- a/mobsf/DynamicAnalyzer/tools/frida_scripts/android/auxiliary/string_compare.js +++ b/mobsf/DynamicAnalyzer/tools/frida_scripts/android/auxiliary/string_compare.js @@ -1,15 +1,37 @@ -//https://github.com/iddoeldor/frida-snippets#reveal-native-methods //String comparison Java.perform(function () { send('[AUXILIARY] [String Compare] capturing all string comparisons') - var str = Java.use('java.lang.String'), objectClass = 'java.lang.Object'; - str.equals.overload(objectClass).implementation = function (obj) { - var response = str.equals.overload(objectClass).call(this, obj); - if (obj) { - if (obj.toString().length > 5) { - send('[AUXILIARY] [String Compare] ' + str.toString.call(this) + ' == ' + obj.toString() + ' ? ' + response); - } + let Exception = Java.use('java.lang.Exception'); + let javaString = Java.use('java.lang.String') + let objectClass = 'java.lang.Object'; + var skiplist = ['android.app.SystemServiceRegistry.getSystemService'] + javaString.equals.overload(objectClass).implementation = function (obj) { + var response = javaString.equals.overload(objectClass).call(this, obj); + if (obj && obj.toString().length > 5) { + var stack = []; + var calledFrom = Exception.$new().getStackTrace().toString().split(','); + // Otherwise capture string comparisons + let i = 0; + do { + i = i + 1; + stack.push(calledFrom[i]); + } while (i <= 5); + var skipClass, skipMethod = false; + skiplist.forEach(function (toSkip) { + if (calledFrom[4].includes(toSkip)) + skipClass = true; + }); + if (!skipClass) { + var data = { + caller: stack, + string1: javaString.toString.call(this), + string2: obj.toString(), + return: response, + } + send('[AUXILIARY] [String Compare] ' + JSON.stringify(data, null, 2)); + } } return response; } -}); + }); + \ No newline at end of file diff --git a/mobsf/DynamicAnalyzer/tools/frida_scripts/android/default/dump_clipboard.js b/mobsf/DynamicAnalyzer/tools/frida_scripts/android/default/dump_clipboard.js index 8cbd873806..9773657d06 100644 --- a/mobsf/DynamicAnalyzer/tools/frida_scripts/android/default/dump_clipboard.js +++ b/mobsf/DynamicAnalyzer/tools/frida_scripts/android/default/dump_clipboard.js @@ -1,40 +1,42 @@ // Based on https://github.com/sensepost/objection/blob/f8e78d8a29574c6dadd2b953a63207b45a19b1cf/objection/hooks/android/clipboard/monitor.js -var ActivityThread = Java.use('android.app.ActivityThread'); -var ClipboardManager = Java.use('android.content.ClipboardManager'); -var CLIPBOARD_SERVICE = 'clipboard'; - -var currentApplication = ActivityThread.currentApplication(); -var context = currentApplication.getApplicationContext(); - -var clipboard_handle = context.getApplicationContext().getSystemService(CLIPBOARD_SERVICE); -var clipboard = Java.cast(clipboard_handle, ClipboardManager); - // Variable used for the current string data var string_data; function check_clipboard_data() { Java.perform(function () { - - var primary_clip = clipboard.getPrimaryClip(); - - // If we have managed to get the primary clipboard and there are - // items stored in it, process an update. - if (primary_clip != null && primary_clip.getItemCount() > 0) { - - var data = primary_clip.getItemAt(0).coerceToText(context).toString(); - - // If the data is the same, just stop. - if (string_data == data) { - return; + var ActivityThread = Java.use('android.app.ActivityThread'); + var ClipboardManager = Java.use('android.content.ClipboardManager'); + var CLIPBOARD_SERVICE = 'clipboard'; + + var currentApplication = ActivityThread.currentApplication(); + var context = currentApplication.getApplicationContext(); + + var clipboard_handle = context.getApplicationContext().getSystemService(CLIPBOARD_SERVICE); + var clipboard = Java.cast(clipboard_handle, ClipboardManager); + + setInterval(function(){ + + var primary_clip = clipboard.getPrimaryClip(); + + // If we have managed to get the primary clipboard and there are + // items stored in it, process an update. + if (primary_clip != null && primary_clip.getItemCount() > 0) { + + var data = primary_clip.getItemAt(0).coerceToText(context).toString(); + + // If the data is the same, just stop. + if (string_data == data) { + return; + } + + // Update the data with the new string and report back. + string_data = data; + send('mobsf-android-clipboard:' + data); } - - // Update the data with the new string and report back. - string_data = data; - send('mobsf-android-clipboard:' + data); - } + // Poll every 5 seconds + }, 1000 * 5); }); } -// Poll every 5 seconds -setInterval(check_clipboard_data, 1000 * 5); \ No newline at end of file +check_clipboard_data(); \ No newline at end of file diff --git a/mobsf/DynamicAnalyzer/tools/frida_scripts/android/others/stop-app-terminate.js b/mobsf/DynamicAnalyzer/tools/frida_scripts/android/others/stop-app-terminate.js index c38ada2fa3..b398821411 100644 --- a/mobsf/DynamicAnalyzer/tools/frida_scripts/android/others/stop-app-terminate.js +++ b/mobsf/DynamicAnalyzer/tools/frida_scripts/android/others/stop-app-terminate.js @@ -132,38 +132,46 @@ Java.perform(function() { console.error(e); } }) -Interceptor.attach(Module.findExportByName(null, "exit"), { - onEnter: function(args) { - console.warn("Native Exit() Called :-->:\n" + Thread.backtrace(this.context, Backtracer.ACCURATE).map(DebugSymbol.fromAddress).join("\n") + "\n"); - }, - onLeave: function(retval) {} -}); -Interceptor.attach(Module.findExportByName(null, "abort"), { - onEnter: function(args) { - console.warn("Native Abort() Called :-->:\n" + Thread.backtrace(this.context, Backtracer.ACCURATE).map(DebugSymbol.fromAddress).join("\n") + "\n"); - }, - onLeave: function(retval) {} -}); -var fork = Module.findExportByName(null, "fork") -Interceptor.attach(fork, { - onEnter: function(args) {}, - onLeave: function(retval) { - var pid = parseInt(retval.toString(16), 16) - console.log("Second Process PID : ", pid) - } -}) -Interceptor.attach(Module.findExportByName("libc.so", "system"), { - onEnter: function(args) { - var cmd = Memory.readCString(args[0]); - if (cmd.indexOf("kill") != -1) { - console.log("Bypass native system: " + cmd); - var NewKill = args[0].writeUtf8String("bypassed"); - args[0] = ptr(NewKill); - } - }, - onLeave: function(retval) {} -}); - +try { + Interceptor.attach(Module.findExportByName(null, "exit"), { + onEnter: function(args) { + console.warn("Native Exit() Called :-->:\n" + Thread.backtrace(this.context, Backtracer.ACCURATE).map(DebugSymbol.fromAddress).join("\n") + "\n"); + }, + onLeave: function(retval) {} + }); +} catch (e) {} +try { + Interceptor.attach(Module.findExportByName(null, "abort"), { + onEnter: function(args) { + console.warn("Native Abort() Called :-->:\n" + Thread.backtrace(this.context, Backtracer.ACCURATE).map(DebugSymbol.fromAddress).join("\n") + "\n"); + }, + onLeave: function(retval) {} + }); +} catch (e) {} +try { + var fork = Module.findExportByName(null, "fork") + Interceptor.attach(fork, { + onEnter: function(args) {}, + onLeave: function(retval) { + var pid = parseInt(retval.toString(16), 16) + console.log("Second Process PID : ", pid) + } + }) +} catch (e) {} +try { + Interceptor.attach(Module.findExportByName("libc.so", "system"), { + onEnter: function(args) { + var cmd = Memory.readCString(args[0]); + if (cmd.indexOf("kill") != -1) { + console.log("Bypass native system: " + cmd); + var NewKill = args[0].writeUtf8String("bypassed"); + args[0] = ptr(NewKill); + } + }, + onLeave: function(retval) {} + }); +} catch (e) {} +try { var abortPtr = Module.getExportByName('libc.so', 'abort'); var abort = new NativeFunction(abortPtr, 'int', ['int']); var exitPtr = Module.getExportByName('libc.so', 'exit'); @@ -200,4 +208,4 @@ Interceptor.attach(Module.findExportByName("libc.so", "system"), { console.log('Shutdown Replaced'); return 0; }, 'int', ['int', 'int'])); - +} catch (e) {} diff --git a/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/default/jailbreak_bypass.js b/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/default/jailbreak_bypass.js index b644ed02b4..f694a61793 100644 --- a/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/default/jailbreak_bypass.js +++ b/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/default/jailbreak_bypass.js @@ -1,90 +1,89 @@ -function bypassJailbreakDetection(){ - - var paths = [ - "/Applications/blackra1n.app", - "/Applications/Cydia.app", - "/Applications/FakeCarrier.app", - "/Applications/Icy.app", - "/Applications/IntelliScreen.app", - "/Applications/MxTube.app", - "/Applications/RockApp.app", - "/Applications/SBSetttings.app", - "/Applications/WinterBoard.app", - "/bin/bash", - "/bin/sh", - "/bin/su", - "/etc/apt", - "/etc/ssh/sshd_config", - "/Library/MobileSubstrate/DynamicLibraries/LiveClock.plist", - "/Library/MobileSubstrate/DynamicLibraries/Veency.plist", - "/Library/MobileSubstrate/MobileSubstrate.dylib", - "/pguntether", - "/private/var/lib/cydia", - "/private/var/mobile/Library/SBSettings/Themes", - "/private/var/stash", - "/private/var/tmp/cydia.log", - "/System/Library/LaunchDaemons/com.ikey.bbot.plist", - "/System/Library/LaunchDaemons/com.saurik.Cydia.Startup.plist", - "/usr/bin/cycript", - "/usr/bin/ssh", - "/usr/bin/sshd", - "/usr/libexec/sftp-server", - "/usr/libexec/ssh-keysign", - "/usr/sbin/frida-server", - "/usr/sbin/sshd", - "/var/cache/apt", - "/var/lib/cydia", - "/var/log/syslog", - "/var/mobile/Media/.evasi0n7_installed", - "/var/tmp/cydia.log", - "/etc/apt", - "/Library/MobileSubstrate/MobileSubstrate.dylib", - "/Applications/Cydia.app", - "/Applications/blackra1n.app", - "/Applications/FakeCarrier.app", - "/Applications/Icy.app", - "/Applications/IntelliScreen.app", - "/Applications/MxTube.app", - "/Applications/RockApp.app", - "/Applications/SBSetttings.app", - "/private/var/lib/apt/", - "/Applications/WinterBoard.app", - "/usr/sbin/sshd", - "/private/var/tmp/cydia.log", - "/usr/binsshd", - "/usr/libexec/sftp-server", - "/Systetem/Library/LaunchDaemons/com.ikey.bbot.plist", - "/System/Library/LaunchDaemons/com.saurik.Cy@dia.Startup.plist", - "/var/log/syslog", - "/bin/bash", - "/bin/sh", - "/etc/ssh/sshd_config", - "/usr/libexec/ssh-keysign", - "/Library/MobileSubstrate/DynamicLibraries/Veency.plist", - "/System/Library/LaunchDaemons/com.ikey.bbot.plist", - "/private/var/stash", - "/usr/bin/cycript", - "/usr/bin/ssh", - "/usr/bin/sshd", - "/var/cache/apt", - "/var/lib/cydia", - "/var/tmp/cydia.log", - "/Applications/SBSettings.app", - "/Library/MobileSubstrate/DynamicLibraries/LiveClock.plist", - "/System/Library/LaunchDaemons/com.saurik.Cydia.Startup.plist", - "/private/var/lib/apt", - "/private/var/lib/cydia", - "/private/var/mobile/Library/SBSettings/Themes", - "/var/lib/apt", - "/private/jailbreak.txt", - "/bin/su", - "/pguntether", - "/usr/sbin/frida-server", - "/private/Jailbreaktest.txt", - "/var/mobile/Media/.evasi0n7_installed", - "cydia://package/com.example.package" - ]; +var paths = [ + "/Applications/blackra1n.app", + "/Applications/Cydia.app", + "/Applications/FakeCarrier.app", + "/Applications/Icy.app", + "/Applications/IntelliScreen.app", + "/Applications/MxTube.app", + "/Applications/RockApp.app", + "/Applications/SBSetttings.app", + "/Applications/WinterBoard.app", + "/bin/bash", + "/bin/sh", + "/bin/su", + "/etc/apt", + "/etc/ssh/sshd_config", + "/Library/MobileSubstrate/DynamicLibraries/LiveClock.plist", + "/Library/MobileSubstrate/DynamicLibraries/Veency.plist", + "/Library/MobileSubstrate/MobileSubstrate.dylib", + "/pguntether", + "/private/var/lib/cydia", + "/private/var/mobile/Library/SBSettings/Themes", + "/private/var/stash", + "/private/var/tmp/cydia.log", + "/System/Library/LaunchDaemons/com.ikey.bbot.plist", + "/System/Library/LaunchDaemons/com.saurik.Cydia.Startup.plist", + "/usr/bin/cycript", + "/usr/bin/ssh", + "/usr/bin/sshd", + "/usr/libexec/sftp-server", + "/usr/libexec/ssh-keysign", + "/usr/sbin/frida-server", + "/usr/sbin/sshd", + "/var/cache/apt", + "/var/lib/cydia", + "/var/log/syslog", + "/var/mobile/Media/.evasi0n7_installed", + "/var/tmp/cydia.log", + "/etc/apt", + "/Library/MobileSubstrate/MobileSubstrate.dylib", + "/Applications/Cydia.app", + "/Applications/blackra1n.app", + "/Applications/FakeCarrier.app", + "/Applications/Icy.app", + "/Applications/IntelliScreen.app", + "/Applications/MxTube.app", + "/Applications/RockApp.app", + "/Applications/SBSetttings.app", + "/private/var/lib/apt/", + "/Applications/WinterBoard.app", + "/usr/sbin/sshd", + "/private/var/tmp/cydia.log", + "/usr/binsshd", + "/usr/libexec/sftp-server", + "/Systetem/Library/LaunchDaemons/com.ikey.bbot.plist", + "/System/Library/LaunchDaemons/com.saurik.Cy@dia.Startup.plist", + "/var/log/syslog", + "/bin/bash", + "/bin/sh", + "/etc/ssh/sshd_config", + "/usr/libexec/ssh-keysign", + "/Library/MobileSubstrate/DynamicLibraries/Veency.plist", + "/System/Library/LaunchDaemons/com.ikey.bbot.plist", + "/private/var/stash", + "/usr/bin/cycript", + "/usr/bin/ssh", + "/usr/bin/sshd", + "/var/cache/apt", + "/var/lib/cydia", + "/var/tmp/cydia.log", + "/Applications/SBSettings.app", + "/Library/MobileSubstrate/DynamicLibraries/LiveClock.plist", + "/System/Library/LaunchDaemons/com.saurik.Cydia.Startup.plist", + "/private/var/lib/apt", + "/private/var/lib/cydia", + "/private/var/mobile/Library/SBSettings/Themes", + "/var/lib/apt", + "/private/jailbreak.txt", + "/bin/su", + "/pguntether", + "/usr/sbin/frida-server", + "/private/Jailbreaktest.txt", + "/var/mobile/Media/.evasi0n7_installed", + "cydia://package/com.example.package" +]; +function bypassJailbreakDetection(){ try { var f = Module.findExportByName("libSystem.B.dylib", "stat64"); @@ -196,20 +195,21 @@ function bypassJailbreakDetection2() { }, onComplete: function() {} }); - send("[Jailbreak Detection Bypass 2] success"); + send("[Jailbreak Detection Bypass] success"); } catch(e) { - send('[Jailbreak Detection Bypass 2] script error:' + e.toString()); + send('[Jailbreak Detection Bypass] script error:' + e.toString()); } } try { if (ObjC.available) { + send('Bypassing Jailbreak detection checks'); bypassJailbreakDetection(); // Disable the below if the app is crashing setTimeout(() => { bypassJailbreakDetection2(); - }, "1000"); + }, 1000); } else { send('[Jailbreak Detection Bypass] error: Objective-C Runtime is not available!'); } diff --git a/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/dump/data-dir.js b/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/dump/data-dir.js index 2e118f8e6e..f09c546080 100644 --- a/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/dump/data-dir.js +++ b/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/dump/data-dir.js @@ -61,7 +61,7 @@ send('Dumping Application Directory file information'); try { setTimeout(() => { send(JSON.stringify({'[MBSFDUMP] datadir': getDataProtectionKeysForAllPaths()})); - }, "2000"); + }, 2000); } catch(err) {} // /******************************************************************************** diff --git a/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/dump/nslog.js b/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/dump/nslog.js index 60ef1c5325..ed49f71bef 100644 --- a/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/dump/nslog.js +++ b/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/dump/nslog.js @@ -26,8 +26,13 @@ function NSLogv(){ } try { - NSlog(); + setTimeout(() => { + NSlog(); + }, 1000); + } catch(err) {} try { - NSLogv(); + setTimeout(() => { + NSLogv(); + }, 1000); } catch(err) {} \ No newline at end of file diff --git a/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/dump/nsuserdefaults.js b/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/dump/nsuserdefaults.js index 1853e5a0c9..23e16a8f9b 100644 --- a/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/dump/nsuserdefaults.js +++ b/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/dump/nsuserdefaults.js @@ -30,6 +30,6 @@ function ns_userdefaults() { try{ setTimeout(() => { ns_userdefaults(); - }, "2000"); + }, 2000); } catch(err) {} \ No newline at end of file diff --git a/mobsf/DynamicAnalyzer/views/android/frida_core.py b/mobsf/DynamicAnalyzer/views/android/frida_core.py index 6926b66f97..01d21c77c9 100644 --- a/mobsf/DynamicAnalyzer/views/android/frida_core.py +++ b/mobsf/DynamicAnalyzer/views/android/frida_core.py @@ -1,6 +1,3 @@ -import io -import os -import glob import logging from pathlib import Path import sys @@ -21,7 +18,6 @@ ) from mobsf.MobSF.utils import ( get_device, - is_file_exists, ) logger = logging.getLogger(__name__) @@ -37,28 +33,26 @@ def __init__(self, app_hash, package, defaults, auxiliary, extras, code): self.auxiliary = auxiliary self.extras = extras self.code = code - self.frida_dir = os.path.join(settings.TOOLS_DIR, - 'frida_scripts', 'android') - self.apk_dir = os.path.join(settings.UPLD_DIR, self.hash + '/') - self.api_mon = os.path.join(self.apk_dir, 'mobsf_api_monitor.txt') - self.frida_log = os.path.join(self.apk_dir, 'mobsf_frida_out.txt') - self.deps = os.path.join(self.apk_dir, 'mobsf_app_deps.txt') - self.clipboard = os.path.join(self.apk_dir, 'mobsf_app_clipboard.txt') - - def get_default_scripts(self): - """Get default Frida Scripts.""" + self.frida_dir = Path(settings.TOOLS_DIR) / 'frida_scripts' / 'android' + self.apk_dir = Path(settings.UPLD_DIR) / self.hash + self.api_mon = self.apk_dir / 'mobsf_api_monitor.txt' + self.frida_log = self.apk_dir / 'mobsf_frida_out.txt' + self.deps = self.apk_dir / 'mobsf_app_deps.txt' + self.clipboard = self.apk_dir / 'mobsf_app_clipboard.txt' + + def get_scripts(self, script_type, selected_scripts): + """Get Frida Scripts.""" combined_script = [] header = [] - if not self.defaults: + if not selected_scripts: return header - def_scripts = os.path.join(self.frida_dir, 'default') - files = glob.glob(def_scripts + '**/*.js', recursive=True) - for item in files: - script = Path(item) - if script.stem in self.defaults: - header.append('send("Loaded Frida Script - {}");'.format( - script.stem)) - combined_script.append(script.read_text()) + all_scripts = self.frida_dir / script_type + for script in all_scripts.rglob('*.js'): + if '*' in selected_scripts: + combined_script.append(script.read_text('utf-8', 'ignore')) + if script.stem in selected_scripts: + header.append(f'send("Loaded Frida Script - {script.stem}");') + combined_script.append(script.read_text('utf-8', 'ignore')) return header + combined_script def get_auxiliary(self): @@ -88,12 +82,16 @@ def get_script(self): """Get final script.""" if not self.code: self.code = '' + rpc_list = [] # Load custom code first scripts = [self.code] - scripts.extend(self.get_default_scripts()) + scripts.extend(self.get_scripts('default', self.defaults)) + rpc_list.extend(self.get_scripts('rpc', ['*'])) scripts.extend(self.get_auxiliary()) + rpc_script = ','.join(rpc_list) + rpc = f'rpc.exports = {{ \n{rpc_script}\n }};' combined = '\n'.join(scripts) - final = f'setTimeout(function() {{ \n{combined}\n}}, 1000)' + final = f'{rpc}\n setTimeout(function() {{ \n{combined}\n }}, 1000)' return final def frida_response(self, message, data): @@ -112,15 +110,15 @@ def frida_response(self, message, data): msg = msg.replace(clip, '') self.write_log(self.clipboard, f'{msg}\n') elif msg.startswith(deps): - info = msg.replace(deps, '') + '\n' - self.write_log(self.deps, info) - self.write_log(self.frida_log, info) + info = msg.replace(deps, '') + self.write_log(self.deps, f'{info}\n') + self.write_log(self.frida_log, f'{info}\n') elif msg.startswith(aux): - self.write_log(self.frida_log, - msg.replace(aux, '[*] ') + '\n') + msg = msg.replace(aux, '[*] ') + self.write_log(self.frida_log, f'{msg}\n') else: logger.debug('[Frida] %s', msg) - self.write_log(self.frida_log, msg + '\n') + self.write_log(self.frida_log, f'{msg}\n') else: logger.error('[Frida] %s', message) @@ -165,13 +163,23 @@ def session(self, pid, package): if pid and package: _FPID = pid self.package = package + try: + front = device.get_frontmost_application() + if front and front.pid != _FPID: + # Not the front most app. + # Get the pid of the front most app + logger.warning('Front most app has PID %s', front.pid) + _FPID = front.pid + except Exception: + pass + # pid is the fornt most app session = device.attach(_FPID) time.sleep(2) except frida.NotSupportedError: logger.exception('Not Supported Error') return except Exception: - logger.warning('Cannot attach to pid, spawning again') + logger.exception('Cannot attach to pid, spawning again') self.spawn() session = device.attach(_FPID) time.sleep(2) @@ -179,6 +187,8 @@ def session(self, pid, package): script = session.create_script(self.get_script()) script.on('message', self.frida_response) script.load() + api = script.exports_sync + self.api_handler(api) sys.stdin.read() script.unload() session.detach() @@ -212,18 +222,57 @@ def ps(self): logger.exception('Failed to enumerate running applications') return ps_dict + def api_handler(self, api): + """Call Frida rpc functions.""" + loaded_classes = [] + loaded_class_methods = [] + implementations = [] + try: + raction = self.extras.get('rclass_action') + rclass = self.extras.get('rclass_name') + rclass_pattern = self.extras.get('rclass_pattern') + rmethod = self.extras.get('rmethod_name') + rmethod_pattern = self.extras.get('rmethod_pattern') + if raction == 'raction': + loaded_classes = api.getLoadedClasses() + elif raction == 'getclasses' and rclass_pattern: + loaded_classes = api.getLoadedClasses(f'/{rclass_pattern}/i') + elif raction == 'getmethods' and rclass and rmethod: + loaded_class_methods = api.getMethods(rclass) + elif raction == 'getmethods' and rclass and rmethod_pattern: + loaded_class_methods = api.getMethods( + rclass, + f'/{rmethod_pattern}/i') + elif raction == 'getimplementations' and rclass and rmethod: + implementations = api.getImplementations(rclass, rmethod) + except Exception: + logger.exception('Error while calling Frida RPC functions') + if loaded_classes: + rpc_classes = self.apk_dir / 'mobsf_rpc_classes.txt' + loaded_classes = sorted(loaded_classes) + rpc_classes.write_text('\n'.join( + loaded_classes), 'utf-8') + if loaded_class_methods: + rpc_methods = self.apk_dir / 'mobsf_rpc_methods.txt' + loaded_class_methods = sorted(loaded_class_methods) + rpc_methods.write_text('\n'.join( + loaded_class_methods), 'utf-8') + if implementations: + implementations = sorted(implementations) + rpc_impl = self.apk_dir / 'mobsf_rpc_impl.txt' + rpc_impl.write_text('\n'.join( + implementations), 'utf-8') + def clean_up(self): - if is_file_exists(self.api_mon): - os.remove(self.api_mon) - if is_file_exists(self.frida_log): - os.remove(self.frida_log) - if is_file_exists(self.clipboard): - os.remove(self.clipboard) + if self.api_mon.exists(): + self.api_mon.unlink() + if self.frida_log.exists(): + self.frida_log.unlink() + if self.clipboard.exists(): + self.clipboard.unlink() def write_log(self, file_path, data): - with io.open( - file_path, - 'a', - encoding='utf-8', - errors='replace') as flip: + with file_path.open('a', + encoding='utf-8', + errors='replace') as flip: flip.write(data) diff --git a/mobsf/DynamicAnalyzer/views/android/tests_frida.py b/mobsf/DynamicAnalyzer/views/android/tests_frida.py index 11204a4a89..05f438e132 100644 --- a/mobsf/DynamicAnalyzer/views/android/tests_frida.py +++ b/mobsf/DynamicAnalyzer/views/android/tests_frida.py @@ -163,44 +163,6 @@ def live_api(request, api=False): return print_n_send_error_response(request, err, 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', - 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 decode_base64(data, altchars=b'+/'): """Decode base64, padding being optional. diff --git a/mobsf/DynamicAnalyzer/views/common/frida.py b/mobsf/DynamicAnalyzer/views/common/frida.py index f6adc5fafa..1f426a24da 100644 --- a/mobsf/DynamicAnalyzer/views/common/frida.py +++ b/mobsf/DynamicAnalyzer/views/common/frida.py @@ -1,9 +1,11 @@ """Shared Frida Views.""" +import logging import glob import os from pathlib import Path from django.conf import settings +from django.shortcuts import render from django.views.decorators.http import require_http_methods from mobsf.DynamicAnalyzer.views.common.shared import ( @@ -11,8 +13,15 @@ ) from mobsf.MobSF.utils import ( is_file_exists, + is_md5, is_safe_path, + print_n_send_error_response, ) + + +logger = logging.getLogger(__name__) + + # AJAX @@ -65,3 +74,45 @@ def get_script(request, api=False): except Exception: pass return send_response(data, api) +# AJAX + HTML + + +def frida_logs(request, api=False): + try: + data = { + 'status': 'failed', + 'message': 'Data does not exist.'} + if api: + apphash = request.POST['hash'] + stream = True + else: + apphash = request.GET.get('hash', '') + stream = request.GET.get('stream', '') + if not is_md5(apphash): + data['message'] = 'Invalid hash' + return send_response(data, api) + if stream: + apk_dir = os.path.join(settings.UPLD_DIR, apphash + '/') + frida_logs = os.path.join(apk_dir, 'mobsf_frida_out.txt') + if not is_file_exists(frida_logs): + return send_response(data, api) + with open(frida_logs, 'r', + encoding='utf8', + errors='ignore') as flip: + data = { + 'status': 'ok', + 'message': 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) diff --git a/mobsf/DynamicAnalyzer/views/ios/corellium_apis.py b/mobsf/DynamicAnalyzer/views/ios/corellium_apis.py index e672a9ad88..540ab0e5b4 100644 --- a/mobsf/DynamicAnalyzer/views/ios/corellium_apis.py +++ b/mobsf/DynamicAnalyzer/views/ios/corellium_apis.py @@ -262,13 +262,20 @@ def poll_instance(self): def screenshot(self): """Take screenshot inside VM.""" - r = requests.get( - f'{self.api}/instances/{self.instance_id}/screenshot.png?scale=1', - headers=self.headers, - stream=True) - if r.status_code == 200: - return r.content - logger.error('Failed to take a screenshot. %s', r.json()['error']) + r = None + err = '' + try: + r = requests.get( + (f'{self.api}/instances/{self.instance_id}' + '/screenshot.png?scale=1'), + headers=self.headers, + stream=True) + if r.status_code == 200: + return r.content + except Exception: + if r and r.json().get('error'): + err = r.json()['error'] + logger.error('Failed to take a screenshot. %s', err) return False def start_network_capture(self): diff --git a/mobsf/DynamicAnalyzer/views/ios/corellium_instance.py b/mobsf/DynamicAnalyzer/views/ios/corellium_instance.py index ee96970e5c..5cdffb67c2 100644 --- a/mobsf/DynamicAnalyzer/views/ios/corellium_instance.py +++ b/mobsf/DynamicAnalyzer/views/ios/corellium_instance.py @@ -719,9 +719,9 @@ def touch(request, api=False): 'message': '', } try: - x_axis = request.POST['x'] - y_axis = request.POST['y'] - event = request.POST['event'] + x_axis = request.POST.get('x') + y_axis = request.POST.get('y') + event = request.POST.get('event') max_x = request.POST.get('max_x', 0) max_y = request.POST.get('max_y', 0) diff --git a/mobsf/DynamicAnalyzer/views/ios/frida_core.py b/mobsf/DynamicAnalyzer/views/ios/frida_core.py index bfe3fa447b..6c23aef8f7 100644 --- a/mobsf/DynamicAnalyzer/views/ios/frida_core.py +++ b/mobsf/DynamicAnalyzer/views/ios/frida_core.py @@ -168,12 +168,15 @@ def spawn(self): return except frida.ServerNotRunningError: self.frida_ssh_forward() + _PID = None if not _PID: _PID = frida.get_remote_device().spawn([self.bundle_id]) logger.info('Spawning %s', self.bundle_id) time.sleep(2) except frida.TimedOutError: logger.error('Timed out while waiting for device to appear') + except frida.ServerNotRunningError: + logger.error('Frida Server is not running') except frida.NotSupportedError: logger.error(self.not_supported_text) return @@ -194,19 +197,21 @@ def session(self, pid, bundle_id): if pid and bundle_id: _PID = pid self.bundle_id = bundle_id - front = device.get_frontmost_application() - if not front or front.pid != _PID: - # No front most app, spawn the app or - # pid is not the front most app - _PID = device.spawn([self.bundle_id]) - logger.info('Spawning %s', self.bundle_id) - # pid is the fornt most app + try: + front = device.get_frontmost_application() + if front or front.pid != _PID: + # Not the front most app. + # Get the pid of the front most app + logger.warning('Front most app has PID %s', front.pid) + _PID = front.pid + except Exception: + pass session = device.attach(_PID) except frida.NotSupportedError: logger.error(self.not_supported_text) return except Exception: - logger.warning('Cannot attach to pid, spawning again') + logger.warning('Cannot attach to pid, spawning again.') self.spawn() session = device.attach(_PID) if session and device and _PID: @@ -215,8 +220,7 @@ def session(self, pid, bundle_id): script.load() api = script.exports_sync device.resume(_PID) - self.app_container = api.get_container() - self.container_file.write_text(self.app_container) + self.api_handler(api) sys.stdin.read() script.unload() session.detach() @@ -253,6 +257,52 @@ def ps(self): logger.exception('Failed to enumerate running applications') return ps_dict + def api_handler(self, api): + """Call Frida rpc functions.""" + loaded_classes = [] + loaded_class_methods = [] + implementations = [] + try: + try: + self.app_container = api.get_container() + self.container_file.write_text(self.app_container) + except frida.InvalidOperationError: + pass + raction = self.extras.get('rclass_action') + rclass = self.extras.get('rclass_name') + rclass_pattern = self.extras.get('rclass_pattern') + rmethod = self.extras.get('rmethod_name') + rmethod_pattern = self.extras.get('rmethod_pattern') + if raction == 'raction': + loaded_classes = api.getLoadedClasses() + elif raction == 'getclasses' and rclass_pattern: + loaded_classes = api.getLoadedClasses(f'/{rclass_pattern}/i') + elif raction == 'getmethods' and rclass and rmethod: + loaded_class_methods = api.getMethods(rclass) + elif raction == 'getmethods' and rclass and rmethod_pattern: + loaded_class_methods = api.getMethods( + rclass, + f'/{rmethod_pattern}/i') + elif raction == 'getimplementations' and rclass and rmethod: + implementations = api.getImplementations(rclass, rmethod) + except Exception: + logger.exception('Error while calling Frida RPC functions') + if loaded_classes: + loaded_classes = sorted(loaded_classes) + rpc_classes = self.ipa_dir / 'mobsf_rpc_classes.txt' + rpc_classes.write_text('\n'.join( + loaded_classes), 'utf-8') + if loaded_class_methods: + loaded_class_methods = sorted(loaded_class_methods) + rpc_methods = self.ipa_dir / 'mobsf_rpc_methods.txt' + rpc_methods.write_text('\n'.join( + loaded_class_methods), 'utf-8') + if implementations: + implementations = sorted(implementations) + rpc_impl = self.ipa_dir / 'mobsf_rpc_impl.txt' + rpc_impl.write_text('\n'.join( + implementations), 'utf-8') + def clean_up(self): if self.frida_log.exists(): self.frida_log.unlink() diff --git a/mobsf/MobSF/init.py b/mobsf/MobSF/init.py index a8d4e4b236..779144958f 100644 --- a/mobsf/MobSF/init.py +++ b/mobsf/MobSF/init.py @@ -10,7 +10,7 @@ logger = logging.getLogger(__name__) -VERSION = '3.8.4' +VERSION = '3.8.5' BANNER = """ __ __ _ ____ _____ _____ ___ | \/ | ___ | |__/ ___|| ___|_ _|___ / ( _ ) diff --git a/mobsf/MobSF/urls.py b/mobsf/MobSF/urls.py index da6c2593af..351565db1f 100755 --- a/mobsf/MobSF/urls.py +++ b/mobsf/MobSF/urls.py @@ -194,7 +194,7 @@ tests_frida.live_api, name='live_api'), re_path(r'^frida_logs/$', - tests_frida.frida_logs, + frida.frida_logs, name='frida_logs'), re_path(r'^get_dependencies/$', tests_frida.get_runtime_dependencies), # Report diff --git a/mobsf/MobSF/utils.py b/mobsf/MobSF/utils.py index 7e104470b8..e5a639b8a3 100755 --- a/mobsf/MobSF/utils.py +++ b/mobsf/MobSF/utils.py @@ -228,6 +228,8 @@ def find_between(s, first, last): def is_number(s): + if not s: + return False if s == 'NaN': return False try: diff --git a/mobsf/MobSF/views/api/api_dynamic_analysis.py b/mobsf/MobSF/views/api/api_dynamic_analysis.py index 5d0c3ca29e..3e6332a2de 100644 --- a/mobsf/MobSF/views/api/api_dynamic_analysis.py +++ b/mobsf/MobSF/views/api/api_dynamic_analysis.py @@ -222,7 +222,7 @@ def api_frida_logs(request): if 'hash' not in request.POST: return make_api_response( {'error': 'Missing Parameters'}, 422) - resp = tests_frida.frida_logs(request, True) + resp = frida.frida_logs(request, True) # frida logs can be json or html if resp.get('data'): return make_api_response(resp, 200) diff --git a/mobsf/static/others/css/terminal.css b/mobsf/static/others/css/terminal.css index 2dd8937962..a3ff1071a3 100644 --- a/mobsf/static/others/css/terminal.css +++ b/mobsf/static/others/css/terminal.css @@ -1,7 +1,3 @@ - -::selection { - background: #FF5E99; -} output { color: #d2d2d2; } diff --git a/mobsf/static/terminal/terminal.css b/mobsf/static/terminal/terminal.css deleted file mode 100644 index 2dd8937962..0000000000 --- a/mobsf/static/terminal/terminal.css +++ /dev/null @@ -1,79 +0,0 @@ - -::selection { - background: #FF5E99; -} -output { - color: #d2d2d2; -} -#shell { - padding: .1em 1.5em 1em 1em; - color: #d2d2d2; - background: black; - border-radius: 12px; -} -#shell output { - clear: both; - width: 100%; - -} -#shell output h3 { - margin: 0; -} -#shell output pre { - margin: 0; -} -.input-line { - display: -webkit-box; - -webkit-box-orient: horizontal; - -webkit-box-align: stretch; - display: -moz-box; - -moz-box-orient: horizontal; - -moz-box-align: stretch; - display: box; - box-orient: horizontal; - box-align: stretch; - clear: both; -} -.input-line > div:nth-child(2) { - -webkit-box-flex: 1; - -moz-box-flex: 1; - box-flex: 1; -} -.prompt { - white-space: nowrap; - color: #96b38a; - margin-right: 7px; - display: -webkit-box; - -webkit-box-pack: center; - -webkit-box-orient: vertical; - display: -moz-box; - -moz-box-pack: center; - -moz-box-orient: vertical; - display: box; - box-pack: center; - box-orient: vertical; - -webkit-user-select: none; - -moz-user-select: none; - user-select: none; -} -.cmdline { - outline: none; - background-color: transparent; - margin: 0; - width: 100%; - font: inherit; - border: none; - color: inherit; -} -.ls-files { - -webkit-column-width: 100px; - -moz-column-width: 100px; - -o-column-width: 100px; - column-width: 100px; -} - -.clean-text { - background-color: none; - background: none; - color: #ccc; -} diff --git a/mobsf/templates/dynamic_analysis/android/dynamic_analyzer.html b/mobsf/templates/dynamic_analysis/android/dynamic_analyzer.html index 2cdf972ec1..89b2971cec 100644 --- a/mobsf/templates/dynamic_analysis/android/dynamic_analyzer.html +++ b/mobsf/templates/dynamic_analysis/android/dynamic_analyzer.html @@ -241,7 +241,7 @@

Auxiliary

@@ -847,11 +847,15 @@ function get_logs(){ $.ajax({ - url : '/frida_logs/?hash={{ hash }}&stream=1', + url : '{% url 'frida_logs' %}?hash={{ hash }}&stream=1', type : "GET", dataType: "json", success : function(json){ - $('#frida-logs').text(json.data); + if (json.status==="ok"){ + $('#frida-logs').text(json.message); + } else { + $('#frida-logs').text('') + } }, error : function(xhr, ajaxOptions, thrownError) { console.log(xhr.responseText); @@ -1232,6 +1236,7 @@ }); if (frida_action === 'spawn'){ + document.getElementById('frida_session').disabled = false; print_status("Instrumenting the application with Frida."); } else if (frida_action === 'session' && pid === null){ print_status("Injecting Frida script to existing session."); diff --git a/mobsf/templates/dynamic_analysis/android/frida_logs.html b/mobsf/templates/dynamic_analysis/android/frida_logs.html index 728e99935c..7fd8d42bc8 100644 --- a/mobsf/templates/dynamic_analysis/android/frida_logs.html +++ b/mobsf/templates/dynamic_analysis/android/frida_logs.html @@ -31,7 +31,7 @@ type : "GET", dataType: "json", success : function(json){ - $('#logs').text(json.data); + $('#logs').text(json.message); }, error : function(xhr, ajaxOptions, thrownError) { console.log(xhr.responseText); diff --git a/mobsf/templates/dynamic_analysis/ios/dynamic_analysis.html b/mobsf/templates/dynamic_analysis/ios/dynamic_analysis.html index 54c1f935dc..250a6c2474 100644 --- a/mobsf/templates/dynamic_analysis/ios/dynamic_analysis.html +++ b/mobsf/templates/dynamic_analysis/ios/dynamic_analysis.html @@ -802,9 +802,8 @@ 'IPA Installed', 'The IPA is successfully installed', 'success' - ).then(function () { - location.reload(); - }) + ); + location.reload(); } }, error : function(xhr,errmsg,err) { diff --git a/mobsf/templates/dynamic_analysis/ios/dynamic_analyzer.html b/mobsf/templates/dynamic_analysis/ios/dynamic_analyzer.html index 7996bc97c6..f94efb1a25 100644 --- a/mobsf/templates/dynamic_analysis/ios/dynamic_analyzer.html +++ b/mobsf/templates/dynamic_analysis/ios/dynamic_analyzer.html @@ -280,7 +280,7 @@

Auxiliary

@@ -774,11 +774,15 @@ // Frida Logs Tab setInterval( function () { $.ajax({ - url : '/frida_logs/?hash={{ hash }}&stream=1', + url : '{% url 'frida_logs' %}?hash={{ hash }}&stream=1', type : "GET", dataType: "json", success : function(json){ - $('#frida-logs').text(json.data); + if (json.status==="ok"){ + $('#frida-logs').text(json.message); + } else { + $('#frida-logs').text('') + } }, error : function(xhr, ajaxOptions, thrownError) { console.log(xhr.responseText); @@ -1077,6 +1081,7 @@ }); if (frida_action === 'spawn'){ + document.getElementById('frida_session').disabled = false; print_status("Instrumenting the application with Frida."); } else if (frida_action === 'session' && pid === null){ print_status("Injecting Frida script to existing session."); diff --git a/pyproject.toml b/pyproject.toml index ba9db61b19..f643c82c3d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "mobsf" -version = "3.8.4" +version = "3.8.5" description = "Mobile Security Framework (MobSF) is an automated, all-in-one mobile application (Android/iOS/Windows) pen-testing, malware analysis and security assessment framework capable of performing static and dynamic analysis." keywords = ["mobsf", "mobile security framework", "mobile security", "security tool", "static analysis", "dynamic analysis", "malware analysis"] authors = ["Ajin Abraham "]