frida-snippets

Hand-crafted Frida examples

Stars
2.3K

PRs Welcome & also output examples

Table of Contents

{
    "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>

  • Add to ~/.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

  1. curl -o ~/$JEB$/scripts/FridaCodeGenerator.py https://raw.githubusercontent.com/iddoeldor/frida-snippets/master/scripts/FridaCodeGenerator.py
  2. Place cursor at Java method's signature
  3. Press Ctrl+Shift+Z
  4. Code is copied to system clipboard (using xclip)

Fetch SSL keys

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)
    }
  })
}

⬆ Back to top

Load CPP module

#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"

Load C module

$ ./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', [])()

⬆ Back to top

One time watchpoint

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

⬆ Back to top

Socket activity

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

⬆ Back to top

Intercept Open

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')}`);
    }
  });
});

⬆ Back to top

Execute shell command

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

⬆ Back to top

List modules

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
{
  "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": "[email protected]",
  "base": "0x749b448000",
  "size": 90112,
  "path": "/system/lib64/[email protected]"
}
{
  "name": "[email protected]",
  "base": "0x749ac9e000",
  "size": 94208,
  "path": "/system/lib64/[email protected]"
}
{
  "name": "[email protected]",
  "base": "0x74981e0000",
  "size": 98304,
  "path": "/system/lib64/[email protected]"
}
{
  "name": "[email protected]",
  "base": "0x73fb4cc000",
  "size": 40960,
  "path": "/vendor/lib64/hw/[email protected]"
}
{
  "name": "[email protected]",
  "base": "0x73fb51f000",
  "size": 90112,
  "path": "/system/lib64/vndk-sp-29/[email protected]"
}
{
  "name": "[email protected]",
  "base": "0x73fb542000",
  "size": 94208,
  "path": "/system/lib64/vndk-sp-29/[email protected]"
}
{
  "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"
}

⬆ Back to top

Log SQLite query

Interceptor.attach(Module.findExportByName('libsqlite.so', 'sqlite3_prepare16_v2'), {
      onEnter: function(args) {
          console.log('DB: ' + Memory.readUtf16String(args[0]) + '\tSQL: ' + Memory.readUtf16String(args[1]));
      }
});

⬆ Back to top

system property get

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":""}
....

⬆ Back to top

Binder transactions

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  [email protected].
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...

⬆ Back to top

Reveal native methods

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"}
..

⬆ Back to top

Log method arguments

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)

⬆ Back to top

Enumerate loaded classes

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

⬆ Back to top

Class description

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;
	}

⬆ Back to top

Turn Wifi OFF

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);
}

⬆ Back to top

Set proxy

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);

⬆ Back to top

Get IMEI

Can also hook & change IMEI.

function getIMEI(){
    console.log('IMEI =', Java.use("android.telephony.TelephonyManager").$new().getDeviceId());
}
Java.perform(getIMEI)

⬆ Back to top

Hook io InputStream

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);

⬆ Back to top

Android make 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();
});

Await for condition

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));

⬆ Back to top

Webview URLS

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);
};

⬆ Back to top

Print runtime strings

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;
    }   
  }); 
});

⬆ Back to top

String comparison

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;
    }
});

⬆ Back to top

Hook JNI by address

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'));
                }
            });
        }
    }
});

⬆ Back to top

Hook constructor

Java.use('java.lang.StringBuilder').$init.overload('java.lang.String').implementation = function(stringArgument) {
      console.log("c'tor");
      return this.$init(stringArgument);
};

⬆ Back to top

Hook reflection

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);
  };

⬆ Back to top

Trace class

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) {
    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);


⬆ Back to top

Get Android ID

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'));
}

⬆ Back to top

Change location

Java.perform(() => {
	var Location = Java.use('android.location.Location');
	Location.getLatitude.implementation = function() {
		return LATITUDE;
	}
	Location.getLongitude.implementation = function() {
		return LONGITUDE;
	}
})

⬆ Back to top

Bypass FLAG_SECURE

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);
    };
});

⬆ Back to top

Shared Preferences update

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);
  }
}

⬆ Back to top

Hook overloads

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');
})

⬆ Back to top

Register broadcast receiver

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'));
});

⬆ Back to top

list classes implements interface

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
      }
    }
  })
}

⬆ Back to top

Increase step count

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();
    }
  });
});

⬆ Back to top

OS Log

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));
    }
  }
})

⬆ Back to top

iOS alert box

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);
})

⬆ Back to top

File Access

Log each file open

Interceptor.attach(ObjC.classes.NSFileManager['- fileExistsAtPath:'].implementation, {
    onEnter: function (args) {
        console.log('open' , ObjC.Object(args[2]).toString());
    }
});

⬆ Back to top

Observe class

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

⬆ Back to top

Find iOS application UUID

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') );

⬆ Back to top

Extract 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));

⬆ Back to top

Describe class members

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);
  }
});

⬆ Back to top

Class hierarchy

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);

⬆ Back to top

Hook refelaction

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

⬆ Back to top

Intercept Entire Module

To reduce UI related functions I ues the following steps:

  1. Output log to a file using -o /tmp/log1
  2. Copy MRU to excludesList using $ 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 + "'");
      }
    })
  })

⬆ Back to top

Dump memory segments

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 () {}
});

⬆ Back to top

Memory scan

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);
		}
	});
}

⬆ Back to top

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();
    }   
  })  

⬆ Back to top

Cpp demangler

$ 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

⬆ Back to top

Early hook

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

⬆ Back to top

Device properties

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>

⬆ Back to top

Take screenshot

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)

⬆ Back to top

Log SSH Commands

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}`);
  }  
}); 

⬆ Back to top

TODOs

  • Add GIFs & examples
  • Add links to /scripts
  • Extend universal SSL unpinning for ios andoid 1 [android