Binder transactions
Get system property
Reveal manually registered native symbols
Enumerate loaded classes
Class description
Turn WiFi off
Set proxy
Get IMEI
Hook io InputStream
Android make Toast
Await for specific module to load
Webview URLS
Print all runtime strings & stacktrace
String comparison
Hook JNI by address
Hook constructor
Hook Java reflection
Trace class
Hooking Unity3d
Get Android ID
Change location
Bypass FLAG_SECURE
Shared Preferences update
Hook all method overloads
Register broadcast receiver
Increase step count
$ frida --codeshare FrenchYeti/android-file-system-access-hook -f com.example.app --no-pause
null
to the implementation
property.{
"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(\"<name>\");\n});"},
{"trigger": "fridaexport", "contents": "Module.findExportByName(null, \"<export_name>\");"},
{"trigger": "fridabase", "contents": "Module.findBaseAddress(name);"},
{"trigger": "fridaoverload", "contents": "kls.method_name.overload().implementation=function(){}"}
]
}
To list abbreviations :ab
Expand by writing key
and <Space>
~/.vimrc
ab fridaintercept Interceptor.attach(ptr, {<CR><Tab>onEnter: function(args) {<CR><CR>},<CR>onLeave: function(retval) {<CR><CR>}<CR><BS>})
ab fridabacktrace console.warn(Thread.backtrace(this.context, Backtracer.ACCURATE).map(DebugSymbol.fromAddress).join('\n'));<ESC>F(3;
ab fridadescribe console.log(Object.getOwnPropertyNames(Java.use('$').__proto__).join('\n\t'))<Esc>F$
Java method hook generator using keyboard shortcut
curl -o ~/$JEB$/scripts/FridaCodeGenerator.py https://raw.githubusercontent.com/iddoeldor/frida-snippets/master/scripts/FridaCodeGenerator.py
Ctrl+Shift+Z
xclip
)var keylog_callback = new NativeCallback(function(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: function (args) {
ptr(args[0]).add(CALLBACK_OFFSET).writePointer(keylog_callback);
}
});
} else if (Java.available) {
var set_keylog_callback = new NativeFunction(Module.findExportByName(Module.findBaseAddress('libssl.so'), 'SSL_CTX_set_keylog_callback'), 'void', ['pointer', 'pointer']);
Interceptor.attach(Module.findExportByName(libSSL, 'SSL_CTX_new'), {
onLeave: function(retval) {
set_keylog_callback(retval, keylog_callback);
}
});
}
#include <iostream>
#include <string>
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"
$ ./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 <stdio.h>
#include <stdlib.h>
#include <android/log.h>
#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', [])()
Intercept funcPtr
& log who read/write to x2
via removing permissions w/ mprotect
.
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
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 example
# wrap the script above inside 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
An example for intercepting libc#open
& logging backtrace if specific file was opened.
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')}`);
}
});
});
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)
List directory contents:
def ls(folder):
cmd = Shell(['/bin/sh', '-c', 'ls -la ' + folder], None)
cmd.exec()
for chunk in cmd.output:
print(chunk.strip().decode())
Pull binary from 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
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)
});
List modules & exports
sudo frida Process --no-pause --eval 'var x={};Process.enumerateModulesSync().forEach(function(m){x[m.name] = Module.enumerateExportsSync(m.name)});x' -q | less +F
Interceptor.attach(Module.findExportByName('libsqlite.so', 'sqlite3_prepare16_v2'), {
onEnter: function(args) {
console.log('DB: ' + Memory.readUtf16String(args[0]) + '\tSQL: ' + Memory.readUtf16String(args[1]));
}
});
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":""}
....
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...
registerNativeMethods
can be used as anti reversing technique to the native .so libraries, e.g. hiding the symbols as much as possible, obfuscating the exported symbols and eventually adding some protection over the JNI bridge.source
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<address, jclass>
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"}
..
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" The symbol's value is absolute, and will not be changed by further linking. "B" The symbol is in the uninitialized data section (known as BSS). "C" The symbol is common. Common symbols are uninitialized data. When linking, multiple common symbols may appear with the same name. If the symbol is defined anywhere, the common symbols are treated as undefined references. "D" The symbol is in the initialized data section. "G" The symbol is in an initialized data section for small objects. Some object file formats permit more efficient access to small data objects, such as a global int variable as opposed to a large global array. "I" The symbol is an indirect reference to another symbol. This is a GNU extension to the a.out object file format which is rarely used. "N" The symbol is a debugging symbol. "R" The symbol is in a read only data section. "S" The symbol is in an uninitialized data section for small objects. "T" The symbol is in the text (code) section. "U" The symbol is undefined. "V" The symbol is a weak object. When a weak defined symbol is linked with a normal defined symbol, the normal defined symbol is used with no error. When a weak undefined symbol is linked and the symbol is not defined, the value of the weak symbol becomes zero with no error. "W" The symbol is a weak symbol that has not been specifically tagged as a weak object symbol. When a weak defined symbol is linked with a normal defined symbol, the normal defined symbol is used with no error. When a weak undefined symbol is linked and the symbol is not defined, the value of the symbol is determined in a system-specific manner without error. On some systems, uppercase indicates that a default value has been specified. "-" The symbol is a stabs symbol in an a.out object file. In this case, the next values printed are the stabs other field, the stabs desc field, and the stab type. Stabs symbols are used to hold debugging information. "?" The symbol type is unknown, or object file format specific.
And save to a file named pkg.classes
$ frida -U com.pkg -qe 'Java.perform(function(){Java.enumerateLoadedClasses({"onMatch":function(c){console.log(c);}});});' -o pkg.classes
Get class methods & members.
Object.getOwnPropertyNames(Java.use('com.company.CustomClass').__proto__).join('\n\t')
If there is a name collision, method & member has the same name, an underscore will be added to member. source
let fieldJsName = env.stringFromJni(fieldName);
while (jsMethods.hasOwnProperty(fieldJsName)) {
fieldJsName = '_' + fieldJsName;
}
It will turn WiFi off on the creation of the first Acivity.
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);
}
It will set a system-wide proxy using the supplied IP address and port.
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);
Can also hook & change IMEI.
function getIMEI(){
console.log('IMEI =', Java.use("android.telephony.TelephonyManager").$new().getDeviceId());
}
Java.perform(getIMEI)
Hook InputputStream
& print buffer as ascii
with char limit & exclude list.
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);
// 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();
});
Await until specific DLL will load in Unity app, can implement hot swap.
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));
Log whenever WebView switch 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);
};
Hoooking toString
of StringBuilder/Buffer & printing stacktrace.
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;
}
});
});
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;
}
});
Hook native method by module name and method address and print arguments.
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'));
}
});
}
}
});
Java.use('java.lang.StringBuilder').$init.overload('java.lang.String').implementation = function(stringArgument) {
console.log("c'tor");
return this.$init(stringArgument);
};
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);
};
Tracing class method, with pretty colors and options to print as JSON & stacktrace.
TODO add trace for c'tor.
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) {
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;
}
}
}
// 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);
The ANDROID_ID is unique in each application in 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'));
}
Java.perform(() => {
var Location = Java.use('android.location.Location');
Location.getLatitude.implementation = function() {
return LATITUDE;
}
Location.getLongitude.implementation = function() {
return LONGITUDE;
}
})
Bypass screenshot prevention stackoverflow question
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);
};
});
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);
}
}
function hookOverloads(className, func) {
var clazz = Java.use(className);
var overloads = clazz[func].overloads;
for (var i in overloads) {
if (overloads[i].hasOwnProperty('argumentTypes')) {
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');
})
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'));
});
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();
}
});
});
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));
}
}
})
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);
})
Log each file open
Interceptor.attach(ObjC.classes.NSFileManager['- fileExistsAtPath:'].implementation, {
onEnter: function (args) {
console.log('open' , ObjC.Object(args[2]).toString());
}
});
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
Get UUID for specific path when attached to an app by reading plist file under each app container.
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') );
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));
Print map of members (with values) for each class instance
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);
}
});
Object.keys(ObjC.classes) will list all available Objective C classes,but actually this will return all classes loaded in current process, including system frameworks.If we want something like weak_classdump, to list classes from executable it self only, Objective C runtime already provides such function objc_copyClassNamesForImage
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);
Hooking 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
To reduce UI related functions I ues the following steps:
-o /tmp/log1
$ sort /tmp/log1 | uniq -c | sort -rn | head -n20 | cut -d# -f2 | paste -sd "," -
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 + "'");
}
})
})
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 () {}
});
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);
}
});
}
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
iter.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();
}
})
$ npm i frida-compile demangler-js -g
add to your script
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));
}
});
});
compile
$ frida-compile script.js -o out.js
run
$ frida -Uf com.app -l out.js
Set hooks before DT_INIT_ARRAY ( source )
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
})
}
})
Credit: iGio90
Example of quick&dirty iOS device properties extraction
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>
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
# <Ctrl+C> 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)
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}`);
}
});
1. Frida概况 frida 是一个 hook 框架 可以在不改动目标源码的情况下,动态查看函数运行入参,返回值,注入代码,更程序逻辑。 黑客可以用hook框架做逆向,分析和利用应用漏洞,进行非法操作。 开发者可以hook自己的应用进程,查看API接口入参,返回值。如果应用出现问题可以通过此方法打印信息,不需要额外增加日志,简单方便。 hook代码使用 js 脚本实现,借助 python 执行
Frida操作java类的一些记录 这篇笔记主要记录在使用Frida写一个测试框架中会需要的Api的使用方法,包括了 何时执行 获取当前系统的信息 获取所有加载到当前进程的类信息 获取app的所有加载的类 获取app加载的所有so模块 获取app类的方法 获取加载模块的方法 操作一个java类对象 修改java的函数参数 修改java的函数的返回值 何时执行 为了保证能正常运行,需要使用如下方法执
https://www.jianshu.com/p/7be526b77bd2 https://www.jianshu.com/p/d4a44f803f33 https://www.jianshu.com/p/bab4f4714d98 https://forum.90sec.com/t/topic/95 http://www.gouzai.pw/2019/03/07/Frida%E4%BB%8E%E
Awesome Frida A curated list of awesome projects, libraries, and tools powered by Frida. What is Frida? Frida is Greasemonkey for native apps, or, put in more technical terms, it’s a dynamic code inst
《Frida操作手册》 PS:by : @hluwa @r0ysue changelog: date log 2019-5-4 4.4 FRIDA脚本系列(四)更新篇:几个主要机制的大更新 2019-3-29 4.3 FRIDA脚本系列(三)超神篇:百度AI“调教”抖音AI 2019-1-16 Brida操作指南 2019-1-11 4.2 FRIDA脚本系列(二)成长篇:动静态结合逆向Whats