Common Frida Hooks

推荐运行环境

Templates

Frida Hook Runner(Node.js + TypeScript)

 1// index.ts — spawn 模式注入脚本,支持超时自动 detach
 2import frida from "frida";
 3import path from "path";
 4
 5const TARGET_PACKAGE = "com.example.target"; // 替换为目标包名
 6const DEFAULT_TIMEOUT_MS = 15000;
 7
 8const hookScript = process.argv[2];
 9if (!hookScript) {
10  console.error("Usage: node index.ts <hook-script.ts>");
11  process.exit(1);
12}
13const hookPath = path.isAbsolute(hookScript) ? hookScript : path.resolve(process.cwd(), hookScript);
14
15function getInjectTimeoutMs(): number {
16  const rawValue = process.env.INJECT_TIMEOUT_MS;
17  if (!rawValue) return DEFAULT_TIMEOUT_MS;
18
19  const timeoutMs = Number(rawValue);
20  if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) return DEFAULT_TIMEOUT_MS;
21
22  return timeoutMs;
23}
24
25function onMessage(message: frida.Message, _data: Buffer | null): void {
26  if (message.type === "send") console.log("[*]", (message as frida.SendMessage).payload);
27  else if (message.type === "error") console.log("[!]", (message as frida.ErrorMessage).stack);
28}
29
30async function compileAgent(): Promise<string> {
31  const compiler = new frida.Compiler();
32  compiler.diagnostics.connect((diag) => console.log("[diag]", diag));
33  return compiler.build(hookPath);
34}
35
36async function main(): Promise<void> {
37  const bundle = await compileAgent();
38  const device = await frida.getUsbDevice();
39  const pid = await device.spawn([TARGET_PACKAGE]);
40  const session = await device.attach(pid);
41  const script = await session.createScript(bundle);
42  const timeoutMs = getInjectTimeoutMs();
43
44  let exitResolve: () => void;
45  const exitPromise = new Promise<void>((resolve) => { exitResolve = resolve; });
46
47  script.message.connect((message, data) => {
48    if (message.type === "send" && (message as frida.SendMessage).payload === "__exit__") {
49      console.log("[*] script requested exit, detaching...");
50      exitResolve();
51      return;
52    }
53    onMessage(message, data);
54  });
55
56  await script.load();
57  await device.resume(pid);
58  console.log(`[*] ${path.basename(hookPath)} loaded. Press Ctrl+C to detach.`);
59
60  await Promise.race([
61    exitPromise,
62    new Promise<void>((resolve) => { process.on("SIGINT", resolve); process.on("SIGTERM", resolve); }),
63    new Promise<void>((resolve) => setTimeout(() => {
64      const timeoutLabel = timeoutMs === DEFAULT_TIMEOUT_MS ? "15s" : `${timeoutMs}ms`;
65      console.log(`[*] timeout (${timeoutLabel}), force detaching...`);
66      resolve();
67    }, timeoutMs)),
68  ]);
69
70  await session.detach();
71  process.exit(0);
72}
73
74main().catch((e) => { console.error("[!]", e); process.exit(1); }).finally(() => {
75  console.log("[*] Force detached.");
76  process.exit(0);
77});

用法说明:

Interceptor 基本模式 — onEnter/onLeave 上下文传值 & 寄存器读写

1Interceptor.attach(ptr(0x0), {
2    onEnter(args) {
3        this.anyValue = args[0]; // same context
4        (this.context as Arm64CpuContext).x1 = ptr(0x1234); // type safe read
5    },
6    onLeave(ret) {
7        if (this.anyValue) { console.log(ret) }
8    }
9})

Hooks

