iddoeldor/frida-snippets
GitHub: iddoeldor/frida-snippets
汇集了大量针对 Android、iOS 及 Native 环境的 Frida 代码示例,旨在帮助开发者快速构建动态分析与逆向工程脚本。
Stars: 2515 | Forks: 441
 以及输出示例
## 目录
\");\n});"},
{"trigger": "fridaexport", "contents": "Module.findExportByName(null, \"\");"},
{"trigger": "fridabase", "contents": "Module.findBaseAddress(name);"},
{"trigger": "fridaoverload", "contents": "kls.method_name.overload().implementation=function(){}"}
]
}
```
` 展开
* 添加到 `~/.vimrc`
```
ab fridaintercept Interceptor.attach(ptr, {onEnter: function(args) {},onLeave: function(retval) {}})
ab fridabacktrace console.warn(Thread.backtrace(this.context, Backtracer.ACCURATE).map(DebugSymbol.fromAddress).join('\n'));F(3;
ab fridadescribe console.log(Object.getOwnPropertyNames(Java.use('$').__proto__).join('\n\t'))F$
```
#### 获取 SSL 密钥 ``` var keylog_callback = new NativeCallback((ssl, line) => { send(Memory.readCString(line)); }, 'void', ['pointer', 'pointer']); if (ObjC.available) { var CALLBACK_OFFSET = 0x2A8 if (Memory.readDouble(Module.findExportByName('CoreFoundation', 'kCFCoreFoundationVersionNumber')) >= 1751.108) { CALLBACK_OFFSET = 0x2B8 } Interceptor.attach(Module.findExportByName('libboringssl.dylib', 'SSL_CTX_set_info_callback'), { onEnter(args) { ptr(args[0]).add(CALLBACK_OFFSET).writePointer(keylog_callback) } }) } else if (Java.available) { var set_keylog_callback = new NativeFunction(Module.findExportByName('libssl.so', 'SSL_CTX_set_keylog_callback'), 'void', ['pointer', 'pointer']); Interceptor.attach(Module.findExportByName('libssl.so', 'SSL_CTX_new'), { onLeave(retval) { set_keylog_callback(retval, keylog_callback) } }) } ```
[⬆ 回到顶部](#table-of-contents) #### 加载 CPP 模块 ``` #include
#include
extern "C" {
void* create_stdstr(char *data, int size) {
std::string* s = new std::string();
(*s).assign(data, size);
return s;
}
}
```
```
$ ./android-ndk/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android21-clang++ a.cpp -o a -shared -static-libstdc++ && adb push a /data/local/tmp/a
```
```
[device]->
function readStdString(str) {
if ((str.readU8() & 1) === 1) { // size LSB (=1) indicates if it's a long string
return str.add(2 * Process.pointerSize).readPointer().readUtf8String();
}
return str.add(1).readUtf8String();
}
[device]-> Module.load('/data/local/tmp/a');
[device]-> var fp_create_stdstr = Module.findExportByName('a', 'create_stdstr');
[device]-> var createStdString = new NativeFunction(fp_create_stdstr, 'pointer', ['pointer', 'int']);
[device]-> var stdstr1 = createStdString(Memory.allocUtf8String("abcd"), 3);
"0x07691234567"
[device]-> readStdString(stdstr1);
"abc"
```
#### 加载 C 模块
* https://frida.re/docs/javascript-api/#cmodule
* https://frida.re/news/2019/09/18/frida-12-7-released/
```
$ ./aarch64-linux-android21-clang /tmp/b.c -o /tmp/a -shared ../sysroot/usr/lib/aarch64-linux-android/21/liblog.so && adb push /tmp/a /data/local/tmp/a
```
```
#include
#include
#include
#define TAG "TEST1"
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, TAG, __VA_ARGS__)
void test(void) {
FILE* fp = popen("ls -l /sdcard 2>&1", "r");
if (fp == NULL)
LOGE("executing cmd failed");
char b[256];
while (fgets(b, sizeof(b), fp) != NULL) {
LOGI("%s", b);
}
pclose(fp);
}
```
```
$ frida -Uf com.app --no-pause --enable-jit -e "Module.load('/data/local/tmp/a')"
[ ] -> new NativeFunction(Module.findExportByName('a', 'test'), 'void', [])()
```
[⬆ 回到顶部](#table-of-contents) #### 单次监视点 拦截 `funcPtr` 并通过使用 `mprotect` 移除权限来记录谁对 `x2` 进行了读/写。 ``` Process.setExceptionHandler(function(exp) { console.warn(JSON.stringify(Object.assign(exp, { _lr: DebugSymbol.fromAddress(exp.context.lr), _pc: DebugSymbol.fromAddress(exp.context.pc) }), null, 2)); Memory.protect(exp.memory.address, Process.pointerSize, 'rw-'); // can also use `new NativeFunction(Module.findExportByName(null, 'mprotect'), 'int', ['pointer', 'uint', 'int'])(parseInt(this.context.x2), 2, 0)` return true; // goto PC }); Interceptor.attach(funcPtr, { onEnter: function (args) { console.log('onEnter', JSON.stringify({ x2: this.context.x2, mprotect_ret: Memory.protect(this.context.x2, 2, '---'), errno: this.errno }, null, 2)); }, onLeave: function (retval) { console.log('onLeave'); } }); ```
[⬆ 回到顶部](#table-of-contents) #### Socket 活动 ``` Process .getModuleByName({ linux: 'libc.so', darwin: 'libSystem.B.dylib', windows: 'ws2_32.dll' }[Process.platform]) .enumerateExports().filter(ex => ex.type === 'function' && ['connect', 'recv', 'send', 'read', 'write'].some(prefix => ex.name.indexOf(prefix) === 0)) .forEach(ex => { Interceptor.attach(ex.address, { onEnter: function (args) { var fd = args[0].toInt32(); var socktype = Socket.type(fd); if (socktype !== 'tcp' && socktype !== 'tcp6') return; var address = Socket.peerAddress(fd); if (address === null) return; console.log(fd, ex.name, address.ip + ':' + address.port); } }) }) ```
[⬆ 回到顶部](#table-of-contents) #### 拦截 Open 拦截 `libc#open` 并在特定文件被打开时记录回溯(backtrace)的示例。 ``` Interceptor.attach(Module.findExportByName("/system/lib/libc.so", "open"), { onEnter: function(args) { this.flag = false; var filename = Memory.readCString(ptr(args[0])); console.log('filename =', filename) if (filename.endsWith(".xml")) { this.flag = true; var backtrace = Thread.backtrace(this.context, Backtracer.ACCURATE).map(DebugSymbol.fromAddress).join("\n\t"); console.log("file name [ " + Memory.readCString(ptr(args[0])) + " ]\nBacktrace:" + backtrace); } }, onLeave: function(retval) { if (this.flag) // passed from onEnter console.warn("\nretval: " + retval); } }); ``` ``` var fds = {}; // for f in /proc/`pidof $APP`/fd/*; do echo $f': 'readlink $f; done Interceptor.attach(Module.findExportByName(null, 'open'), { onEnter: function (args) { var fname = args[0].readCString(); if (fname.endsWith('.jar')) { this.flag = true; this.fname = fname; } }, onLeave: function (retval) { if (this.flag) { fds[retval] = this.fname; } } }); ['read', 'pread', 'readv'].forEach(fnc => { Interceptor.attach(Module.findExportByName(null, fnc), { onEnter: function (args) { var fd = args[0]; if (fd in fds) console.log(`${fnc}: ${fds[fd]} \t${Thread.backtrace(this.context, Backtracer.ACCURATE).map(DebugSymbol.fromAddress).join('\n\t')}`); } }); }); ```
[⬆ 回到顶部](#table-of-contents) #### 执行 Shell 命令 ``` import frida from frida_tools.application import Reactor import threading import click class Shell(object): def __init__(self, argv, env): self._stop_requested = threading.Event() self._reactor = Reactor(run_until_return=lambda reactor: self._stop_requested.wait()) self._device = frida.get_usb_device() self._sessions = set() self._device.on("child-added", lambda child: self._reactor.schedule(lambda: self._on_child_added(child))) self._device.on("child-removed", lambda child: self._reactor.schedule(lambda: self._on_child_removed(child))) self._device.on("output", lambda pid, fd, data: self._reactor.schedule(lambda: self._on_output(pid, fd, data))) self.argv = argv self.env = env self.output = [] # stdout will pushed into array def exec(self): self._reactor.schedule(lambda: self._start()) self._reactor.run() def _start(self): click.secho("✔ spawn(argv={})".format(self.argv), fg='green', dim=True) pid = self._device.spawn(self.argv, env=self.env, stdio='pipe') self._instrument(pid) def _stop_if_idle(self): if len(self._sessions) == 0: self._stop_requested.set() def _instrument(self, pid): click.secho("✔ attach(pid={})".format(pid), fg='green', dim=True) session = self._device.attach(pid) session.on("detached", lambda reason: self._reactor.schedule(lambda: self._on_detached(pid, session, reason))) click.secho("✔ enable_child_gating()", fg='green', dim=True) session.enable_child_gating() # print("✔ resume(pid={})".format(pid)) self._device.resume(pid) self._sessions.add(session) def _on_child_added(self, child): click.secho("⚡ child_added: {}".format(child), fg='green', dim=True) self._instrument(child.pid) @staticmethod def _on_child_removed(child): click.secho("⚡ child_removed: {}".format(child), fg='green', dim=True) def _on_output(self, pid, fd, data): # print("⚡ output: pid={}, fd={}, data={}".format(pid, fd, repr(data))) # fd=0 (input) fd=1(stdout) fd=2(stderr) if fd != 2: self.output.append(data) def _on_detached(self, pid, session, reason): click.secho("⚡ detached: pid={}, reason='{}'".format(pid, reason), fg='green', dim=True) self._sessions.remove(session) self._reactor.schedule(self._stop_if_idle, delay=0.5) @staticmethod def _on_message(pid, message): click.secho("⚡ message: pid={}, payload={}".format(pid, message), fg='green', dim=True) ```
[⬆ 回到顶部](#table-of-contents) #### 列出模块 ``` Process.enumerateModulesSync() .filter(function(m){ return m['path'].toLowerCase().indexOf('app') !=-1 ; }) .forEach(function(m) { console.log(JSON.stringify(m, null, ' ')); // to list exports use Module.enumerateExportsSync(m.name) }); ``` 列出模块和导出表 ``` sudo frida Process --no-pause --eval 'var x={};Process.enumerateModulesSync().forEach(function(m){x[m.name] = Module.enumerateExportsSync(m.name)});x' -q | less +F ```
[⬆ 回到顶部](#table-of-contents) #### 记录 SQLite 查询 ``` Interceptor.attach(Module.findExportByName('libsqlite.so', 'sqlite3_prepare16_v2'), { onEnter: function(args) { console.log('DB: ' + Memory.readUtf16String(args[0]) + '\tSQL: ' + Memory.readUtf16String(args[1])); } }); ```
[⬆ 回到顶部](#table-of-contents) #### 获取系统属性 ``` Interceptor.attach(Module.findExportByName(null, '__system_property_get'), { onEnter: function (args) { this._name = args[0].readCString(); this._value = args[1]; }, onLeave: function (retval) { console.log(JSON.stringify({ result_length: retval, name: this._name, val: this._value.readCString() })); } }); ```
[⬆ 回到顶部](#table-of-contents) #### Binder 事务 ``` var LAST_MSG = ''; Java.perform(() => { Interceptor.attach(Module.findExportByName('libbinder.so', 'ioctl'), { onEnter: function(args) { var binder_write_read_ptr = args[2]; if (args[1] == 0xC0306201) { // BINDER_WRITE_READ var binder_write_read = { // 'fd': args[0].toInt32(), 'write_size': binder_write_read_ptr.readU64(), 'write_consumed': binder_write_read_ptr.add(Process.pointerSize).readU64(), 'write_buffer': binder_write_read_ptr.add(Process.pointerSize * 2).readPointer(), } if (binder_write_read.write_size > 0) { var ptr = binder_write_read.write_buffer.add(binder_write_read.write_consumed + 4); switch (binder_write_read.write_buffer.readU32() & 0xff) { case 0: // BC_TRANSACTION case 1: // BC_REPLY var binder_transaction_data = { 'target': { 'handle': ptr.readU32(), 'ptr': ptr.readPointer() }, 'cookie': ptr.add(8).readPointer(), 'code': ptr.add(16).readU32(), 'flags': ptr.add(20).readU32(), 'sender_pid': ptr.add(24).readS32(), 'sender_euid': ptr.add(28).readU32(), 'data_size': ptr.add(32).readU64(), 'offsets_size': ptr.add(40).readU64(), 'data': { 'ptr': { 'buffer': ptr.add(48).readPointer(), 'offsets': ptr.add(56).readPointer() }, 'buf': ptr.add(48).readByteArray(8) } } var _log = hexdump(binder_transaction_data.data.ptr.buffer, { length: binder_transaction_data.data_size, ansi: true }); if (LAST_MSG.toString() != _log.toString()) { console.log(JSON.stringify(binder_transaction_data, null, 2)); console.log(_log); } break; } } } } }); }); ```
[⬆ 回到顶部](#table-of-contents) #### 揭示 Native 方法 `registerNativeMethods` 可被用作针对 native .so 库的反逆向技术,例如尽可能隐藏符号、混淆导出符号,并最终在 JNI 桥接层增加一些保护。 [来源](https://stackoverflow.com/questions/51811348/find-manually-registered-obfuscated-native-function-address) ``` var RevealNativeMethods = function() { var pSize = Process.pointerSize; var env = Java.vm.getEnv(); var RegisterNatives = 215, FindClassIndex = 6; // search "215" @ https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/functions.html var jclassAddress2NameMap = {}; function getNativeAddress(idx) { return env.handle.readPointer().add(idx * pSize).readPointer(); } // intercepting FindClass to populate Map Interceptor.attach(getNativeAddress(FindClassIndex), { onEnter: function(args) { jclassAddress2NameMap[args[0]] = args[1].readCString(); } }); // RegisterNative(jClass*, .., JNINativeMethod *methods[nMethods], uint nMethods) // https://android.googlesource.com/platform/libnativehelper/+/master/include_jni/jni.h#977 Interceptor.attach(getNativeAddress(RegisterNatives), { onEnter: function(args) { for (var i = 0, nMethods = parseInt(args[3]); i < nMethods; i++) { /* https://android.googlesource.com/platform/libnativehelper/+/master/include_jni/jni.h#129 typedef struct { const char* name; const char* signature; void* fnPtr; } JNINativeMethod; */ var structSize = pSize * 3; // = sizeof(JNINativeMethod) var methodsPtr = ptr(args[2]); var signature = methodsPtr.add(i * structSize + pSize).readPointer(); var fnPtr = methodsPtr.add(i * structSize + (pSize * 2)).readPointer(); // void* fnPtr var jClass = jclassAddress2NameMap[args[0]].split('/'); var methodName = methodsPtr.add(i * structSize).readPointer().readCString(); console.log('\x1b[3' + '6;01' + 'm', JSON.stringify({ module: DebugSymbol.fromAddress(fnPtr)['moduleName'], // https://www.frida.re/docs/javascript-api/#debugsymbol package: jClass.slice(0, -1).join('.'), class: jClass[jClass.length - 1], method: methodName, // methodsPtr.readPointer().readCString(), // char* name signature: signature.readCString(), // char* signature TODO Java bytecode signature parser { Z: 'boolean', B: 'byte', C: 'char', S: 'short', I: 'int', J: 'long', F: 'float', D: 'double', L: 'fully-qualified-class;', '[': 'array' } https://github.com/skylot/jadx/blob/master/jadx-core/src/main/java/jadx/core/dex/nodes/parser/SignatureParser.java address: fnPtr }), '\x1b[39;49;00m'); } } }); } Java.perform(RevealNativeMethods); ``` @OldVersion ``` var fIntercepted = false; function revealNativeMethods() { if (fIntercepted === true) { return; } var jclassAddress2NameMap = {}; var androidRunTimeSharedLibrary = "libart.so"; // may change between devices Module.enumerateSymbolsSync(androidRunTimeSharedLibrary).forEach(function(symbol){ switch (symbol.name) { case "_ZN3art3JNI21RegisterNativeMethodsEP7_JNIEnvP7_jclassPK15JNINativeMethodib": /* $ c++filt "_ZN3art3JNI21RegisterNativeMethodsEP7_JNIEnvP7_jclassPK15JNINativeMethodib" art::JNI::RegisterNativeMethods(_JNIEnv*, _jclass*, JNINativeMethod const*, int, bool) */ var RegisterNativeMethodsPtr = symbol.address; console.log("RegisterNativeMethods is at " + RegisterNativeMethodsPtr); Interceptor.attach(RegisterNativeMethodsPtr, { onEnter: function(args) { var methodsPtr = ptr(args[2]); var methodCount = parseInt(args[3]); for (var i = 0; i < methodCount; i++) { var pSize = Process.pointerSize; /* https://android.googlesource.com/platform/libnativehelper/+/master/include_jni/jni.h#129 typedef struct { const char* name; const char* signature; void* fnPtr; } JNINativeMethod; */ var structSize = pSize * 3; // JNINativeMethod contains 3 pointers var namePtr = Memory.readPointer(methodsPtr.add(i * structSize)); var sigPtr = Memory.readPointer(methodsPtr.add(i * structSize + pSize)); var fnPtrPtr = Memory.readPointer(methodsPtr.add(i * structSize + (pSize * 2))); // output schema: className#methodName(arguments)returnVal@address console.log( // package & class, replacing forward slash with dot for convenience jclassAddress2NameMap[args[0]].replace(/\//g, '.') + '#' + Memory.readCString(namePtr) + // method Memory.readCString(sigPtr) + // signature (arguments & return type) '@' + fnPtrPtr // C side address ); } }, onLeave: function (ignoredReturnValue) {} }); break; case "_ZN3art3JNI9FindClassEP7_JNIEnvPKc": // art::JNI::FindClass Interceptor.attach(symbol.address, { onEnter: function(args) { if (args[1] != null) { jclassAddress2NameMap[args[0]] = Memory.readCString(args[1]); } }, onLeave: function (ignoredReturnValue) {} }); break; } }); fIntercepted = true; } Java.perform(revealNativeMethods); ```
[⬆ 回到顶部](#table-of-contents) #### 记录方法参数 ``` def on_message(m, _data): if m['type'] == 'send': print(m['payload']) elif m['type'] == 'error': print(m) def switch(argument_key, idx): """ c/c++ variable type to javascript reader switch implementation # TODO handle other arguments, [long, longlong..] :param argument_key: variable type :param idx: index in symbols array :return: javascript to read the type of variable """ argument_key = argument_key.replace(' ', '') return '%d: %s' % (idx, { 'int': 'args[%d].toInt32(),', 'unsignedint': 'args[%d].toInt32(),', 'std::string': 'Memory.readUtf8String(Memory.readPointer(args[%d])),', 'bool': 'Boolean(args[%d]),' }[argument_key] % idx) def list_symbols_from_object_files(module_id): import subprocess return subprocess.getoutput('nm --demangle --dynamic %s' % module_id) def parse_nm_output(nm_stdout, symbols): for line in nm_stdout.splitlines(): split = line.split() open_parenthesis_idx = line.find('(') raw_arguments = [] if open_parenthesis_idx == -1 else line[open_parenthesis_idx + 1:-1] if len(raw_arguments) > 0: # ignore methods without arguments raw_argument_list = raw_arguments.split(',') symbols.append({ 'address': split[0], 'type': split[1], # @see Symbol Type Table 'name': split[2][:split[2].find('(')], # method name 'args': raw_argument_list }) def get_js_script(method, module_id): js_script = """ var moduleName = "{{moduleName}}", nativeFuncAddr = {{methodAddress}}; Interceptor.attach(Module.findExportByName(null, "dlopen"), { onEnter: function(args) { this.lib = Memory.readUtf8String(args[0]); console.log("[*] dlopen called with: " + this.lib); }, onLeave: function(retval) { if (this.lib.endsWith(moduleName)) { Interceptor.attach(Module.findBaseAddress(moduleName).add(nativeFuncAddr), { onEnter: function(args) { console.log("[*] hook invoked", JSON.stringify({{arguments}}, null, '\t')); } }); } } }); """ replace_map = { '{{moduleName}}': module_id, '{{methodAddress}}': '0x' + method['address'], '{{arguments}}': '{' + ''.join([switch(method['args'][i], i + 1) for i in range(len(method['args']))]) + '}' } for k, v in replace_map.items(): js_script = js_script.replace(k, v) print('[+] JS Script:\n', js_script) return js_script def main(app_id, module_id, method): """ $ python3.x+ script.py --method SomeClass::someMethod --app com.company.app --module libfoo.so :param app_id: application identifier / bundle id :param module_id: shared object identifier / known suffix, will iterate loaded modules (@see dlopen) :param method: method/symbol name :return: hook native method and print arguments when invoked """ # TODO extract all app's modules via `adb shell -c 'ls -lR /data/app/' + app_if + '*' | grep "\.so"` nm_stdout = list_symbols_from_object_files(module_id) symbols = [] parse_nm_output(nm_stdout, symbols) selection_idx = None for idx, symbol in enumerate(symbols): if method is None: # if --method flag is not passed print("%4d) %s (%d)" % (idx, symbol['name'], len(symbol['args']))) elif method == symbol['name']: selection_idx = idx break if selection_idx is None: if method is None: selection_idx = input("Enter symbol number: ") else: print('[+] Method not found, remove method flag to get list of methods to select from, `nm` stdout:') print(nm_stdout) exit(2) method = symbols[int(selection_idx)] print('[+] Selected method: %s' % method['name']) print('[+] Method arguments: %s' % method['args']) from frida import get_usb_device device = get_usb_device() pid = device.spawn([app_id]) session = device.attach(pid) script = session.create_script(get_js_script(method, module_id)) script.on('message', on_message) script.load() device.resume(app_id) # keep hook alive from sys import stdin stdin.read() if __name__ == '__main__': from argparse import ArgumentParser parser = ArgumentParser() parser.add_argument('--app', help='app identifier "com.company.app"') parser.add_argument('--module', help='loaded module name "libfoo.2.so"') parser.add_argument('--method', help='method name "SomeClass::someMethod", if empty it will print select-list') args = parser.parse_args() main(args.app, args.module, args.method) ```
[⬆ 回到顶部](#table-of-contents) #### 枚举已加载的类 并保存到名为 `pkg.classes` 的文件中 ``` $ frida -U com.pkg -qe 'Java.perform(function(){Java.enumerateLoadedClasses({"onMatch":function(c){console.log(c);}});});' -o pkg.classes ```
[⬆ 回到顶部](#table-of-contents) #### 类描述 获取类方法和成员。 ``` Object.getOwnPropertyNames(Java.use('com.company.CustomClass').__proto__).join('\n\t') ``` 如果存在名称冲突,即方法和成员同名,则会向成员添加下划线。[来源](https://github.com/frida/frida-java/pull/21) ``` let fieldJsName = env.stringFromJni(fieldName); while (jsMethods.hasOwnProperty(fieldJsName)) { fieldJsName = '_' + fieldJsName; } ```
[⬆ 回到顶部](#table-of-contents) #### 关闭 WiFi 它将在第一个 Activity 创建时关闭 WiFi。 ``` var WifiManager = Java.use("android.net.wifi.WifiManager"); Java.use("android.app.Activity").onCreate.overload("android.os.Bundle").implementation = function(bundle) { var wManager = Java.cast(this.getSystemService("wifi"), WifiManager); console.log('isWifiEnabled ?', wManager.isWifiEnabled()); wManager.setWifiEnabled(false); this.$init(bundle); } ```
[⬆ 回到顶部](#table-of-contents) #### 设置代理 它将使用提供的 IP 地址和端口设置系统级代理。 ``` var ActivityThread = Java.use('android.app.ActivityThread'); var ConnectivityManager = Java.use('android.net.ConnectivityManager'); var ProxyInfo = Java.use('android.net.ProxyInfo'); var proxyInfo = ProxyInfo.$new('192.168.1.10', 8080, ''); // change to null in order to disable the proxy. var context = ActivityThread.currentApplication().getApplicationContext(); var connectivityManager = Java.cast(context.getSystemService('connectivity'), ConnectivityManager); connectivityManager.setGlobalProxy(proxyInfo); ```
[⬆ 回到顶部](#table-of-contents) #### 获取 IMEI 也可以 Hook 并更改 IMEI。 ``` function getIMEI(){ console.log('IMEI =', Java.use("android.telephony.TelephonyManager").$new().getDeviceId()); } Java.perform(getIMEI) ```
[⬆ 回到顶部](#table-of-contents) #### Hook io InputStream Hook `InputputStream` 并将缓冲区打印为带有字符限制和排除列表的 `ascii`。 ``` function binaryToHexToAscii(array, readLimit) { var result = []; // read 100 bytes #performance readLimit = readLimit || 100; for (var i = 0; i < readLimit; ++i) { result.push(String.fromCharCode( // hex2ascii part parseInt( ('0' + (array[i] & 0xFF).toString(16)).slice(-2), // binary2hex part 16 ) )); } return result.join(''); } function hookInputStream() { Java.use('java.io.InputStream')['read'].overload('[B').implementation = function(b) { // execute original and save return value var retval = this.read(b); var resp = binaryToHexToAscii(b); // conditions to not print garbage packets var reExcludeList = new RegExp(['Mmm'/*, 'Ping' /*, ' Yo'*/].join('|')); if ( ! reExcludeList.test(resp) ) { console.log(resp); } var reIncludeList = new RegExp(['AAA', 'BBB', 'CCC'].join('|')); if ( reIncludeList.test(resp) ) { send( binaryToHexToAscii(b, 1200) ); } return retval; }; } Java.perform(hookInputStream); ```
[⬆ 回到顶部](#table-of-contents) #### Android 弹出 Toast ``` // 0 = // https://developer.android.com/reference/android/widget/Toast#LENGTH_LONG Java.scheduleOnMainThread(() => { Java.use("android.widget.Toast") .makeText(Java.use("android.app.ActivityThread").currentApplication().getApplicationContext(), Java.use("java.lang.StringBuilder").$new("Text to Toast here"), 0).show(); }); ```
[⬆ 回到顶部](#table-of-contents) #### 等待条件 等待直到特定 DLL 在 Unity 应用中加载,可以实现热插拔。 ``` var awaitForCondition = function(callback) { var int = setInterval(function() { if (Module.findExportByName(null, "mono_get_root_domain")) { clearInterval(int); callback(); return; } }, 0); } function hook() { Interceptor.attach(Module.findExportByName(null, "mono_assembly_load_from_full"), { onEnter: function(args) { this._dll = Memory.readUtf8String(ptr(args[1])); console.log('[*]', this._dll); }, onLeave: function(retval) { if (this._dll.endsWith("Assembly-CSharp.dll")) { console.log(JSON.stringify({ retval: retval, name: this._dll }, null, 2)); } } }); } Java.perform(awaitForCondition(hook)); ```
[⬆ 回到顶部](#table-of-contents) #### Webview URLS 当 WebView 切换 URL 时进行记录。 ``` Java.use("android.webkit.WebView").loadUrl.overload("java.lang.String").implementation = function (s) { send(s.toString()); this.loadUrl.overload("java.lang.String").call(this, s); }; ```
[⬆ 回到顶部](#table-of-contents) #### 打印运行时字符串 Hook StringBuilder/Buffer 的 `toString` 并打印堆栈跟踪。 ``` Java.perform(function() { ['java.lang.StringBuilder', 'java.lang.StringBuffer'].forEach(function(clazz, i) { console.log('[?] ' + i + ' = ' + clazz); var func = 'toString'; Java.use(clazz)[func].implementation = function() { var ret = this[func](); if (ret.indexOf('') != -1) { // print stacktrace if return value contains specific string Java.perform(function() { var jAndroidLog = Java.use("android.util.Log"), jException = Java.use("java.lang.Exception"); console.log( jAndroidLog.getStackTraceString( jException.$new() ) ); }); } send('[' + i + '] ' + ret); return ret; } }); }); ```
[⬆ 回到顶部](#table-of-contents) #### 打印 SharedPreferences 更新 ``` Java.perform(function() { var shared_pref_class = Java.use('android.app.SharedPreferencesImpl$EditorImpl'); shared_pref_class.putString.overload('java.lang.String', 'java.lang.String').implementation = function(k, v) { console.log('Shared preference updated: ', k, '=', v); return this.putString(k, v); } shared_pref_class.putInt.overload('java.lang.String', 'int').implementation = function(k, v) { console.log('Shared preference updated: ', k, '=', v); return this.putInt(k, v); } shared_pref_class.putFloat.overload('java.lang.String', 'float').implementation = function(k, v) { console.log('Shared preference updated: ', k, '=', v); return this.putFloat(k, v); } shared_pref_class.putBoolean.overload('java.lang.String', 'boolean').implementation = function(k, v) { console.log('Shared preference updated: ', k, '=', v); return this.putBoolean(k, v); } shared_pref_class.putLong.overload('java.lang.String', 'long').implementation = function(k, v) { console.log('Shared preference updated: ', k, '=', v); return this.putLong(k, v); } shared_pref_class.putStringSet.overload('java.lang.String', java.util.Set).implementation = function(k, v) { console.log('Shared preference updated: ', k, '=', v); return this.putStringSet(k, v); } }); ```
[⬆ 回到顶部](#table-of-contents) #### 字符串比较 ``` Java.perform(function() { 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(str.toString.call(this) + ' == ' + obj.toString() + ' ? ' + response); } } return response; } }); ```
[⬆ 回到顶部](#table-of-contents) #### 通过地址 Hook JNI 通过模块名和方法地址 Hook native 方法并打印参数。 ``` var moduleName = "libfoo.so"; var nativeFuncAddr = 0x1234; // $ nm --demangle --dynamic libfoo.so | grep "Class::method(" Interceptor.attach(Module.findExportByName(null, "dlopen"), { onEnter: function(args) { this.lib = Memory.readUtf8String(args[0]); console.log("dlopen called with: " + this.lib); }, onLeave: function(retval) { if (this.lib.endsWith(moduleName)) { console.log("ret: " + retval); var baseAddr = Module.findBaseAddress(moduleName); Interceptor.attach(baseAddr.add(nativeFuncAddr), { onEnter: function(args) { console.log("[-] hook invoked"); console.log(JSON.stringify({ a1: args[1].toInt32(), a2: Memory.readUtf8String(Memory.readPointer(args[2])), a3: Boolean(args[3]) }, null, '\t')); } }); } } }); ```
[⬆ 回到顶部](#table-of-contents) #### Hook 构造函数 ``` Java.use('java.lang.StringBuilder').$init.overload('java.lang.String').implementation = function(stringArgument) { console.log("c'tor"); return this.$init(stringArgument); }; ```
[⬆ 回到顶部](#table-of-contents) #### Hook 反射 `java.lang.reflect.Method#invoke(Object obj, Object... args, boolean bool)` ``` Java.use('java.lang.reflect.Method').invoke.overload('java.lang.Object', '[Ljava.lang.Object;', 'boolean').implementation = function(a,b,c) { console.log('hooked!', a, b, c); return this.invoke(a,b,c); }; ```
[⬆ 回到顶部](#table-of-contents) #### 追踪类 追踪类方法,带有漂亮的颜色以及打印为 JSON 和堆栈跟踪的选项。 TODO 添加对构造函数的追踪。 ``` var Color = { RESET: "\x1b[39;49;00m", Black: "0;01", Blue: "4;01", Cyan: "6;01", Gray: "7;11", Green: "2;01", Purple: "5;01", Red: "1;01", Yellow: "3;01", Light: { Black: "0;11", Blue: "4;11", Cyan: "6;11", Gray: "7;01", Green: "2;11", Purple: "5;11", Red: "1;11", Yellow: "3;11" } }; /** * * @param input. * If an object is passed it will print as json * @param kwargs options map { * -l level: string; log/warn/error * -i indent: boolean; print JSON prettify * -c color: @see ColorMap * } */ var LOG = function (input, kwargs) { kwargs = kwargs || {}; var logLevel = kwargs['l'] || 'log', colorPrefix = '\x1b[3', colorSuffix = 'm'; if (typeof input === 'object') input = JSON.stringify(input, null, kwargs['i'] ? 2 : null); if (kwargs['c']) input = colorPrefix + kwargs['c'] + colorSuffix + input + Color.RESET; console[logLevel](input); }; var printBacktrace = function () { Java.perform(function() { var android_util_Log = Java.use('android.util.Log'), java_lang_Exception = Java.use('java.lang.Exception'); // getting stacktrace by throwing an exception LOG(android_util_Log.getStackTraceString(java_lang_Exception.$new()), { c: Color.Gray }); }); }; function traceClass(targetClass) { var hook; try { hook = Java.use(targetClass); } catch (e) { console.error("trace class failed", e); return; } var methods = hook.class.getDeclaredMethods(); hook.$dispose(); var parsedMethods = []; methods.forEach(function (method) { var methodStr = method.toString(); var methodReplace = methodStr.replace(targetClass + ".", "TOKEN").match(/\sTOKEN(.*)\(/)[1]; parsedMethods.push(methodReplace); }); uniqBy(parsedMethods, JSON.stringify).forEach(function (targetMethod) { traceMethod(targetClass + '.' + targetMethod); }); } function traceMethod(targetClassMethod) { try { var delim = targetClassMethod.lastIndexOf('.'); if (delim === -1) return; var targetClass = targetClassMethod.slice(0, delim); var targetMethod = targetClassMethod.slice(delim + 1, targetClassMethod.length); var hook = Java.use(targetClass); var overloadCount = hook[targetMethod].overloads.length; LOG({ tracing: targetClassMethod, overloaded: overloadCount }, { c: Color.Green }); for (var i = 0; i < overloadCount; i++) { hook[targetMethod].overloads[i].implementation = function () { var log = { '#': targetClassMethod, args: [] }; for (var j = 0; j < arguments.length; j++) { var arg = arguments[j]; // quick&dirty fix for java.io.StringWriter char[].toString() impl because frida prints [object Object] if (j === 0 && arguments[j]) { if (arguments[j].toString() === '[object Object]') { var s = []; for (var k = 0, l = arguments[j].length; k < l; k++) { s.push(arguments[j][k]); } arg = s.join(''); } } log.args.push({ i: j, o: arg, s: arg ? arg.toString(): 'null'}); } var retval; try { retval = this[targetMethod].apply(this, arguments); // might crash (Frida bug?) log.returns = { val: retval, str: retval ? retval.toString() : null }; } catch (e) { console.error(e); } LOG(log, { c: Color.Blue }); return retval; } } } catch(error) { LOG({ tracing: targetClassMethod, "overloaded": 0}, { c: Color.Red }); } } // remove duplicates from array function uniqBy(array, key) { var seen = {}; return array.filter(function (item) { var k = key(item); return seen.hasOwnProperty(k) ? false : (seen[k] = true); }); } var Main = function() { Java.perform(function () { // avoid java.lang.ClassNotFoundException [ // "java.io.File", 'java.net.Socket' ].forEach(traceClass); Java.use('java.net.Socket').isConnected.overload().implementation = function () { LOG('Socket.isConnected.overload', { c: Color.Light.Cyan }); printBacktrace(); return true; } }); }; Java.perform(Main); ```
[⬆ 回到顶部](#table-of-contents) #### 获取 Android ID [ANDROID_ID]( ) 在 Android 的每个应用中都是唯一的。
```
function getContext() {
return Java.use('android.app.ActivityThread').currentApplication().getApplicationContext().getContentResolver();
}
function logAndroidId() {
console.log('[-]', Java.use('android.provider.Settings$Secure').getString(getContext(), 'android_id'));
}
```
[⬆ 回到顶部](#table-of-contents) #### 修改位置 ``` Java.perform(() => { var Location = Java.use('android.location.Location'); Location.getLatitude.implementation = function() { return LATITUDE; } Location.getLongitude.implementation = function() { return LONGITUDE; } }) ```
[⬆ 回到顶部](#table-of-contents) #### 绕过 FLAG_SECURE 绕过截屏防护 [stackoverflow 问题](https://stackoverflow.com/questions/9822076/how-do-i-prevent-android-taking-a-screenshot-when-my-app-goes-to-the-background) ``` Java.perform(function() { Java.use('android.view.SurfaceView').setSecure.overload('boolean').implementation = function(flag){ console.log('[1] flag:', flag); this.call(false); }; var LayoutParams = Java.use('android.view.WindowManager$LayoutParams'); Java.use('android.view.ViewWindow').setFlags.overload('int', 'int').implementation = function(flags, mask){ console.log('flag secure: ', LayoutParams.FLAG_SECURE.value); console.log('before:', flags); flags = (flags.value & ~LayoutParams.FLAG_SECURE.value); console.log('after:', flags); this.call(this, flags, mask); }; }); ```
[⬆ 回到顶部](#table-of-contents) #### SharedPreferences 更新 ``` function notifyNewSharedPreference() { Java.use('android.app.SharedPreferencesImpl$EditorImpl').putString.overload('java.lang.String', 'java.lang.String').implementation = function(k, v) { console.log('[SharedPreferencesImpl]', k, '=', v); return this.putString(k, v); } } ```
[⬆ 回到顶部](#table-of-contents) #### Hook 重载 ``` function hookOverloads(className, func) { var clazz = Java.use(className); var overloads = clazz[func].overloads; for (var i in overloads) { if (overloads[i].hasOwnProperty('argumentTypes') || overloads[i]['argumentTypes'] != undefined) { var parameters = []; var curArgumentTypes = overloads[i].argumentTypes, args = [], argLog = '['; for (var j in curArgumentTypes) { var cName = curArgumentTypes[j].className; parameters.push(cName); argLog += "'(" + cName + ") ' + v" + j + ","; args.push('v' + j); } argLog += ']'; var script = "var ret = this." + func + '(' + args.join(',') + ") || '';\n" + "console.log(JSON.stringify(" + argLog + "));\n" + "return ret;" args.push(script); clazz[func].overload.apply(this, parameters).implementation = Function.apply(null, args); } } } Java.perform(function() { hookOverloads('java.lang.StringBuilder', '$init'); }) ```
[⬆ 回到顶部](#table-of-contents) #### 注册广播接收器 ``` Java.perform(() => { const MyBroadcastReceiver = Java.registerClass({ name: 'MyBroadcastReceiver', superClass: Java.use('android.content.BroadcastReceiver'), methods: { onReceive: [{ returnType: 'void', argumentTypes: ['android.content.Context', 'android.content.Intent'], implementation: function(context, intent) { // .. } }] }, }); let ctx = Java.use('android.app.ActivityThread').currentApplication().getApplicationContext(); ctx.registerReceiver(MyBroadcastReceiver.$new(), Java.use('android.content.IntentFilter').$new('com.example.JAVA_TO_AGENT')); }); ```
[⬆ 回到顶部](#table-of-contents) #### 列出实现接口的类 ``` function listClassesImplementsInterface(aInterface) { let classLoaders = Java.enumerateClassLoadersSync() Java.enumerateLoadedClassesSync().forEach(className => { for (let i = 0; i < classLoaders.length; i++) { let classLoader = classLoaders[i] Java.classFactory.loader = classLoader try { let jclass = Java.use(className).class let ifaces = jclass.getInterfaces().toString() jclass = null if (ifaces.indexOf(aInterface) != -1) { console.log(JSON.stringify({ name: className, loader: classLoader.toString(), interfaces: ifaces })) break // we found one ClassLoader, that's enough } } catch (e) { // continue to next ClassLoader } } }) } ```
[⬆ 回到顶部](#table-of-contents) #### 增加步数 ``` Java.perform(() => { var customSensorEventListener = null; var curSteps = 0; var totalNumberOfRequiredSteps = 10000; function incSteps() { Java.perform(() => { var sEvent = Java.use('android.hardware.SensorEvent').$new(1); sEvent.values.values = Java.array('float', [curSteps]); // https://developer.android.com/reference/android/hardware/SensorEvent#values sEvent.timestamp = Java.use('java.lang.Long').$new(Java.use('java.lang.System').nanoTime()); sEvent.accuracy = Java.use('java.lang.Integer').$new(3); // https://developer.android.com/reference/android/hardware/SensorManager#SENSOR_STATUS_ACCURACY_HIGH customSensorEventListener.onSensorChanged(sEvent); if (curSteps < totalNumberOfRequiredSteps) { setTimeout(() => { curSteps += 50; incSteps(); }, 1500) } }); } Java.choose('.CustomSensorEventListener', { // class that implements SensorEventListener onMatch: function (instance) { customSensorEventListener = instance; }, onComplete: function () { incSteps(); } }); }); ```
[⬆ 回到顶部](#table-of-contents) #### OS 日志 ``` var m = 'libsystem_trace.dylib'; // bool os_log_type_enabled(os_log_t oslog, os_log_type_t type); var isEnabledFunc = Module.findExportByName(m, 'os_log_type_enabled'); // _os_log_impl(void *dso, os_log_t log, os_log_type_t type, const char *format, uint8_t *buf, unsigned int size); var logFunc = Module.findExportByName(m, '_os_log_impl'); // Enable all logs Interceptor.attach(isEnabledFunc, { onLeave: function (ret) { ret.replace(0x1); } }); Interceptor.attach(logFunc, { onEnter: function (a) { /* OS_ENUM(os_log_type, uint8_t, OS_LOG_TYPE_DEFAULT = 0x00, OS_LOG_TYPE_INFO = 0x01, OS_LOG_TYPE_DEBUG = 0x02, OS_LOG_TYPE_ERROR = 0x10, OS_LOG_TYPE_FAULT = 0x11); */ var type = a[2]; var format = a[3]; if (type !== 0x2) { console.log(JSON.stringify({ type: type, format: format.readCString(), //buf: a[4].readPointer().readCString() // TODO }, null, 2)); } } }) ```
[⬆ 回到顶部](#table-of-contents) #### iOS 警告框 ``` var UIAlertController = ObjC.classes.UIAlertController; var UIAlertAction = ObjC.classes.UIAlertAction; var UIApplication = ObjC.classes.UIApplication; var handler = new ObjC.Block({ retType: 'void', argTypes: ['object'], implementation: function () {} }); ObjC.schedule(ObjC.mainQueue, function () { var alert = UIAlertController.alertControllerWithTitle_message_preferredStyle_('Frida', 'Hello from Frida', 1); var defaultAction = UIAlertAction.actionWithTitle_style_handler_('OK', 0, handler); alert.addAction_(defaultAction); // Instead of using `ObjC.choose()` and looking for UIViewController instances on the heap, we have direct access through UIApplication: UIApplication.sharedApplication().keyWindow().rootViewController().presentViewController_animated_completion_(alert, true, NULL); }) ```
[⬆ 回到顶部](#table-of-contents) #### 文件访问 记录每次文件打开 ``` Interceptor.attach(ObjC.classes.NSFileManager['- fileExistsAtPath:'].implementation, { onEnter: function (args) { console.log('open' , ObjC.Object(args[2]).toString()); } }); ```
[⬆ 回到顶部](#table-of-contents) #### 观察类 ``` function observeClass(name) { var k = ObjC.classes[name]; k.$ownMethods.forEach(function(m) { var impl = k[m].implementation; console.log('Observing ' + name + ' ' + m); Interceptor.attach(impl, { onEnter: function(a) { this.log = []; this.log.push('(' + a[0] + ',' + Memory.readUtf8String(a[1]) + ') ' + name + ' ' + m); if (m.indexOf(':') !== -1) { var params = m.split(':'); params[0] = params[0].split(' ')[1]; for (var i = 0; i < params.length - 1; i++) { try { this.log.push(params[i] + ': ' + new ObjC.Object(a[2 + i]).toString()); } catch (e) { this.log.push(params[i] + ': ' + a[2 + i].toString()); } } } this.log.push( Thread.backtrace(this.context, Backtracer.ACCURATE) .map(DebugSymbol.fromAddress) .join('\n') ); }, onLeave: function(r) { try { this.log.push('RET: ' + new ObjC.Object(r).toString()); } catch (e) { this.log.push('RET: ' + r.toString()); } console.log(this.log.join('\n') + '\n'); } }); }); } ```
[⬆ 回到顶部](#table-of-contents) #### 查找 iOS 应用 UUID 当附加到应用时,通过读取每个应用容器下的 plist 文件来获取特定路径的 UUID。 ``` var PLACEHOLDER = '{UUID}'; function extractUUIDfromPath(path) { var bundleIdentifier = String(ObjC.classes.NSBundle.mainBundle().objectForInfoDictionaryKey_('CFBundleIdentifier')); var path_prefix = path.substr(0, path.indexOf(PLACEHOLDER)); var plist_metadata = '/.com.apple.mobile_container_manager.metadata.plist'; var errorPtr = Memory.alloc(Process.pointerSize); Memory.writePointer(errorPtr, NULL); var folders = ObjC.classes.NSFileManager.defaultManager().contentsOfDirectoryAtPath_error_(path_prefix, errorPtr); var error = Memory.readPointer(errorPtr); if (errorPtr) console.error( new ObjC.Object( error ) ); for (var i = 0, l = folders.count(); i < l; i++) { var uuid = folders.objectAtIndex_(i); var metadata = path_prefix + uuid + plist_metadata; var dict = ObjC.classes.NSMutableDictionary.alloc().initWithContentsOfFile_(metadata); var enumerator = dict.keyEnumerator(); var key; while ((key = enumerator.nextObject()) !== null) { if (key == 'MCMMetadataIdentifier') { var appId = String(dict.objectForKey_(key)); if (appId.indexOf(bundleIdentifier) != -1) { return path.replace(PLACEHOLDER, uuid); } } } } } console.log( extractUUIDfromPath('/var/mobile/Containers/Data/Application/' + PLACEHOLDER + '/Documents') ); ```
[⬆ 回到顶部](#table-of-contents) #### 提取 Cookies ``` var cookieJar = {}; var cookies = ObjC.classes.NSHTTPCookieStorage.sharedHTTPCookieStorage().cookies(); for (var i = 0, l = cookies.count(); i < l; i++) { var cookie = cookies['- objectAtIndex:'](i); cookieJar[cookie.Name()] = cookie.Value().toString(); // ["- expiresDate"]().toString() } console.log(JSON.stringify(cookieJar, null, 2)); ```
[⬆ 回到顶部](#table-of-contents) #### 描述类成员 打印每个类实例的成员映射(包含值) ``` ObjC.choose(ObjC.classes[clazz], { onMatch: function (obj) { console.log('onMatch: ', obj); Object.keys(obj.$ivars).forEach(function(v) { console.log('\t', v, '=', obj.$ivars[v]); }); }, onComplete: function () { console.log('onComplete', arguments.length); } }); ```
[⬆ 回到顶部](#table-of-contents) #### 类层次结构 Object.keys(ObjC.classes) 将列出所有可用的 Objective C 类, 但实际上这将返回当前进程中加载的所有类,包括系统框架。 如果我们想要像 weak_classdump 那样仅列出可执行文件本身的类,Objective C 运行时已经提供了这样的函数 [objc_copyClassNamesForImage](#https://developer.apple.com/documentation/objectivec/1418485-objc_copyclassnamesforimage?language=objc) ``` var objc_copyClassNamesForImage = new NativeFunction( Module.findExportByName(null, 'objc_copyClassNamesForImage'), 'pointer', ['pointer', 'pointer'] ); var free = new NativeFunction(Module.findExportByName(null, 'free'), 'void', ['pointer']); var classes = new Array(count); var p = Memory.alloc(Process.pointerSize); Memory.writeUInt(p, 0); var path = ObjC.classes.NSBundle.mainBundle().executablePath().UTF8String(); var pPath = Memory.allocUtf8String(path); var pClasses = objc_copyClassNamesForImage(pPath, p); var count = Memory.readUInt(p); for (var i = 0; i < count; i++) { var pClassName = Memory.readPointer(pClasses.add(i * Process.pointerSize)); classes[i] = Memory.readUtf8String(pClassName); } free(pClasses); var tree = {}; classes.forEach(function(name) { var clazz = ObjC.classes[name]; var chain = [name]; while (clazz = clazz.$superClass) { chain.unshift(clazz.$className); } var node = tree; chain.forEach(function(clazz) { node[clazz] = node[clazz] || {}; node = node[clazz]; }); }); send(tree); ```
[⬆ 回到顶部](#table-of-contents) #### Hook 反射 Hook `objc_msgSend` ``` import frida, sys f = open('/tmp/log', 'w') def on_message(msg, _data): f.write(msg['payload']+'\n') frida_script = """ Interceptor.attach(Module.findExportByName('/usr/lib/libobjc.A.dylib', 'objc_msgSend'), { onEnter: function(args) { var m = Memory.readCString(args[1]); if (m != 'length' && !m.startsWith('_fastC')) send(m); } }); """ device = frida.get_usb_device() pid = device.spawn(["com.example"]) # or .get_frontmost_application() session = device.attach(pid) script = session.create_script(frida_script) script.on('message', on_message) script.load() device.resume(pid) sys.stdin.read() ``` ``` $ sort /tmp/log | uniq -c | sort -n ```
[⬆ 回到顶部](#table-of-contents) #### 拦截整个模块 为了减少与 UI 相关的函数,我使用以下步骤: 1. 使用 `-o /tmp/log1` 将日志输出到文件 2. 使用 `$ sort /tmp/log1 | uniq -c | sort -rn | head -n20 | cut -d# -f2 | paste -sd "," -` 将 MRU(最近最常使用)复制到排除列表 ``` var mName = 'MyModule', excludeList = ['Alot', 'Of', 'UI', 'Related', 'Functions']; Module.enumerateExportsSync(mName) .filter(function(e) { var fromTypeFunction = e.type == 'function';· var notInExcludes = excludeList.indexOf(e.name) == -1; return fromTypeFunction && notInExcludes; }) .forEach(function(e) { Interceptor.attach(Module.findExportByName(mName, e.name), { onEnter: function(args) { console.log(mName + "#'" + e.name + "'"); } }) }) ```
[⬆ 回到顶部](#table-of-contents) #### 转储内存段 ``` Process.enumerateRanges('rw-', { onMatch: function (range) { var fname = `/sdcard/${range.base}_dump`; var f = new File(fname, 'wb'); f.write(instance.base.readByteArray(instance.size)); f.flush(); f.close(); console.log(`base=${range.base} size=${range.size} prot=${range.protection} fname=${fname}`); }, onComplete: function () {} }); ```
[⬆ 回到顶部](#table-of-contents) #### 内存扫描 ``` function memscan(str) { Process.enumerateModulesSync().filter(m => m.path.startsWith('/data')).forEach(m => { var pattern = str.split('').map(letter => letter.charCodeAt(0).toString(16)).join(' '); try { var res = Memory.scanSync(m.base, m.size, pattern); if (res.length > 0) console.log(JSON.stringify({m, res})); } catch (e) { console.warn(e); } }); } ``` ``` var memscn = function (str) { Process.enumerateModulesSync().forEach(function (m) { var pattern = str.split('').map(function (l) { return l.charCodeAt(0).toString(16) }).join(' '); try { var res = Memory.scanSync(m.base, m.size, pattern); if (res.length > 0) console.log(JSON.stringify({m, res}, null , 2)); } catch (e) { console.warn(e); } }); } ```
[⬆ 回到顶部](#table-of-contents) #### Stalker ``` var _module = Process.findModuleByName('myModule'); var base = ptr(_module.base); var startTraceOffset = 0xabcd1234, numInstructionsToTrace = 50; var startTrace = base.add(startTraceOffset), endTrace = startTrace.add(4 * (numInstructionsToTrace - 1)); Interceptor.attach(ObjC.classes.CustomClass['- func'].implementation, { onEnter: function (args) { var tid = Process.getCurrentThreadId(); this.tid = tid; console.warn(`onEnter [ ${tid} ]`); Stalker.follow(tid, { transform: function (iterator) { var instruction; while ((instruction = iterator.next()) !== null) { // condition to putCallout if (instruction.address <= endTrace && instruction.address >= startTrace) { // print instruction & registers values iterator.putCallout(function(context) { var offset = ptr(context.pc).sub(base); var inst = Instruction.parse(context.pc).toString(); var modified_inst = inst; inst.replace(/,/g, '').split(' ').forEach(op => { if (op.startsWith('x')) modified_inst = modified_inst.replace(op, context[op]); else if (op.startsWith('w')) modified_inst = modified_inst.replace(op, context[op.replace('w', 'x')]); }); modified_inst = '\x1b[35;01m' + modified_inst + '\x1b[0m'; console.log(`x8=${context.x8} x25=${context.x25} x0=${context.x0} x21=${context.x21}`) console.log(`${offset} ${inst} # ${modified_inst}`); }); } iterator.keep(); } } }) }, onLeave: function (retval) { console.log(`onLeave [ ${this.tid} ]`); // cleanup Stalker.unfollow(this.tid); Stalker.garbageCollect(); } }) ```
[⬆ 回到顶部](#table-of-contents) #### Cpp 反混淆器 ``` $ npm i frida-compile demangler-js -g ``` 添加到你的脚本 ``` const demangle = require('demangler-js').demangle; ... Module.enumerateExportsSync('library.so') .filter(x => x.name.startsWith('_Z')) .forEach(x => { Interceptor.attach(x.address, { onEnter: function (args) { console.log('[-] ' + demangle(x.name)); } }); }); ``` 编译 ``` $ frida-compile script.js -o out.js ``` 运行 ``` $ frida -Uf com.app -l out.js ```
[⬆ 回到顶部](#table-of-contents) #### 早期 Hook 在 DT_INIT_ARRAY 之前设置 Hook ([来源](https://cs.android.com/android/platform/superproject/+/master:bionic/linker/linker_soinfo.cpp;l=386;drc=android-8.0.0_r1?q=call_constructor&ss=android%2Fplatform%2Fsuperproject)) ``` let base; let do_dlopen = null; let call_ctor = null; const target_lib_name = 'targetlib.so'; Process.findModuleByName('linker64').enumerateSymbols().forEach(sym => { if (sym.name.indexOf('do_dlopen') >= 0) { do_dlopen = sym.address; } else if (sym.name.indexOf('call_constructor') >= 0) { call_ctor = sym.address; } }) Interceptor.attach(do_dlopen, function (args) { if (args[0].readUtf8String().indexOf(target_lib_name) >= 0) { Interceptor.attach(call_ctor, function () { const module = Process.findModuleByName(target_lib_name); base = module.base; console.log('loading', target_lib_name, '- base @', base); // DoStuff }) } }) ``` 致谢: [iGio90](https://github.com/iGio90)
[⬆ 回到顶部](#table-of-contents) #### 设备属性 快速粗糙的 iOS 设备属性提取示例 ``` var UIDevice = ObjC.classes.UIDevice.currentDevice(); UIDevice.$ownMethods .filter(function(method) { return method.indexOf(':') == -1 /* filter out methods with parameters */ && method.indexOf('+') == -1 /* filter out public methods */ }) .forEach(function(method) { console.log(method, ':', UIDevice[method]()) }) console.log('executablePath =', ObjC.classes.NSBundle.mainBundle().executablePath().toString()); ``` ``` if (ObjC.available) { var processInfo = ObjC.classes.NSProcessInfo.processInfo(); var versionString = processInfo.operatingSystemVersionString().toString(); // E.g. "Version 13.5 (Build 17F75)" var ver = versionString.split(' '); var version = ver[1]; // E.g. 13.5 console.log("iOS version: " + version); } ```
[⬆ 回到顶部](#table-of-contents) #### 截取屏幕截图 ``` function screenshot() { ObjC.schedule(ObjC.mainQueue, function() { var getNativeFunction = function (ex, retVal, args) { return new NativeFunction(Module.findExportByName('UIKit', ex), retVal, args); }; var api = { UIWindow: ObjC.classes.UIWindow, UIGraphicsBeginImageContextWithOptions: getNativeFunction('UIGraphicsBeginImageContextWithOptions', 'void', [['double', 'double'], 'bool', 'double']), UIGraphicsBeginImageContextWithOptions: getNativeFunction('UIGraphicsBeginImageContextWithOptions', 'void', [['double', 'double'], 'bool', 'double']), UIGraphicsEndImageContext: getNativeFunction('UIGraphicsEndImageContext', 'void', []), UIGraphicsGetImageFromCurrentImageContext: getNativeFunction('UIGraphicsGetImageFromCurrentImageContext', 'pointer', []), UIImagePNGRepresentation: getNativeFunction('UIImagePNGRepresentation', 'pointer', ['pointer']) }; var view = api.UIWindow.keyWindow(); var bounds = view.bounds(); var size = bounds[1]; api.UIGraphicsBeginImageContextWithOptions(size, 0, 0); view.drawViewHierarchyInRect_afterScreenUpdates_(bounds, true); var image = api.UIGraphicsGetImageFromCurrentImageContext(); api.UIGraphicsEndImageContext(); var png = new ObjC.Object(api.UIImagePNGRepresentation(image)); send('screenshot', Memory.readByteArray(png.bytes(), png.length())); }); } rpc.exports = { takescreenshot: screenshot } ``` ``` ... def save_screenshot(d): f = open('/tmp/screenshot.png', 'wb') f.write(d) f.close() def on_message(msg, data): save_screenshot(data) script.exports.takescreenshot() # open screenshot & invoke rpc via input # will take screenshot, open it with eog & wait for export function name to invoke via input
def on_message(msg, data):
if 'payload' in msg:
if msg['payload'] == 'screenshot':
i = '/tmp/screenshot.png'
f = open(i, 'wb')
f.write(data)
f.close()
subprocess.call(['eog', i])
while True:
try:
time.sleep(1)
except KeyboardInterrupt:
script.exports.takescreenshot()
try:
getattr(script.exports, input())()
except (KeyboardInterrupt, frida.core.RPCException) as e:
print('[!]', e)
```
[⬆ 回到顶部](#table-of-contents) #### 记录 SSH 命令 ``` Interceptor.attach(ObjC.classes.NMSSHChannel['- execute:error:timeout:'].implementation, { onEnter: function(args) { this.cmd = ObjC.Object(args[2]).toString(); this.timeout = args[4]; }, onLeave: function(retv) { console.log(`CMD: ${ObjC.Object(args[2]).toString()} Timeout: ${args[4]} Ret: ${retv}`); } }); ```
[⬆ 回到顶部](#table-of-contents) #### 待办事项 - 添加 GIF 和示例 - 添加指向 /scripts 的链接 - 扩展通用 SSL unpinning(SSL 证书锁定解除)适用于 [ios](https://codeshare.frida.re/@dki/ios10-ssl-bypass/) [android 1](https://github.com/Fuzion24/JustTrustMe/blob/master/app/src/main/java/just/trust/me/Main.java) [android
Native
* [`Load C/C++ module`](#load-cpp-module) * [`One time watchpoint`](#one-time-watchpoint) * [`Socket activity`](#socket-activity) * [`Intercept open`](#intercept-open) * [`Execute shell command`](#execute-shell-command) * [`List modules`](#list-modules) * [`Log SQLite query`](#log-sqlite-query) * [`Log method arguments`](#log-method-arguments) * [`Intercept entire module`](#intercept-entire-module) * [`Dump memory segments`](#dump-memory-segments) * [`Memory scan`](#memory-scan) * [`Stalker`](#stalker) * [`Cpp Demangler`](#cpp-demangler) * [`Early hook`](#early-hook)Android
* [`Binder transactions`](#binder-transactions) * [`Get system property`](#system-property-get) * [`Reveal manually registered native symbols`](#reveal-native-methods) * [`Enumerate loaded classes`](#enumerate-loaded-classes) * [`Class description`](#class-description) * [`Turn WiFi off`](#turn-wifi-off) * [`Set proxy`](#set-proxy) * [`Get IMEI`](#get-imei) * [`Hook io InputStream`](#hook-io-inputstream) * [`Android make Toast`](#android-make-toast) * [`Await for specific module to load`](#await-for-condition) * [`Webview URLS`](#webview-urls) * [`Print all runtime strings & stacktrace`](#print-runtime-strings) * [`Print shared preferences updates`](#Print-shared-preferences-updates) * [`String comparison`](#string-comparison) * [`Hook JNI by address`](#hook-jni-by-address) * [`Hook constructor`](#hook-constructor) * [`Hook Java reflection`](#hook-refelaction) * [`Trace class`](#trace-class) * [`Hooking Unity3d`](https://github.com/iddoeldor/mplus) * [`Get Android ID`](#get-android-id) * [`Change location`](#change-location) * [`Bypass FLAG_SECURE`](#bypass-flag_secure) * [`Shared Preferences update`](#shared-preferences-update) * [`Hook all method overloads`](#hook-overloads) * [`Register broadcast receiver`](#register-broadcast-receiver) * [`Increase step count`](#increase-step-count) * [`list classes implements interface with class loaders`](#list-classes-implements-interface) * 文件系统访问 Hook `$ frida --codeshare FrenchYeti/android-file-system-access-hook -f com.example.app --no-pause` * 如何移除/禁用 Java hooks?将 `null` 赋值给 `implementation` 属性。iOS
* [`OS Log`](#os-log) * [`iOS alert box`](#ios-alert-box) * [`File access`](#file-access) * [`Observe class`](#observe-class) * [`Find application UUID`](#find-ios-application-uuid) * [`Extract cookies`](#extract-cookies) * [`Describe class members`](#describe-class-members) * [`Class hierarchy`](#class-hierarchy) * [`Hook refelaction`](#hook-refelaction) * [`Device properties`](#device-properties) * [`Take screenshot`](#take-screenshot) * [`Log SSH commands`](#log-ssh-commands)Windows
Sublime snippets
``` { "scope": "source.js", "completions": [ {"trigger": "fridainterceptor", "contents": "Interceptor.attach(\n ptr,\n {\n onEnter:function(args) {\n\n },\n onLeave: function(retval) {\n\n }\n }\n)"}, {"trigger": "fridaperform", "contents": "function main(){\n console.log('main()');\n}\n\nconsole.log('script loaded');\nJava.perform(main);"}, {"trigger": "fridause", "contents": "var kls = Java.use('kls');"}, {"trigger": "fridahex", "contents": "hexdump(\n ptr,\n {\n offset: 0,\n length: ptr_size\n }\n);" }, {"trigger": "fridabacktrace", "contents": "console.log('called from:\\n' +\n Thread.backtrace(this.context, Backtracer.ACCURATE)\n .map(DebugSymbol.fromAddress).join('\\n') + '\\n'\n);"}, {"trigger": "fridamods", "contents": "var mods = Process.enumerateModules().filter(function(mod){\n return mod.name.includes(\"Vim snippets
列出缩写 `:ab` 通过输入 `key` 和 `JEB
使用键盘快捷键生成 Java 方法 Hook 1. `curl -o ~/$JEB$/scripts/FridaCodeGenerator.py https://raw.githubusercontent.com/iddoeldor/frida-snippets/master/scripts/FridaCodeGenerator.py` 2. 将光标放在 Java 方法的签名处 3. 按下 `Ctrl+Shift+Z` 4. 代码已复制到系统剪贴板(使用 `xclip`)#### 获取 SSL 密钥 ``` var keylog_callback = new NativeCallback((ssl, line) => { send(Memory.readCString(line)); }, 'void', ['pointer', 'pointer']); if (ObjC.available) { var CALLBACK_OFFSET = 0x2A8 if (Memory.readDouble(Module.findExportByName('CoreFoundation', 'kCFCoreFoundationVersionNumber')) >= 1751.108) { CALLBACK_OFFSET = 0x2B8 } Interceptor.attach(Module.findExportByName('libboringssl.dylib', 'SSL_CTX_set_info_callback'), { onEnter(args) { ptr(args[0]).add(CALLBACK_OFFSET).writePointer(keylog_callback) } }) } else if (Java.available) { var set_keylog_callback = new NativeFunction(Module.findExportByName('libssl.so', 'SSL_CTX_set_keylog_callback'), 'void', ['pointer', 'pointer']); Interceptor.attach(Module.findExportByName('libssl.so', 'SSL_CTX_new'), { onLeave(retval) { set_keylog_callback(retval, keylog_callback) } }) } ```
[⬆ 回到顶部](#table-of-contents) #### 加载 CPP 模块 ``` #include
[⬆ 回到顶部](#table-of-contents) #### 单次监视点 拦截 `funcPtr` 并通过使用 `mprotect` 移除权限来记录谁对 `x2` 进行了读/写。 ``` Process.setExceptionHandler(function(exp) { console.warn(JSON.stringify(Object.assign(exp, { _lr: DebugSymbol.fromAddress(exp.context.lr), _pc: DebugSymbol.fromAddress(exp.context.pc) }), null, 2)); Memory.protect(exp.memory.address, Process.pointerSize, 'rw-'); // can also use `new NativeFunction(Module.findExportByName(null, 'mprotect'), 'int', ['pointer', 'uint', 'int'])(parseInt(this.context.x2), 2, 0)` return true; // goto PC }); Interceptor.attach(funcPtr, { onEnter: function (args) { console.log('onEnter', JSON.stringify({ x2: this.context.x2, mprotect_ret: Memory.protect(this.context.x2, 2, '---'), errno: this.errno }, null, 2)); }, onLeave: function (retval) { console.log('onLeave'); } }); ```
输出示例
``` [iOS Device::com.app]-> onEnter { "x2": "0x1c145c6e0", "mprotect_ret": true, "errno": 2 } { "type": "access-violation", "address": "0x1853b0198", "memory": { "operation": "read", "address": "0x1c145c6e0" }, "context": { "lr": "0x100453358", "fp": "0x16fb2e860", "x28": "0x0", "x27": "0x0", "x26": "0x104312600", "x25": "0x0", "x24": "0x0", "x23": "0x0", "x22": "0x0", "x21": "0xb000000422bbda03", "x20": "0x1c4a22560", "x19": "0xb000000422bbda03", "x18": "0x0", "x17": "0x100d25290", "x16": "0x1853b0190", "x15": "0x0", "x14": "0x5", "x13": "0xe5a1c4119597", "x12": "0x10e80ca30", "x11": "0x180000003f", "x10": "0x10e80ca00", "x9": "0x1020ad7c3", "x8": "0x0", "x7": "0x0", "x6": "0x0", "x5": "0x0", "x4": "0xb000000422bbda03", "x3": "0x1c4a22560", "x2": "0x1c145c6e0", "x1": "0x1020ad7c3", "x0": "0x1c145c6e0", "sp": "0x16fb2e790", "pc": "0x1853b0198" }, "nativeContext": "0x16fc42b24" } onLeave ```[⬆ 回到顶部](#table-of-contents) #### Socket 活动 ``` Process .getModuleByName({ linux: 'libc.so', darwin: 'libSystem.B.dylib', windows: 'ws2_32.dll' }[Process.platform]) .enumerateExports().filter(ex => ex.type === 'function' && ['connect', 'recv', 'send', 'read', 'write'].some(prefix => ex.name.indexOf(prefix) === 0)) .forEach(ex => { Interceptor.attach(ex.address, { onEnter: function (args) { var fd = args[0].toInt32(); var socktype = Socket.type(fd); if (socktype !== 'tcp' && socktype !== 'tcp6') return; var address = Socket.peerAddress(fd); if (address === null) return; console.log(fd, ex.name, address.ip + ':' + address.port); } }) }) ```
输出示例
Android 示例 ``` # 将上述 script 封装在 Java.perform 内 $ frida -Uf com.example.app -l script.js --no-pause [Android Model-X::com.example.app]-> 117 write 5.0.2.1:5242 117 read 5.0.2.1:5242 135 write 5.0.2.1:4244 135 read 5.0.2.1:4244 135 read 5.0.2.1:4244 ```[⬆ 回到顶部](#table-of-contents) #### 拦截 Open 拦截 `libc#open` 并在特定文件被打开时记录回溯(backtrace)的示例。 ``` Interceptor.attach(Module.findExportByName("/system/lib/libc.so", "open"), { onEnter: function(args) { this.flag = false; var filename = Memory.readCString(ptr(args[0])); console.log('filename =', filename) if (filename.endsWith(".xml")) { this.flag = true; var backtrace = Thread.backtrace(this.context, Backtracer.ACCURATE).map(DebugSymbol.fromAddress).join("\n\t"); console.log("file name [ " + Memory.readCString(ptr(args[0])) + " ]\nBacktrace:" + backtrace); } }, onLeave: function(retval) { if (this.flag) // passed from onEnter console.warn("\nretval: " + retval); } }); ``` ``` var fds = {}; // for f in /proc/`pidof $APP`/fd/*; do echo $f': 'readlink $f; done Interceptor.attach(Module.findExportByName(null, 'open'), { onEnter: function (args) { var fname = args[0].readCString(); if (fname.endsWith('.jar')) { this.flag = true; this.fname = fname; } }, onLeave: function (retval) { if (this.flag) { fds[retval] = this.fname; } } }); ['read', 'pread', 'readv'].forEach(fnc => { Interceptor.attach(Module.findExportByName(null, fnc), { onEnter: function (args) { var fd = args[0]; if (fd in fds) console.log(`${fnc}: ${fds[fd]} \t${Thread.backtrace(this.context, Backtracer.ACCURATE).map(DebugSymbol.fromAddress).join('\n\t')}`); } }); }); ```
输出示例
拦截 `com.android.chrome` [⬆ 回到顶部](#table-of-contents) #### 执行 Shell 命令 ``` import frida from frida_tools.application import Reactor import threading import click class Shell(object): def __init__(self, argv, env): self._stop_requested = threading.Event() self._reactor = Reactor(run_until_return=lambda reactor: self._stop_requested.wait()) self._device = frida.get_usb_device() self._sessions = set() self._device.on("child-added", lambda child: self._reactor.schedule(lambda: self._on_child_added(child))) self._device.on("child-removed", lambda child: self._reactor.schedule(lambda: self._on_child_removed(child))) self._device.on("output", lambda pid, fd, data: self._reactor.schedule(lambda: self._on_output(pid, fd, data))) self.argv = argv self.env = env self.output = [] # stdout will pushed into array def exec(self): self._reactor.schedule(lambda: self._start()) self._reactor.run() def _start(self): click.secho("✔ spawn(argv={})".format(self.argv), fg='green', dim=True) pid = self._device.spawn(self.argv, env=self.env, stdio='pipe') self._instrument(pid) def _stop_if_idle(self): if len(self._sessions) == 0: self._stop_requested.set() def _instrument(self, pid): click.secho("✔ attach(pid={})".format(pid), fg='green', dim=True) session = self._device.attach(pid) session.on("detached", lambda reason: self._reactor.schedule(lambda: self._on_detached(pid, session, reason))) click.secho("✔ enable_child_gating()", fg='green', dim=True) session.enable_child_gating() # print("✔ resume(pid={})".format(pid)) self._device.resume(pid) self._sessions.add(session) def _on_child_added(self, child): click.secho("⚡ child_added: {}".format(child), fg='green', dim=True) self._instrument(child.pid) @staticmethod def _on_child_removed(child): click.secho("⚡ child_removed: {}".format(child), fg='green', dim=True) def _on_output(self, pid, fd, data): # print("⚡ output: pid={}, fd={}, data={}".format(pid, fd, repr(data))) # fd=0 (input) fd=1(stdout) fd=2(stderr) if fd != 2: self.output.append(data) def _on_detached(self, pid, session, reason): click.secho("⚡ detached: pid={}, reason='{}'".format(pid, reason), fg='green', dim=True) self._sessions.remove(session) self._reactor.schedule(self._stop_if_idle, delay=0.5) @staticmethod def _on_message(pid, message): click.secho("⚡ message: pid={}, payload={}".format(pid, message), fg='green', dim=True) ```
使用示例
列出目录内容: ``` def ls(folder): cmd = Shell(['/bin/sh', '-c', 'ls -la ' + folder], None) cmd.exec() for chunk in cmd.output: print(chunk.strip().decode()) ``` 从 iOS 提取二进制文件 ``` cmd = Shell(['/bin/sh', '-c', 'cat /System/Library/PrivateFrameworks/Example.framework/example'], None) cmd.exec() with open('/tmp/example', 'wb+') as f: f.writelines(cmd.output) # $ file /tmp/example # /tmp/example: Mach-O 64-bit 64-bit architecture=12 executable ```[⬆ 回到顶部](#table-of-contents) #### 列出模块 ``` Process.enumerateModulesSync() .filter(function(m){ return m['path'].toLowerCase().indexOf('app') !=-1 ; }) .forEach(function(m) { console.log(JSON.stringify(m, null, ' ')); // to list exports use Module.enumerateExportsSync(m.name) }); ``` 列出模块和导出表 ``` sudo frida Process --no-pause --eval 'var x={};Process.enumerateModulesSync().forEach(function(m){x[m.name] = Module.enumerateExportsSync(m.name)});x' -q | less +F ```
输出示例
``` { "name": "app_process64", "base": "0x6313a1c000", "size": 40960, "path": "/system/bin/app_process64" } { "name": "libappfuse.so", "base": "0x749ab96000", "size": 53248, "path": "/system/lib64/libappfuse.so" } { "name": "android.hardware.graphics.mapper@2.0.so", "base": "0x749b448000", "size": 90112, "path": "/system/lib64/android.hardware.graphics.mapper@2.0.so" } { "name": "android.hardware.graphics.mapper@2.1.so", "base": "0x749ac9e000", "size": 94208, "path": "/system/lib64/android.hardware.graphics.mapper@2.1.so" } { "name": "android.hardware.graphics.mapper@3.0.so", "base": "0x74981e0000", "size": 98304, "path": "/system/lib64/android.hardware.graphics.mapper@3.0.so" } { "name": "android.hardware.graphics.mapper@2.0-impl-2.1.so", "base": "0x73fb4cc000", "size": 40960, "path": "/vendor/lib64/hw/android.hardware.graphics.mapper@2.0-impl-2.1.so" } { "name": "android.hardware.graphics.mapper@2.0.so", "base": "0x73fb51f000", "size": 90112, "path": "/system/lib64/vndk-sp-29/android.hardware.graphics.mapper@2.0.so" } { "name": "android.hardware.graphics.mapper@2.1.so", "base": "0x73fb542000", "size": 94208, "path": "/system/lib64/vndk-sp-29/android.hardware.graphics.mapper@2.1.so" } { "name": "base.odex", "base": "0x73ab4cd000", "size": 16965632, "path": "/data/app/com.noodlecake.altosadventure-O2YLuwCOq7LbWSkRHkRLcg==/oat/arm64/base.odex" } { "name": "libfrida-gadget.so", "base": "0x73a2c05000", "size": 22876160, "path": "/data/app/com.noodlecake.altosadventure-O2YLuwCOq7LbWSkRHkRLcg==/lib/arm64/libfrida-gadget.so" } { "name": "libmain.so", "base": "0x73fb894000", "size": 73728, "path": "/data/app/com.noodlecake.altosadventure-O2YLuwCOq7LbWSkRHkRLcg==/lib/arm64/libmain.so" } { "name": "libunity.so", "base": "0x739bf88000", "size": 24461312, "path": "/data/app/com.noodlecake.altosadventure-O2YLuwCOq7LbWSkRHkRLcg==/lib/arm64/libunity.so" } { "name": "libil2cpp.so", "base": "0x7396fe6000", "size": 25272320, "path": "/data/app/com.noodlecake.altosadventure-O2YLuwCOq7LbWSkRHkRLcg==/lib/arm64/libil2cpp.so" } { "name": "DynamiteLoader.odex", "base": "0x73b7ee4000", "size": 376832, "path": "/data/user_de/0/com.google.android.gms/app_chimera/m/00000278/oat/arm64/DynamiteLoader.odex" } { "name": "base.odex", "base": "0x72f2ee3000", "size": 166838272, "path": "/data/app/com.google.android.gms-j7RpxBsNAd3ttAYEdp2ahg==/oat/arm64/base.odex" } { "name": "base.odex", "base": "0x7396e6d000", "size": 28672, "path": "/data/app/com.google.android.trichromelibrary_432418133-X7Kc2Mqi-VXkY12N59kGug==/oat/arm64/base.odex" } { "name": "base.odex", "base": "0x724f15e000", "size": 13225984, "path": "/data/app/com.google.android.webview-w6i6OBFZ7T_wK4W4TpDAiQ==/oat/arm64/base.odex" } { "name": "libmonochrome.so", "base": "0x73b8592000", "size": 76673024, "path": "/data/app/com.google.android.webview-w6i6OBFZ7T_wK4W4TpDAiQ==/base.apk!/lib/arm64-v8a/libmonochrome.so" } { "name": "libnativeNoodleNews.so", "base": "0x723add3000", "size": 962560, "path": "/data/app/com.noodlecake.altosadventure-O2YLuwCOq7LbWSkRHkRLcg==/lib/arm64/libnativeNoodleNews.so" } { "name": "libconscrypt_gmscore_jni.so", "base": "0x7206629000", "size": 1130496, "path": "/data/app/com.google.android.gms-j7RpxBsNAd3ttAYEdp2ahg==/base.apk!/lib/arm64-v8a/libconscrypt_gmscore_jni.so" } ```[⬆ 回到顶部](#table-of-contents) #### 记录 SQLite 查询 ``` Interceptor.attach(Module.findExportByName('libsqlite.so', 'sqlite3_prepare16_v2'), { onEnter: function(args) { console.log('DB: ' + Memory.readUtf16String(args[0]) + '\tSQL: ' + Memory.readUtf16String(args[1])); } }); ```
输出示例
TODO[⬆ 回到顶部](#table-of-contents) #### 获取系统属性 ``` Interceptor.attach(Module.findExportByName(null, '__system_property_get'), { onEnter: function (args) { this._name = args[0].readCString(); this._value = args[1]; }, onLeave: function (retval) { console.log(JSON.stringify({ result_length: retval, name: this._name, val: this._value.readCString() })); } }); ```
输出示例
``` {"result_length":"0x0","name":"ro.kernel.android.tracing","val":""} {"result_length":"0x0","name":"ro.config.hw_log","val":""} {"result_length":"0x0","name":"ro.config.hw_module_log","val":""} {"result_length":"0x1","name":"ro.debuggable","val":"0"} {"result_length":"0x1","name":"persist.sys.huawei.debug.on","val":"0"} {"result_length":"0x1","name":"ro.logsystem.usertype","val":"6"} {"result_length":"0x6","name":"ro.board.platform","val":"hi6250"} {"result_length":"0x4","name":"persist.sys.enable_iaware","val":"true"} {"result_length":"0x1","name":"persist.sys.cpuset.enable","val":"1"} {"result_length":"0x4","name":"persist.sys.cpuset.subswitch","val":"1272"} {"result_length":"0x4","name":"persist.sys.boost.durationms","val":"1000"} {"result_length":"0x4","name":"persist.sys.boost.isbigcore","val":"true"} {"result_length":"0x7","name":"persist.sys.boost.freqmin.b","val":"1805000"} {"result_length":"0x4","name":"persist.sys.boost.ipapower","val":"3500"} {"result_length":"0x0","name":"persist.sys.boost.skipframe","val":""} {"result_length":"0x0","name":"persist.sys.boost.byeachfling","val":""} {"result_length":"0x1","name":"debug.force_rtl","val":"0"} {"result_length":"0x0","name":"ro.hardware.gralloc","val":""} {"result_length":"0x6","name":"ro.hardware","val":"hi6250"} {"result_length":"0x0","name":"ro.kernel.qemu","val":""} {"result_length":"0x0","name":"ro.config.hw_force_rotation","val":""} {"result_length":"0x0","name":"persist.fb_auto_alloc","val":""} {"result_length":"0x0","name":"ro.config.hw_lock_res_whitelist","val":""} {"result_length":"0x3","name":"ro.sf.lcd_density","val":"480"} {"result_length":"0x0","name":"persist.sys.dpi","val":""} {"result_length":"0x0","name":"persist.sys.rog.width","val":""} {"result_length":"0x4","name":"dalvik.vm.usejitprofiles","val":"true"} {"result_length":"0x1","name":"debug.atrace.tags.enableflags","val":"0"} {"result_length":"0x1","name":"ro.debuggable","val":"0"} {"result_length":"0x1","name":"debug.force_rtl","val":"0"} {"result_length":"0x0","name":"ro.config.hw_lock_res_whitelist","val":""} .... ```[⬆ 回到顶部](#table-of-contents) #### Binder 事务 ``` var LAST_MSG = ''; Java.perform(() => { Interceptor.attach(Module.findExportByName('libbinder.so', 'ioctl'), { onEnter: function(args) { var binder_write_read_ptr = args[2]; if (args[1] == 0xC0306201) { // BINDER_WRITE_READ var binder_write_read = { // 'fd': args[0].toInt32(), 'write_size': binder_write_read_ptr.readU64(), 'write_consumed': binder_write_read_ptr.add(Process.pointerSize).readU64(), 'write_buffer': binder_write_read_ptr.add(Process.pointerSize * 2).readPointer(), } if (binder_write_read.write_size > 0) { var ptr = binder_write_read.write_buffer.add(binder_write_read.write_consumed + 4); switch (binder_write_read.write_buffer.readU32() & 0xff) { case 0: // BC_TRANSACTION case 1: // BC_REPLY var binder_transaction_data = { 'target': { 'handle': ptr.readU32(), 'ptr': ptr.readPointer() }, 'cookie': ptr.add(8).readPointer(), 'code': ptr.add(16).readU32(), 'flags': ptr.add(20).readU32(), 'sender_pid': ptr.add(24).readS32(), 'sender_euid': ptr.add(28).readU32(), 'data_size': ptr.add(32).readU64(), 'offsets_size': ptr.add(40).readU64(), 'data': { 'ptr': { 'buffer': ptr.add(48).readPointer(), 'offsets': ptr.add(56).readPointer() }, 'buf': ptr.add(48).readByteArray(8) } } var _log = hexdump(binder_transaction_data.data.ptr.buffer, { length: binder_transaction_data.data_size, ansi: true }); if (LAST_MSG.toString() != _log.toString()) { console.log(JSON.stringify(binder_transaction_data, null, 2)); console.log(_log); } break; } } } } }); }); ```
输出示例
``` { "target": { "handle": 16, "ptr": "0x10" }, "cookie": "0x0", "code": 22, "flags": 16, "sender_pid": 0, "sender_euid": 0, "data_size": "68", "offsets_size": "0", "data": { "ptr": { "buffer": "0x78dce3dcf0", "offsets": "0x0" }, "buf": {} } } 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF 78dce3dcf0 04 00 40 01 1d 00 00 00 61 00 6e 00 64 00 72 00 ..@.....a.n.d.r. 78dce3dd00 6f 00 69 00 64 00 2e 00 6e 00 65 00 74 00 2e 00 o.i.d...n.e.t... 78dce3dd10 77 00 69 00 66 00 69 00 2e 00 49 00 57 00 69 00 w.i.f.i...I.W.i. 78dce3dd20 66 00 69 00 4d 00 61 00 6e 00 61 00 67 00 65 00 f.i.M.a.n.a.g.e. 78dce3dd30 72 00 00 00 r... ```[⬆ 回到顶部](#table-of-contents) #### 揭示 Native 方法 `registerNativeMethods` 可被用作针对 native .so 库的反逆向技术,例如尽可能隐藏符号、混淆导出符号,并最终在 JNI 桥接层增加一些保护。 [来源](https://stackoverflow.com/questions/51811348/find-manually-registered-obfuscated-native-function-address) ``` var RevealNativeMethods = function() { var pSize = Process.pointerSize; var env = Java.vm.getEnv(); var RegisterNatives = 215, FindClassIndex = 6; // search "215" @ https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/functions.html var jclassAddress2NameMap = {}; function getNativeAddress(idx) { return env.handle.readPointer().add(idx * pSize).readPointer(); } // intercepting FindClass to populate Map Interceptor.attach(getNativeAddress(FindClassIndex), { onEnter: function(args) { jclassAddress2NameMap[args[0]] = args[1].readCString(); } }); // RegisterNative(jClass*, .., JNINativeMethod *methods[nMethods], uint nMethods) // https://android.googlesource.com/platform/libnativehelper/+/master/include_jni/jni.h#977 Interceptor.attach(getNativeAddress(RegisterNatives), { onEnter: function(args) { for (var i = 0, nMethods = parseInt(args[3]); i < nMethods; i++) { /* https://android.googlesource.com/platform/libnativehelper/+/master/include_jni/jni.h#129 typedef struct { const char* name; const char* signature; void* fnPtr; } JNINativeMethod; */ var structSize = pSize * 3; // = sizeof(JNINativeMethod) var methodsPtr = ptr(args[2]); var signature = methodsPtr.add(i * structSize + pSize).readPointer(); var fnPtr = methodsPtr.add(i * structSize + (pSize * 2)).readPointer(); // void* fnPtr var jClass = jclassAddress2NameMap[args[0]].split('/'); var methodName = methodsPtr.add(i * structSize).readPointer().readCString(); console.log('\x1b[3' + '6;01' + 'm', JSON.stringify({ module: DebugSymbol.fromAddress(fnPtr)['moduleName'], // https://www.frida.re/docs/javascript-api/#debugsymbol package: jClass.slice(0, -1).join('.'), class: jClass[jClass.length - 1], method: methodName, // methodsPtr.readPointer().readCString(), // char* name signature: signature.readCString(), // char* signature TODO Java bytecode signature parser { Z: 'boolean', B: 'byte', C: 'char', S: 'short', I: 'int', J: 'long', F: 'float', D: 'double', L: 'fully-qualified-class;', '[': 'array' } https://github.com/skylot/jadx/blob/master/jadx-core/src/main/java/jadx/core/dex/nodes/parser/SignatureParser.java address: fnPtr }), '\x1b[39;49;00m'); } } }); } Java.perform(RevealNativeMethods); ``` @OldVersion ``` var fIntercepted = false; function revealNativeMethods() { if (fIntercepted === true) { return; } var jclassAddress2NameMap = {}; var androidRunTimeSharedLibrary = "libart.so"; // may change between devices Module.enumerateSymbolsSync(androidRunTimeSharedLibrary).forEach(function(symbol){ switch (symbol.name) { case "_ZN3art3JNI21RegisterNativeMethodsEP7_JNIEnvP7_jclassPK15JNINativeMethodib": /* $ c++filt "_ZN3art3JNI21RegisterNativeMethodsEP7_JNIEnvP7_jclassPK15JNINativeMethodib" art::JNI::RegisterNativeMethods(_JNIEnv*, _jclass*, JNINativeMethod const*, int, bool) */ var RegisterNativeMethodsPtr = symbol.address; console.log("RegisterNativeMethods is at " + RegisterNativeMethodsPtr); Interceptor.attach(RegisterNativeMethodsPtr, { onEnter: function(args) { var methodsPtr = ptr(args[2]); var methodCount = parseInt(args[3]); for (var i = 0; i < methodCount; i++) { var pSize = Process.pointerSize; /* https://android.googlesource.com/platform/libnativehelper/+/master/include_jni/jni.h#129 typedef struct { const char* name; const char* signature; void* fnPtr; } JNINativeMethod; */ var structSize = pSize * 3; // JNINativeMethod contains 3 pointers var namePtr = Memory.readPointer(methodsPtr.add(i * structSize)); var sigPtr = Memory.readPointer(methodsPtr.add(i * structSize + pSize)); var fnPtrPtr = Memory.readPointer(methodsPtr.add(i * structSize + (pSize * 2))); // output schema: className#methodName(arguments)returnVal@address console.log( // package & class, replacing forward slash with dot for convenience jclassAddress2NameMap[args[0]].replace(/\//g, '.') + '#' + Memory.readCString(namePtr) + // method Memory.readCString(sigPtr) + // signature (arguments & return type) '@' + fnPtrPtr // C side address ); } }, onLeave: function (ignoredReturnValue) {} }); break; case "_ZN3art3JNI9FindClassEP7_JNIEnvPKc": // art::JNI::FindClass Interceptor.attach(symbol.address, { onEnter: function(args) { if (args[1] != null) { jclassAddress2NameMap[args[0]] = Memory.readCString(args[1]); } }, onLeave: function (ignoredReturnValue) {} }); break; } }); fIntercepted = true; } Java.perform(revealNativeMethods); ```
输出示例
``` $ frida -Uf com.google.android.apps.photos --no-pause -l script.js ``` ``` {"class":"org/chromium/net/GURLUtils","method":"nativeGetOrigin","signature":"(Ljava/lang/String;)Ljava/lang/String;","address":"0x..da910"} .. ```[⬆ 回到顶部](#table-of-contents) #### 记录方法参数 ``` def on_message(m, _data): if m['type'] == 'send': print(m['payload']) elif m['type'] == 'error': print(m) def switch(argument_key, idx): """ c/c++ variable type to javascript reader switch implementation # TODO handle other arguments, [long, longlong..] :param argument_key: variable type :param idx: index in symbols array :return: javascript to read the type of variable """ argument_key = argument_key.replace(' ', '') return '%d: %s' % (idx, { 'int': 'args[%d].toInt32(),', 'unsignedint': 'args[%d].toInt32(),', 'std::string': 'Memory.readUtf8String(Memory.readPointer(args[%d])),', 'bool': 'Boolean(args[%d]),' }[argument_key] % idx) def list_symbols_from_object_files(module_id): import subprocess return subprocess.getoutput('nm --demangle --dynamic %s' % module_id) def parse_nm_output(nm_stdout, symbols): for line in nm_stdout.splitlines(): split = line.split() open_parenthesis_idx = line.find('(') raw_arguments = [] if open_parenthesis_idx == -1 else line[open_parenthesis_idx + 1:-1] if len(raw_arguments) > 0: # ignore methods without arguments raw_argument_list = raw_arguments.split(',') symbols.append({ 'address': split[0], 'type': split[1], # @see Symbol Type Table 'name': split[2][:split[2].find('(')], # method name 'args': raw_argument_list }) def get_js_script(method, module_id): js_script = """ var moduleName = "{{moduleName}}", nativeFuncAddr = {{methodAddress}}; Interceptor.attach(Module.findExportByName(null, "dlopen"), { onEnter: function(args) { this.lib = Memory.readUtf8String(args[0]); console.log("[*] dlopen called with: " + this.lib); }, onLeave: function(retval) { if (this.lib.endsWith(moduleName)) { Interceptor.attach(Module.findBaseAddress(moduleName).add(nativeFuncAddr), { onEnter: function(args) { console.log("[*] hook invoked", JSON.stringify({{arguments}}, null, '\t')); } }); } } }); """ replace_map = { '{{moduleName}}': module_id, '{{methodAddress}}': '0x' + method['address'], '{{arguments}}': '{' + ''.join([switch(method['args'][i], i + 1) for i in range(len(method['args']))]) + '}' } for k, v in replace_map.items(): js_script = js_script.replace(k, v) print('[+] JS Script:\n', js_script) return js_script def main(app_id, module_id, method): """ $ python3.x+ script.py --method SomeClass::someMethod --app com.company.app --module libfoo.so :param app_id: application identifier / bundle id :param module_id: shared object identifier / known suffix, will iterate loaded modules (@see dlopen) :param method: method/symbol name :return: hook native method and print arguments when invoked """ # TODO extract all app's modules via `adb shell -c 'ls -lR /data/app/' + app_if + '*' | grep "\.so"` nm_stdout = list_symbols_from_object_files(module_id) symbols = [] parse_nm_output(nm_stdout, symbols) selection_idx = None for idx, symbol in enumerate(symbols): if method is None: # if --method flag is not passed print("%4d) %s (%d)" % (idx, symbol['name'], len(symbol['args']))) elif method == symbol['name']: selection_idx = idx break if selection_idx is None: if method is None: selection_idx = input("Enter symbol number: ") else: print('[+] Method not found, remove method flag to get list of methods to select from, `nm` stdout:') print(nm_stdout) exit(2) method = symbols[int(selection_idx)] print('[+] Selected method: %s' % method['name']) print('[+] Method arguments: %s' % method['args']) from frida import get_usb_device device = get_usb_device() pid = device.spawn([app_id]) session = device.attach(pid) script = session.create_script(get_js_script(method, module_id)) script.on('message', on_message) script.load() device.resume(app_id) # keep hook alive from sys import stdin stdin.read() if __name__ == '__main__': from argparse import ArgumentParser parser = ArgumentParser() parser.add_argument('--app', help='app identifier "com.company.app"') parser.add_argument('--module', help='loaded module name "libfoo.2.so"') parser.add_argument('--method', help='method name "SomeClass::someMethod", if empty it will print select-list') args = parser.parse_args() main(args.app, args.module, args.method) ```
符号类型表
"A" 符号值是绝对的,不会因后续链接而改变。
"B" 符号位于未初始化数据段(称为 BSS)。
"C" 符号是通用的。通用符号是未初始化的数据。
链接时,可能会出现多个同名的通用符号。
如果符号在任意位置定义,则通用符号被视为未定义引用。
"D" 符号位于已初始化数据段。
"G" 符号位于用于小对象的已初始化数据段。
某些目标文件格式允许更高效地访问小数据对象,例如全局 int 变量,
而不是大型全局数组。
"I" 符号是对另一个符号的间接引用。
这是 a.out 目标文件格式的 GNU 扩展,很少使用。
"N" 符号是调试符号。
"R" 符号位于只读数据段。
"S" 符号位于用于小对象的未初始化数据段。
"T" 符号位于文本(代码)段。
"U" 符号未定义。
"V" 符号是弱对象。当弱定义符号与普通定义符号链接时,
使用普通定义符号且不会出错。
当弱未定义符号被链接且符号未定义时,弱符号的值变为
零且不会出错。
"W" 符号是未特别标记为弱对象符号的弱符号。
当弱定义符号与普通定义符号链接时,
使用普通定义符号且不会出错。
当弱未定义符号被链接且符号未定义时,符号的值以
特定于系统的方式确定,不会出错。
在某些系统上,大写表示已指定默认值。
"-" 符号是 a.out 目标文件中的 stabs 符号。
在这种情况下,打印的下一个值是 stabs other 字段、stabs desc 字段和 stab 类型。
Stabs 符号用于保存调试信息。
"?" 符号类型未知,或者是特定于目标文件格式的。
[⬆ 回到顶部](#table-of-contents) #### 枚举已加载的类 并保存到名为 `pkg.classes` 的文件中 ``` $ frida -U com.pkg -qe 'Java.perform(function(){Java.enumerateLoadedClasses({"onMatch":function(c){console.log(c);}});});' -o pkg.classes ```
输出示例
TODO[⬆ 回到顶部](#table-of-contents) #### 类描述 获取类方法和成员。 ``` Object.getOwnPropertyNames(Java.use('com.company.CustomClass').__proto__).join('\n\t') ``` 如果存在名称冲突,即方法和成员同名,则会向成员添加下划线。[来源](https://github.com/frida/frida-java/pull/21) ``` let fieldJsName = env.stringFromJni(fieldName); while (jsMethods.hasOwnProperty(fieldJsName)) { fieldJsName = '_' + fieldJsName; } ```
输出示例
TODO[⬆ 回到顶部](#table-of-contents) #### 关闭 WiFi 它将在第一个 Activity 创建时关闭 WiFi。 ``` var WifiManager = Java.use("android.net.wifi.WifiManager"); Java.use("android.app.Activity").onCreate.overload("android.os.Bundle").implementation = function(bundle) { var wManager = Java.cast(this.getSystemService("wifi"), WifiManager); console.log('isWifiEnabled ?', wManager.isWifiEnabled()); wManager.setWifiEnabled(false); this.$init(bundle); } ```
输出示例
TODO[⬆ 回到顶部](#table-of-contents) #### 设置代理 它将使用提供的 IP 地址和端口设置系统级代理。 ``` var ActivityThread = Java.use('android.app.ActivityThread'); var ConnectivityManager = Java.use('android.net.ConnectivityManager'); var ProxyInfo = Java.use('android.net.ProxyInfo'); var proxyInfo = ProxyInfo.$new('192.168.1.10', 8080, ''); // change to null in order to disable the proxy. var context = ActivityThread.currentApplication().getApplicationContext(); var connectivityManager = Java.cast(context.getSystemService('connectivity'), ConnectivityManager); connectivityManager.setGlobalProxy(proxyInfo); ```
输出示例
TODO[⬆ 回到顶部](#table-of-contents) #### 获取 IMEI 也可以 Hook 并更改 IMEI。 ``` function getIMEI(){ console.log('IMEI =', Java.use("android.telephony.TelephonyManager").$new().getDeviceId()); } Java.perform(getIMEI) ```
输出示例
TODO[⬆ 回到顶部](#table-of-contents) #### Hook io InputStream Hook `InputputStream` 并将缓冲区打印为带有字符限制和排除列表的 `ascii`。 ``` function binaryToHexToAscii(array, readLimit) { var result = []; // read 100 bytes #performance readLimit = readLimit || 100; for (var i = 0; i < readLimit; ++i) { result.push(String.fromCharCode( // hex2ascii part parseInt( ('0' + (array[i] & 0xFF).toString(16)).slice(-2), // binary2hex part 16 ) )); } return result.join(''); } function hookInputStream() { Java.use('java.io.InputStream')['read'].overload('[B').implementation = function(b) { // execute original and save return value var retval = this.read(b); var resp = binaryToHexToAscii(b); // conditions to not print garbage packets var reExcludeList = new RegExp(['Mmm'/*, 'Ping' /*, ' Yo'*/].join('|')); if ( ! reExcludeList.test(resp) ) { console.log(resp); } var reIncludeList = new RegExp(['AAA', 'BBB', 'CCC'].join('|')); if ( reIncludeList.test(resp) ) { send( binaryToHexToAscii(b, 1200) ); } return retval; }; } Java.perform(hookInputStream); ```
输出示例
TODO[⬆ 回到顶部](#table-of-contents) #### Android 弹出 Toast ``` // 0 = // https://developer.android.com/reference/android/widget/Toast#LENGTH_LONG Java.scheduleOnMainThread(() => { Java.use("android.widget.Toast") .makeText(Java.use("android.app.ActivityThread").currentApplication().getApplicationContext(), Java.use("java.lang.StringBuilder").$new("Text to Toast here"), 0).show(); }); ```
输出示例
TODO[⬆ 回到顶部](#table-of-contents) #### 等待条件 等待直到特定 DLL 在 Unity 应用中加载,可以实现热插拔。 ``` var awaitForCondition = function(callback) { var int = setInterval(function() { if (Module.findExportByName(null, "mono_get_root_domain")) { clearInterval(int); callback(); return; } }, 0); } function hook() { Interceptor.attach(Module.findExportByName(null, "mono_assembly_load_from_full"), { onEnter: function(args) { this._dll = Memory.readUtf8String(ptr(args[1])); console.log('[*]', this._dll); }, onLeave: function(retval) { if (this._dll.endsWith("Assembly-CSharp.dll")) { console.log(JSON.stringify({ retval: retval, name: this._dll }, null, 2)); } } }); } Java.perform(awaitForCondition(hook)); ```
输出示例
TODO[⬆ 回到顶部](#table-of-contents) #### Webview URLS 当 WebView 切换 URL 时进行记录。 ``` Java.use("android.webkit.WebView").loadUrl.overload("java.lang.String").implementation = function (s) { send(s.toString()); this.loadUrl.overload("java.lang.String").call(this, s); }; ```
输出示例
TODO[⬆ 回到顶部](#table-of-contents) #### 打印运行时字符串 Hook StringBuilder/Buffer 的 `toString` 并打印堆栈跟踪。 ``` Java.perform(function() { ['java.lang.StringBuilder', 'java.lang.StringBuffer'].forEach(function(clazz, i) { console.log('[?] ' + i + ' = ' + clazz); var func = 'toString'; Java.use(clazz)[func].implementation = function() { var ret = this[func](); if (ret.indexOf('') != -1) { // print stacktrace if return value contains specific string Java.perform(function() { var jAndroidLog = Java.use("android.util.Log"), jException = Java.use("java.lang.Exception"); console.log( jAndroidLog.getStackTraceString( jException.$new() ) ); }); } send('[' + i + '] ' + ret); return ret; } }); }); ```
输出示例
TODO[⬆ 回到顶部](#table-of-contents) #### 打印 SharedPreferences 更新 ``` Java.perform(function() { var shared_pref_class = Java.use('android.app.SharedPreferencesImpl$EditorImpl'); shared_pref_class.putString.overload('java.lang.String', 'java.lang.String').implementation = function(k, v) { console.log('Shared preference updated: ', k, '=', v); return this.putString(k, v); } shared_pref_class.putInt.overload('java.lang.String', 'int').implementation = function(k, v) { console.log('Shared preference updated: ', k, '=', v); return this.putInt(k, v); } shared_pref_class.putFloat.overload('java.lang.String', 'float').implementation = function(k, v) { console.log('Shared preference updated: ', k, '=', v); return this.putFloat(k, v); } shared_pref_class.putBoolean.overload('java.lang.String', 'boolean').implementation = function(k, v) { console.log('Shared preference updated: ', k, '=', v); return this.putBoolean(k, v); } shared_pref_class.putLong.overload('java.lang.String', 'long').implementation = function(k, v) { console.log('Shared preference updated: ', k, '=', v); return this.putLong(k, v); } shared_pref_class.putStringSet.overload('java.lang.String', java.util.Set).implementation = function(k, v) { console.log('Shared preference updated: ', k, '=', v); return this.putStringSet(k, v); } }); ```
输出示例
TODO[⬆ 回到顶部](#table-of-contents) #### 字符串比较 ``` Java.perform(function() { 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(str.toString.call(this) + ' == ' + obj.toString() + ' ? ' + response); } } return response; } }); ```
输出示例
TODO[⬆ 回到顶部](#table-of-contents) #### 通过地址 Hook JNI 通过模块名和方法地址 Hook native 方法并打印参数。 ``` var moduleName = "libfoo.so"; var nativeFuncAddr = 0x1234; // $ nm --demangle --dynamic libfoo.so | grep "Class::method(" Interceptor.attach(Module.findExportByName(null, "dlopen"), { onEnter: function(args) { this.lib = Memory.readUtf8String(args[0]); console.log("dlopen called with: " + this.lib); }, onLeave: function(retval) { if (this.lib.endsWith(moduleName)) { console.log("ret: " + retval); var baseAddr = Module.findBaseAddress(moduleName); Interceptor.attach(baseAddr.add(nativeFuncAddr), { onEnter: function(args) { console.log("[-] hook invoked"); console.log(JSON.stringify({ a1: args[1].toInt32(), a2: Memory.readUtf8String(Memory.readPointer(args[2])), a3: Boolean(args[3]) }, null, '\t')); } }); } } }); ```
输出示例
TODO[⬆ 回到顶部](#table-of-contents) #### Hook 构造函数 ``` Java.use('java.lang.StringBuilder').$init.overload('java.lang.String').implementation = function(stringArgument) { console.log("c'tor"); return this.$init(stringArgument); }; ```
输出示例
TODO[⬆ 回到顶部](#table-of-contents) #### Hook 反射 `java.lang.reflect.Method#invoke(Object obj, Object... args, boolean bool)` ``` Java.use('java.lang.reflect.Method').invoke.overload('java.lang.Object', '[Ljava.lang.Object;', 'boolean').implementation = function(a,b,c) { console.log('hooked!', a, b, c); return this.invoke(a,b,c); }; ```
输出示例
TODO[⬆ 回到顶部](#table-of-contents) #### 追踪类 追踪类方法,带有漂亮的颜色以及打印为 JSON 和堆栈跟踪的选项。 TODO 添加对构造函数的追踪。 ``` var Color = { RESET: "\x1b[39;49;00m", Black: "0;01", Blue: "4;01", Cyan: "6;01", Gray: "7;11", Green: "2;01", Purple: "5;01", Red: "1;01", Yellow: "3;01", Light: { Black: "0;11", Blue: "4;11", Cyan: "6;11", Gray: "7;01", Green: "2;11", Purple: "5;11", Red: "1;11", Yellow: "3;11" } }; /** * * @param input. * If an object is passed it will print as json * @param kwargs options map { * -l level: string; log/warn/error * -i indent: boolean; print JSON prettify * -c color: @see ColorMap * } */ var LOG = function (input, kwargs) { kwargs = kwargs || {}; var logLevel = kwargs['l'] || 'log', colorPrefix = '\x1b[3', colorSuffix = 'm'; if (typeof input === 'object') input = JSON.stringify(input, null, kwargs['i'] ? 2 : null); if (kwargs['c']) input = colorPrefix + kwargs['c'] + colorSuffix + input + Color.RESET; console[logLevel](input); }; var printBacktrace = function () { Java.perform(function() { var android_util_Log = Java.use('android.util.Log'), java_lang_Exception = Java.use('java.lang.Exception'); // getting stacktrace by throwing an exception LOG(android_util_Log.getStackTraceString(java_lang_Exception.$new()), { c: Color.Gray }); }); }; function traceClass(targetClass) { var hook; try { hook = Java.use(targetClass); } catch (e) { console.error("trace class failed", e); return; } var methods = hook.class.getDeclaredMethods(); hook.$dispose(); var parsedMethods = []; methods.forEach(function (method) { var methodStr = method.toString(); var methodReplace = methodStr.replace(targetClass + ".", "TOKEN").match(/\sTOKEN(.*)\(/)[1]; parsedMethods.push(methodReplace); }); uniqBy(parsedMethods, JSON.stringify).forEach(function (targetMethod) { traceMethod(targetClass + '.' + targetMethod); }); } function traceMethod(targetClassMethod) { try { var delim = targetClassMethod.lastIndexOf('.'); if (delim === -1) return; var targetClass = targetClassMethod.slice(0, delim); var targetMethod = targetClassMethod.slice(delim + 1, targetClassMethod.length); var hook = Java.use(targetClass); var overloadCount = hook[targetMethod].overloads.length; LOG({ tracing: targetClassMethod, overloaded: overloadCount }, { c: Color.Green }); for (var i = 0; i < overloadCount; i++) { hook[targetMethod].overloads[i].implementation = function () { var log = { '#': targetClassMethod, args: [] }; for (var j = 0; j < arguments.length; j++) { var arg = arguments[j]; // quick&dirty fix for java.io.StringWriter char[].toString() impl because frida prints [object Object] if (j === 0 && arguments[j]) { if (arguments[j].toString() === '[object Object]') { var s = []; for (var k = 0, l = arguments[j].length; k < l; k++) { s.push(arguments[j][k]); } arg = s.join(''); } } log.args.push({ i: j, o: arg, s: arg ? arg.toString(): 'null'}); } var retval; try { retval = this[targetMethod].apply(this, arguments); // might crash (Frida bug?) log.returns = { val: retval, str: retval ? retval.toString() : null }; } catch (e) { console.error(e); } LOG(log, { c: Color.Blue }); return retval; } } } catch(error) { LOG({ tracing: targetClassMethod, "overloaded": 0}, { c: Color.Red }); } } // remove duplicates from array function uniqBy(array, key) { var seen = {}; return array.filter(function (item) { var k = key(item); return seen.hasOwnProperty(k) ? false : (seen[k] = true); }); } var Main = function() { Java.perform(function () { // avoid java.lang.ClassNotFoundException [ // "java.io.File", 'java.net.Socket' ].forEach(traceClass); Java.use('java.net.Socket').isConnected.overload().implementation = function () { LOG('Socket.isConnected.overload', { c: Color.Light.Cyan }); printBacktrace(); return true; } }); }; Java.perform(Main); ```
输出示例
TODO[⬆ 回到顶部](#table-of-contents) #### 获取 Android ID [ANDROID_ID](
输出示例
https://stackoverflow.com/a/54818023/2655092[⬆ 回到顶部](#table-of-contents) #### 修改位置 ``` Java.perform(() => { var Location = Java.use('android.location.Location'); Location.getLatitude.implementation = function() { return LATITUDE; } Location.getLongitude.implementation = function() { return LONGITUDE; } }) ```
输出示例
TODO[⬆ 回到顶部](#table-of-contents) #### 绕过 FLAG_SECURE 绕过截屏防护 [stackoverflow 问题](https://stackoverflow.com/questions/9822076/how-do-i-prevent-android-taking-a-screenshot-when-my-app-goes-to-the-background) ``` Java.perform(function() { Java.use('android.view.SurfaceView').setSecure.overload('boolean').implementation = function(flag){ console.log('[1] flag:', flag); this.call(false); }; var LayoutParams = Java.use('android.view.WindowManager$LayoutParams'); Java.use('android.view.ViewWindow').setFlags.overload('int', 'int').implementation = function(flags, mask){ console.log('flag secure: ', LayoutParams.FLAG_SECURE.value); console.log('before:', flags); flags = (flags.value & ~LayoutParams.FLAG_SECURE.value); console.log('after:', flags); this.call(this, flags, mask); }; }); ```
输出示例
https://stackoverflow.com/a/54818023/2655092[⬆ 回到顶部](#table-of-contents) #### SharedPreferences 更新 ``` function notifyNewSharedPreference() { Java.use('android.app.SharedPreferencesImpl$EditorImpl').putString.overload('java.lang.String', 'java.lang.String').implementation = function(k, v) { console.log('[SharedPreferencesImpl]', k, '=', v); return this.putString(k, v); } } ```
输出示例
TODO[⬆ 回到顶部](#table-of-contents) #### Hook 重载 ``` function hookOverloads(className, func) { var clazz = Java.use(className); var overloads = clazz[func].overloads; for (var i in overloads) { if (overloads[i].hasOwnProperty('argumentTypes') || overloads[i]['argumentTypes'] != undefined) { var parameters = []; var curArgumentTypes = overloads[i].argumentTypes, args = [], argLog = '['; for (var j in curArgumentTypes) { var cName = curArgumentTypes[j].className; parameters.push(cName); argLog += "'(" + cName + ") ' + v" + j + ","; args.push('v' + j); } argLog += ']'; var script = "var ret = this." + func + '(' + args.join(',') + ") || '';\n" + "console.log(JSON.stringify(" + argLog + "));\n" + "return ret;" args.push(script); clazz[func].overload.apply(this, parameters).implementation = Function.apply(null, args); } } } Java.perform(function() { hookOverloads('java.lang.StringBuilder', '$init'); }) ```
输出示例
TODO[⬆ 回到顶部](#table-of-contents) #### 注册广播接收器 ``` Java.perform(() => { const MyBroadcastReceiver = Java.registerClass({ name: 'MyBroadcastReceiver', superClass: Java.use('android.content.BroadcastReceiver'), methods: { onReceive: [{ returnType: 'void', argumentTypes: ['android.content.Context', 'android.content.Intent'], implementation: function(context, intent) { // .. } }] }, }); let ctx = Java.use('android.app.ActivityThread').currentApplication().getApplicationContext(); ctx.registerReceiver(MyBroadcastReceiver.$new(), Java.use('android.content.IntentFilter').$new('com.example.JAVA_TO_AGENT')); }); ```
输出示例
TODO[⬆ 回到顶部](#table-of-contents) #### 列出实现接口的类 ``` function listClassesImplementsInterface(aInterface) { let classLoaders = Java.enumerateClassLoadersSync() Java.enumerateLoadedClassesSync().forEach(className => { for (let i = 0; i < classLoaders.length; i++) { let classLoader = classLoaders[i] Java.classFactory.loader = classLoader try { let jclass = Java.use(className).class let ifaces = jclass.getInterfaces().toString() jclass = null if (ifaces.indexOf(aInterface) != -1) { console.log(JSON.stringify({ name: className, loader: classLoader.toString(), interfaces: ifaces })) break // we found one ClassLoader, that's enough } } catch (e) { // continue to next ClassLoader } } }) } ```
[⬆ 回到顶部](#table-of-contents) #### 增加步数 ``` Java.perform(() => { var customSensorEventListener = null; var curSteps = 0; var totalNumberOfRequiredSteps = 10000; function incSteps() { Java.perform(() => { var sEvent = Java.use('android.hardware.SensorEvent').$new(1); sEvent.values.values = Java.array('float', [curSteps]); // https://developer.android.com/reference/android/hardware/SensorEvent#values sEvent.timestamp = Java.use('java.lang.Long').$new(Java.use('java.lang.System').nanoTime()); sEvent.accuracy = Java.use('java.lang.Integer').$new(3); // https://developer.android.com/reference/android/hardware/SensorManager#SENSOR_STATUS_ACCURACY_HIGH customSensorEventListener.onSensorChanged(sEvent); if (curSteps < totalNumberOfRequiredSteps) { setTimeout(() => { curSteps += 50; incSteps(); }, 1500) } }); } Java.choose('.CustomSensorEventListener', { // class that implements SensorEventListener onMatch: function (instance) { customSensorEventListener = instance; }, onComplete: function () { incSteps(); } }); }); ```
输出示例
TODO[⬆ 回到顶部](#table-of-contents) #### OS 日志 ``` var m = 'libsystem_trace.dylib'; // bool os_log_type_enabled(os_log_t oslog, os_log_type_t type); var isEnabledFunc = Module.findExportByName(m, 'os_log_type_enabled'); // _os_log_impl(void *dso, os_log_t log, os_log_type_t type, const char *format, uint8_t *buf, unsigned int size); var logFunc = Module.findExportByName(m, '_os_log_impl'); // Enable all logs Interceptor.attach(isEnabledFunc, { onLeave: function (ret) { ret.replace(0x1); } }); Interceptor.attach(logFunc, { onEnter: function (a) { /* OS_ENUM(os_log_type, uint8_t, OS_LOG_TYPE_DEFAULT = 0x00, OS_LOG_TYPE_INFO = 0x01, OS_LOG_TYPE_DEBUG = 0x02, OS_LOG_TYPE_ERROR = 0x10, OS_LOG_TYPE_FAULT = 0x11); */ var type = a[2]; var format = a[3]; if (type !== 0x2) { console.log(JSON.stringify({ type: type, format: format.readCString(), //buf: a[4].readPointer().readCString() // TODO }, null, 2)); } } }) ```
输出示例
TODO[⬆ 回到顶部](#table-of-contents) #### iOS 警告框 ``` var UIAlertController = ObjC.classes.UIAlertController; var UIAlertAction = ObjC.classes.UIAlertAction; var UIApplication = ObjC.classes.UIApplication; var handler = new ObjC.Block({ retType: 'void', argTypes: ['object'], implementation: function () {} }); ObjC.schedule(ObjC.mainQueue, function () { var alert = UIAlertController.alertControllerWithTitle_message_preferredStyle_('Frida', 'Hello from Frida', 1); var defaultAction = UIAlertAction.actionWithTitle_style_handler_('OK', 0, handler); alert.addAction_(defaultAction); // Instead of using `ObjC.choose()` and looking for UIViewController instances on the heap, we have direct access through UIApplication: UIApplication.sharedApplication().keyWindow().rootViewController().presentViewController_animated_completion_(alert, true, NULL); }) ```
输出示例
TODO[⬆ 回到顶部](#table-of-contents) #### 文件访问 记录每次文件打开 ``` Interceptor.attach(ObjC.classes.NSFileManager['- fileExistsAtPath:'].implementation, { onEnter: function (args) { console.log('open' , ObjC.Object(args[2]).toString()); } }); ```
输出示例
TODO[⬆ 回到顶部](#table-of-contents) #### 观察类 ``` function observeClass(name) { var k = ObjC.classes[name]; k.$ownMethods.forEach(function(m) { var impl = k[m].implementation; console.log('Observing ' + name + ' ' + m); Interceptor.attach(impl, { onEnter: function(a) { this.log = []; this.log.push('(' + a[0] + ',' + Memory.readUtf8String(a[1]) + ') ' + name + ' ' + m); if (m.indexOf(':') !== -1) { var params = m.split(':'); params[0] = params[0].split(' ')[1]; for (var i = 0; i < params.length - 1; i++) { try { this.log.push(params[i] + ': ' + new ObjC.Object(a[2 + i]).toString()); } catch (e) { this.log.push(params[i] + ': ' + a[2 + i].toString()); } } } this.log.push( Thread.backtrace(this.context, Backtracer.ACCURATE) .map(DebugSymbol.fromAddress) .join('\n') ); }, onLeave: function(r) { try { this.log.push('RET: ' + new ObjC.Object(r).toString()); } catch (e) { this.log.push('RET: ' + r.toString()); } console.log(this.log.join('\n') + '\n'); } }); }); } ```
输出示例
`observeClass('Someclass$innerClass');` ``` Observing Someclass$innerClass - func Observing Someclass$innerClass - empty (0x174670040,parameterName) Someclass$innerClass - func 0x10048dd6c libfoo!0x3bdd6c 0x1005a5dd0 libfoo!0x4d5dd0 0x1832151c0 libdispatch.dylib!_dispatch_client_callout 0x183215fb4 libdispatch.dylib!dispatch_once_f RET: 0xabcdef ```[⬆ 回到顶部](#table-of-contents) #### 查找 iOS 应用 UUID 当附加到应用时,通过读取每个应用容器下的 plist 文件来获取特定路径的 UUID。 ``` var PLACEHOLDER = '{UUID}'; function extractUUIDfromPath(path) { var bundleIdentifier = String(ObjC.classes.NSBundle.mainBundle().objectForInfoDictionaryKey_('CFBundleIdentifier')); var path_prefix = path.substr(0, path.indexOf(PLACEHOLDER)); var plist_metadata = '/.com.apple.mobile_container_manager.metadata.plist'; var errorPtr = Memory.alloc(Process.pointerSize); Memory.writePointer(errorPtr, NULL); var folders = ObjC.classes.NSFileManager.defaultManager().contentsOfDirectoryAtPath_error_(path_prefix, errorPtr); var error = Memory.readPointer(errorPtr); if (errorPtr) console.error( new ObjC.Object( error ) ); for (var i = 0, l = folders.count(); i < l; i++) { var uuid = folders.objectAtIndex_(i); var metadata = path_prefix + uuid + plist_metadata; var dict = ObjC.classes.NSMutableDictionary.alloc().initWithContentsOfFile_(metadata); var enumerator = dict.keyEnumerator(); var key; while ((key = enumerator.nextObject()) !== null) { if (key == 'MCMMetadataIdentifier') { var appId = String(dict.objectForKey_(key)); if (appId.indexOf(bundleIdentifier) != -1) { return path.replace(PLACEHOLDER, uuid); } } } } } console.log( extractUUIDfromPath('/var/mobile/Containers/Data/Application/' + PLACEHOLDER + '/Documents') ); ```
输出示例
TODO[⬆ 回到顶部](#table-of-contents) #### 提取 Cookies ``` var cookieJar = {}; var cookies = ObjC.classes.NSHTTPCookieStorage.sharedHTTPCookieStorage().cookies(); for (var i = 0, l = cookies.count(); i < l; i++) { var cookie = cookies['- objectAtIndex:'](i); cookieJar[cookie.Name()] = cookie.Value().toString(); // ["- expiresDate"]().toString() } console.log(JSON.stringify(cookieJar, null, 2)); ```
输出示例
```js { "key1": "value 1", "key2": "value 2" } ```[⬆ 回到顶部](#table-of-contents) #### 描述类成员 打印每个类实例的成员映射(包含值) ``` ObjC.choose(ObjC.classes[clazz], { onMatch: function (obj) { console.log('onMatch: ', obj); Object.keys(obj.$ivars).forEach(function(v) { console.log('\t', v, '=', obj.$ivars[v]); }); }, onComplete: function () { console.log('onComplete', arguments.length); } }); ```
输出示例
TODO[⬆ 回到顶部](#table-of-contents) #### 类层次结构 Object.keys(ObjC.classes) 将列出所有可用的 Objective C 类, 但实际上这将返回当前进程中加载的所有类,包括系统框架。 如果我们想要像 weak_classdump 那样仅列出可执行文件本身的类,Objective C 运行时已经提供了这样的函数 [objc_copyClassNamesForImage](#https://developer.apple.com/documentation/objectivec/1418485-objc_copyclassnamesforimage?language=objc) ``` var objc_copyClassNamesForImage = new NativeFunction( Module.findExportByName(null, 'objc_copyClassNamesForImage'), 'pointer', ['pointer', 'pointer'] ); var free = new NativeFunction(Module.findExportByName(null, 'free'), 'void', ['pointer']); var classes = new Array(count); var p = Memory.alloc(Process.pointerSize); Memory.writeUInt(p, 0); var path = ObjC.classes.NSBundle.mainBundle().executablePath().UTF8String(); var pPath = Memory.allocUtf8String(path); var pClasses = objc_copyClassNamesForImage(pPath, p); var count = Memory.readUInt(p); for (var i = 0; i < count; i++) { var pClassName = Memory.readPointer(pClasses.add(i * Process.pointerSize)); classes[i] = Memory.readUtf8String(pClassName); } free(pClasses); var tree = {}; classes.forEach(function(name) { var clazz = ObjC.classes[name]; var chain = [name]; while (clazz = clazz.$superClass) { chain.unshift(clazz.$className); } var node = tree; chain.forEach(function(clazz) { node[clazz] = node[clazz] || {}; node = node[clazz]; }); }); send(tree); ```
输出示例
TODO[⬆ 回到顶部](#table-of-contents) #### Hook 反射 Hook `objc_msgSend` ``` import frida, sys f = open('/tmp/log', 'w') def on_message(msg, _data): f.write(msg['payload']+'\n') frida_script = """ Interceptor.attach(Module.findExportByName('/usr/lib/libobjc.A.dylib', 'objc_msgSend'), { onEnter: function(args) { var m = Memory.readCString(args[1]); if (m != 'length' && !m.startsWith('_fastC')) send(m); } }); """ device = frida.get_usb_device() pid = device.spawn(["com.example"]) # or .get_frontmost_application() session = device.attach(pid) script = session.create_script(frida_script) script.on('message', on_message) script.load() device.resume(pid) sys.stdin.read() ``` ``` $ sort /tmp/log | uniq -c | sort -n ```
输出示例
TODO[⬆ 回到顶部](#table-of-contents) #### 拦截整个模块 为了减少与 UI 相关的函数,我使用以下步骤: 1. 使用 `-o /tmp/log1` 将日志输出到文件 2. 使用 `$ sort /tmp/log1 | uniq -c | sort -rn | head -n20 | cut -d# -f2 | paste -sd "," -` 将 MRU(最近最常使用)复制到排除列表 ``` var mName = 'MyModule', excludeList = ['Alot', 'Of', 'UI', 'Related', 'Functions']; Module.enumerateExportsSync(mName) .filter(function(e) { var fromTypeFunction = e.type == 'function';· var notInExcludes = excludeList.indexOf(e.name) == -1; return fromTypeFunction && notInExcludes; }) .forEach(function(e) { Interceptor.attach(Module.findExportByName(mName, e.name), { onEnter: function(args) { console.log(mName + "#'" + e.name + "'"); } }) }) ```
输出示例
TODO[⬆ 回到顶部](#table-of-contents) #### 转储内存段 ``` Process.enumerateRanges('rw-', { onMatch: function (range) { var fname = `/sdcard/${range.base}_dump`; var f = new File(fname, 'wb'); f.write(instance.base.readByteArray(instance.size)); f.flush(); f.close(); console.log(`base=${range.base} size=${range.size} prot=${range.protection} fname=${fname}`); }, onComplete: function () {} }); ```
输出示例
TODO[⬆ 回到顶部](#table-of-contents) #### 内存扫描 ``` function memscan(str) { Process.enumerateModulesSync().filter(m => m.path.startsWith('/data')).forEach(m => { var pattern = str.split('').map(letter => letter.charCodeAt(0).toString(16)).join(' '); try { var res = Memory.scanSync(m.base, m.size, pattern); if (res.length > 0) console.log(JSON.stringify({m, res})); } catch (e) { console.warn(e); } }); } ``` ``` var memscn = function (str) { Process.enumerateModulesSync().forEach(function (m) { var pattern = str.split('').map(function (l) { return l.charCodeAt(0).toString(16) }).join(' '); try { var res = Memory.scanSync(m.base, m.size, pattern); if (res.length > 0) console.log(JSON.stringify({m, res}, null , 2)); } catch (e) { console.warn(e); } }); } ```
输出示例
pattern [ 52 41 4e 44 4f 4d ] { "name": "Test", "base": "0x1048fc000", "size": 147000, "path": "/var/containers/Bundle/Application/CD74EB00-9D90-4600-BF5D-F6E5E0CDF878/Test.app/Test" } [{"address":"0x10491f211","size":6}][⬆ 回到顶部](#table-of-contents) #### Stalker ``` var _module = Process.findModuleByName('myModule'); var base = ptr(_module.base); var startTraceOffset = 0xabcd1234, numInstructionsToTrace = 50; var startTrace = base.add(startTraceOffset), endTrace = startTrace.add(4 * (numInstructionsToTrace - 1)); Interceptor.attach(ObjC.classes.CustomClass['- func'].implementation, { onEnter: function (args) { var tid = Process.getCurrentThreadId(); this.tid = tid; console.warn(`onEnter [ ${tid} ]`); Stalker.follow(tid, { transform: function (iterator) { var instruction; while ((instruction = iterator.next()) !== null) { // condition to putCallout if (instruction.address <= endTrace && instruction.address >= startTrace) { // print instruction & registers values iterator.putCallout(function(context) { var offset = ptr(context.pc).sub(base); var inst = Instruction.parse(context.pc).toString(); var modified_inst = inst; inst.replace(/,/g, '').split(' ').forEach(op => { if (op.startsWith('x')) modified_inst = modified_inst.replace(op, context[op]); else if (op.startsWith('w')) modified_inst = modified_inst.replace(op, context[op.replace('w', 'x')]); }); modified_inst = '\x1b[35;01m' + modified_inst + '\x1b[0m'; console.log(`x8=${context.x8} x25=${context.x25} x0=${context.x0} x21=${context.x21}`) console.log(`${offset} ${inst} # ${modified_inst}`); }); } iterator.keep(); } } }) }, onLeave: function (retval) { console.log(`onLeave [ ${this.tid} ]`); // cleanup Stalker.unfollow(this.tid); Stalker.garbageCollect(); } }) ```
输出示例
mul x5, x2, x21 # mul 0x3, 0x4, 0x5[⬆ 回到顶部](#table-of-contents) #### Cpp 反混淆器 ``` $ npm i frida-compile demangler-js -g ``` 添加到你的脚本 ``` const demangle = require('demangler-js').demangle; ... Module.enumerateExportsSync('library.so') .filter(x => x.name.startsWith('_Z')) .forEach(x => { Interceptor.attach(x.address, { onEnter: function (args) { console.log('[-] ' + demangle(x.name)); } }); }); ``` 编译 ``` $ frida-compile script.js -o out.js ``` 运行 ``` $ frida -Uf com.app -l out.js ```
输出示例
TODO[⬆ 回到顶部](#table-of-contents) #### 早期 Hook 在 DT_INIT_ARRAY 之前设置 Hook ([来源](https://cs.android.com/android/platform/superproject/+/master:bionic/linker/linker_soinfo.cpp;l=386;drc=android-8.0.0_r1?q=call_constructor&ss=android%2Fplatform%2Fsuperproject)) ``` let base; let do_dlopen = null; let call_ctor = null; const target_lib_name = 'targetlib.so'; Process.findModuleByName('linker64').enumerateSymbols().forEach(sym => { if (sym.name.indexOf('do_dlopen') >= 0) { do_dlopen = sym.address; } else if (sym.name.indexOf('call_constructor') >= 0) { call_ctor = sym.address; } }) Interceptor.attach(do_dlopen, function (args) { if (args[0].readUtf8String().indexOf(target_lib_name) >= 0) { Interceptor.attach(call_ctor, function () { const module = Process.findModuleByName(target_lib_name); base = module.base; console.log('loading', target_lib_name, '- base @', base); // DoStuff }) } }) ``` 致谢: [iGio90](https://github.com/iGio90)
输出示例
TODO[⬆ 回到顶部](#table-of-contents) #### 设备属性 快速粗糙的 iOS 设备属性提取示例 ``` var UIDevice = ObjC.classes.UIDevice.currentDevice(); UIDevice.$ownMethods .filter(function(method) { return method.indexOf(':') == -1 /* filter out methods with parameters */ && method.indexOf('+') == -1 /* filter out public methods */ }) .forEach(function(method) { console.log(method, ':', UIDevice[method]()) }) console.log('executablePath =', ObjC.classes.NSBundle.mainBundle().executablePath().toString()); ``` ``` if (ObjC.available) { var processInfo = ObjC.classes.NSProcessInfo.processInfo(); var versionString = processInfo.operatingSystemVersionString().toString(); // E.g. "Version 13.5 (Build 17F75)" var ver = versionString.split(' '); var version = ver[1]; // E.g. 13.5 console.log("iOS version: " + version); } ```
输出示例
``` - adjTrackingEnabled : true - adjFbAttributionId : - adjVendorId : 4AAAAAAA-CECC-4BBB-BDDD-DEEEEEEEED18 - adjDeviceType : iPhone - adjDeviceName : iPhone8,2 - adjCreateUuid : dfaaaa2-ebbd-4ccc-addd-eaeeeeeeee7c - adjIdForAdvertisers : 7AAAAA3A-4BBB-4CCC-BDDD-0EEEEEEEE8A6 - sbf_bannerGraphicsQuality : 100 - sbf_controlCenterGraphicsQuality : 100 - sbf_homeScreenFolderGraphicsQuality : 100 - sbf_searchTransitionGraphicsQuality : 100 - sbf_dashBoardPresentationGraphicsQuality : 100 - sbf_homeScreenBlurGraphicsQuality : 100 - userInterfaceIdiom : 0 - _supportsDeepColor : false - name : iPhone - _keyboardGraphicsQuality : 100 - isGeneratingDeviceOrientationNotifications : true - orientation : 1 - _backlightLevel : 1 - isProximityMonitoringEnabled : false - systemVersion : 11.1.1 - _graphicsQuality : 100 - beginGeneratingDeviceOrientationNotifications : undefined - endGeneratingDeviceOrientationNotifications : undefined - buildVersion : 15C222 - systemName : iOS - _isSystemSoundEnabled : true - _feedbackSupportLevel : 1 - model : iPhone - _supportsForceTouch : true - localizedModel : iPhone - identifierForVendor : 4A7B44DB-AAAA-BBB-CCC-D8819581DDD - isBatteryMonitoringEnabled : false - batteryState : 0 - batteryLevel : -1 - proximityState : false - isMultitaskingSupported : true - playInputClick : undefined - _softwareDimmingAlpha : 0 - _playInputSelectSound : undefined - _playInputDeleteSound : undefined - _hasGraphicsQualityOverride : false - _hasTouchPad : false - _clearGraphicsQualityOverride : undefined - _predictionGraphicsQuality : 100 - _nativeScreenGamut : 0 - _tapticEngine : <_UITapticEngine: 0x1c06257c0> ```[⬆ 回到顶部](#table-of-contents) #### 截取屏幕截图 ``` function screenshot() { ObjC.schedule(ObjC.mainQueue, function() { var getNativeFunction = function (ex, retVal, args) { return new NativeFunction(Module.findExportByName('UIKit', ex), retVal, args); }; var api = { UIWindow: ObjC.classes.UIWindow, UIGraphicsBeginImageContextWithOptions: getNativeFunction('UIGraphicsBeginImageContextWithOptions', 'void', [['double', 'double'], 'bool', 'double']), UIGraphicsBeginImageContextWithOptions: getNativeFunction('UIGraphicsBeginImageContextWithOptions', 'void', [['double', 'double'], 'bool', 'double']), UIGraphicsEndImageContext: getNativeFunction('UIGraphicsEndImageContext', 'void', []), UIGraphicsGetImageFromCurrentImageContext: getNativeFunction('UIGraphicsGetImageFromCurrentImageContext', 'pointer', []), UIImagePNGRepresentation: getNativeFunction('UIImagePNGRepresentation', 'pointer', ['pointer']) }; var view = api.UIWindow.keyWindow(); var bounds = view.bounds(); var size = bounds[1]; api.UIGraphicsBeginImageContextWithOptions(size, 0, 0); view.drawViewHierarchyInRect_afterScreenUpdates_(bounds, true); var image = api.UIGraphicsGetImageFromCurrentImageContext(); api.UIGraphicsEndImageContext(); var png = new ObjC.Object(api.UIImagePNGRepresentation(image)); send('screenshot', Memory.readByteArray(png.bytes(), png.length())); }); } rpc.exports = { takescreenshot: screenshot } ``` ``` ... def save_screenshot(d): f = open('/tmp/screenshot.png', 'wb') f.write(d) f.close() def on_message(msg, data): save_screenshot(data) script.exports.takescreenshot() # open screenshot & invoke rpc via input #
输出示例
TODO[⬆ 回到顶部](#table-of-contents) #### 记录 SSH 命令 ``` Interceptor.attach(ObjC.classes.NMSSHChannel['- execute:error:timeout:'].implementation, { onEnter: function(args) { this.cmd = ObjC.Object(args[2]).toString(); this.timeout = args[4]; }, onLeave: function(retv) { console.log(`CMD: ${ObjC.Object(args[2]).toString()} Timeout: ${args[4]} Ret: ${retv}`); } }); ```
输出示例
TODO[⬆ 回到顶部](#table-of-contents) #### 待办事项 - 添加 GIF 和示例 - 添加指向 /scripts 的链接 - 扩展通用 SSL unpinning(SSL 证书锁定解除)适用于 [ios](https://codeshare.frida.re/@dki/ios10-ssl-bypass/) [android 1](https://github.com/Fuzion24/JustTrustMe/blob/master/app/src/main/java/just/trust/me/Main.java) [android
标签:Android安全, API拦截, API接口, CMS安全, CTF工具, CVE监控, Docker支持, Frida, Frida教程, Hook技术, iOS安全, Java Hook, JavaScript, JS文件枚举, Native Hook, UML, 二进制分析, 云安全运维, 云资产清单, 内存调试, 数据可视化, 漏洞搜索, 目录枚举, 移动安全, 绕过技术, 网络安全, 自定义脚本, 自定义脚本, 逆向工具, 逆向工程, 隐私保护, 高性能