动态加载类全方法 Hook — ClassLoader 拦截 + 批量 hook

 1// 拦截动态 dex 加载的类,hook 其所有方法并记录参数/返回值
 2import Java from "frida-java-bridge";
 3
 4const TARGET_CLASS = "com.example.TargetClass"; // 替换为目标类名
 5let installed = false;
 6
 7function hookAllMethods(loader: any): void {
 8    if (installed) return;
 9    installed = true;
10
11    const factory = Java.ClassFactory.get(loader);
12    const Clazz = factory.use(TARGET_CLASS);
13    const methods = Clazz.class.getDeclaredMethods();
14
15    for (let mi = 0; mi < methods.length; mi++) {
16        const name: string = methods[mi].getName();
17        const overloads = (Clazz as any)[name]?.overloads;
18        if (!overloads) continue;
19
20        for (const overload of overloads) {
21            const methodName = name;
22            overload.implementation = function (...args: any[]) {
23                console.log(`[CALL] ${methodName}(${args.map(a => a?.toString?.() ?? "null").join(", ")})`);
24                const ret = overload.call(this, ...args);
25                console.log(`[RET]  ${methodName} => ${ret}`);
26                return ret;
27            };
28        }
29    }
30    console.log(`[+] ${TARGET_CLASS} all methods hooked`);
31}
32
33function tryFindAndHook(targetClass: string): void {
34    if (installed) return;
35    Java.enumerateClassLoaders({
36        onMatch(loader) {
37            if (installed) return;
38            try {
39                loader.loadClass(targetClass);
40                console.log(`[+] found ${targetClass} in loader: ${loader}`);
41                hookAllMethods(loader);
42            } catch (_) { /* not in this loader */ }
43        },
44        onComplete() {}
45    });
46}
47
48Java.perform(() => {
49    if (installed) return;
50
51    // 拦截 DexClassLoader 以捕获动态加载时机
52    try {
53        const DCL = Java.use("dalvik.system.DexClassLoader");
54        DCL.$init.implementation = function (dexPath: string, optimizedDirectory: string, librarySearchPath: string, parent: any) {
55            DCL.$init.call(this, dexPath, optimizedDirectory, librarySearchPath, parent);
56            if (!installed) setImmediate(() => Java.performNow(() => tryFindAndHook(TARGET_CLASS)));
57        };
58        console.log("[+] DexClassLoader.$init hooked");
59    } catch (e) {
60        console.log(`[i] DexClassLoader hook failed: ${e}`);
61    }
62
63    console.log(`[*] Waiting for ${TARGET_CLASS} to be loaded...`);
64});

Module Observer — 第一时间 Hook 目标 so

 1// 监听 so 加载,目标模块映射后立即 hook
 2import Java from "frida-java-bridge";
 3
 4Process.attachModuleObserver({
 5    onAdded(module: Module) {
 6        if (module.name !== "libtarget.so") return; // 替换为目标 so 名
 7
 8        const targetAddr = module.base.add(0x1000); // 替换为目标偏移
 9        Interceptor.attach(targetAddr, {
10            onEnter(args) {
11                send(`JNI_OnLoad called! JavaVM=${args[0]} reserved=${args[1]}`);
12            },
13            onLeave(retval) {
14                send(`JNI_OnLoad returned: ${retval}`);
15                send("__exit__");
16            }
17        });
18    }
19});

异步 Hook — Promise 桥接回调,用于保证 hook 先后加载顺序、类的准确时机加载

 1// 核心:用 Promise 把 hook 回调转成 await 接口,resolve 可传值给下游
 2import Java from "frida-java-bridge";
 3
 4let resolveA: (v: string) => void;
 5const classALoaded = new Promise<string>((r) => { resolveA = r; });
 6
 7// 第一步:hook ClassA,加载完成后 resolve 传值
 8Java.perform(() => {
 9    const ClassA = Java.use("com.example.ClassA");
10    ClassA["init"].implementation = function (...args: any[]) {
11        const result = this["init"](...args);
12        resolveA(result.toString()); // 传值给下游
13        return result;
14    };
15});
16
17// 第二步:ClassB 依赖 ClassA 先加载,必须 await 前者完成后才能 hook
18async function hookClassB() {
19    const valueFromA = await classALoaded; // 阻塞直到 ClassA.init 被调用
20    console.log(`ClassA loaded with: ${valueFromA}, now hooking ClassB...`);
21
22    // await 之后已在 Java 上下文中,大概率不需要再套 Java.perform
23    const ClassB = Java.use("com.example.ClassB");
24    ClassB["process"].implementation = function (...args: any[]) {
25        console.log(`ClassB.process called, depends on: ${valueFromA}`);
26        return this["process"](...args);
27    };
28}
29
30hookClassB();

Hook JNI RegisterNatives

 1// Hook JNI RegisterNatives via JNIEnv 函数表(偏移 215,跨版本稳定)
 2const env = Java.vm.getEnv();
 3const RegisterNatives = env.handle.readPointer().add(215 * Process.pointerSize).readPointer();
 4
 5Interceptor.attach(RegisterNatives, {
 6    onEnter(args) {
 7        const env = Java.vm.tryGetEnv();
 8        if (!env) return;
 9
10        const className = env.getClassName(args[1]);
11        const methods = args[2];
12        const nMethods = args[3].toInt32();
13
14        for (let i = 0; i < nMethods; i++) {
15            const base = methods.add(i * Process.pointerSize * 3);
16            const name = base.readPointer().readCString();
17            const sig = base.add(Process.pointerSize).readPointer().readCString();
18            const fnPtr = base.add(Process.pointerSize * 2).readPointer();
19
20            const mod = Process.findModuleByAddress(fnPtr);
21            const modName = mod ? mod.name : "unknown";
22            const off = mod ? fnPtr.sub(mod.base) : ptr(0);
23
24            console.log(`[RegisterNatives] ${className}.${name}${sig} => ${fnPtr} (${modName}+0x${off.toString(16)})`);
25        }
26    }
27});