Hooks and Tracing

Hooks and Tracing

XHook (Android, PLT hook)

XHook intercepts PLT entries — best for hooking library imports.

 1IxHook xHook = XHookImpl.getInstance(emulator);
 2
 3xHook.register("libtarget.so", "malloc", new ReplaceCallback() {
 4    @Override
 5    public HookStatus onCall(Emulator<?> emulator, long originFunction) {
 6        RegisterContext ctx = emulator.getContext();
 7        int size = ctx.getIntArg(0);
 8        System.out.println("malloc(" + size + ")");
 9        return HookStatus.LR(emulator, 0);    // replace return value
10        // return HookStatus.RET(originFunction); // call original
11    }
12});
13
14xHook.refresh();  // must call after registering

Key classes: unidbg-android/.../hook/xhook/IxHook.java, XHookImpl.java

HookZz (inline hook)

HookZz hooks at arbitrary addresses — works for non-exported functions.

 1IHookZz hookZz = HookZz.getInstance(emulator);
 2
 3hookZz.wrap(module.base + 0x1234, new WrapCallback<RegisterContext>() {
 4    @Override
 5    public void preCall(Emulator<?> emulator, RegisterContext ctx, HookEntryInfo info) {
 6        System.out.println("pre: arg0=" + ctx.getIntArg(0));
 7    }
 8
 9    @Override
10    public void postCall(Emulator<?> emulator, RegisterContext ctx, HookEntryInfo info) {
11        System.out.println("post: ret=" + ctx.getLongArg(0));
12    }
13});

Key classes: unidbg-api/.../hook/hookzz/IHookZz.java, HookZz.java

Whale Hook

1IWhale whale = Whale.getInstance(emulator);
2whale.inlineHookFunction(address, new ReplaceCallback() { ... });

Key classes: unidbg-api/.../hook/whale/IWhale.java

Code tracing

Prefer traceCodeText over traceCode — it is faster (async I/O + instruction cache), outputs more context (memory hexdump, JNI/syscall parsing, function call tracking), and supports module-name filtering and file redirect. Use traceCode only when you need a raw TraceCodeListener callback and nothing else.

 1// traceCode — synchronous, no cache, minimal output
 2emulator.traceCode(module.base, module.base + module.size);
 3
 4// With custom listener (the only reason to prefer traceCode over traceCodeText)
 5emulator.traceCode(module.base, module.base + module.size, new TraceCodeListener() {
 6    @Override
 7    public void onInstruction(Emulator<?> emulator, long address, String asm) {
 8        System.out.println(String.format("0x%x: %s", address, asm));
 9    }
10});

traceCodeText — preferred tracer

traceCodeText is a superset of traceCode: it also hooks memory reads/writes and outputs them alongside each instruction, giving a full execution context dump. Returns a TraceHook that can be detached later.

 1// Trace everything (stdout)
 2TraceHook hook = emulator.traceCodeText();
 3
 4// Trace address range
 5TraceHook hook = emulator.traceCodeText(module.base, module.base + module.size);
 6
 7// Redirect output to file (file is deleted and recreated each run)
 8TraceHook hook = emulator.traceCodeText(module.base, module.base + module.size, "/tmp/trace.txt");
 9
10// Redirect to PrintStream
11TraceHook hook = emulator.traceCodeText(module.base, module.base + module.size, System.err);
12
13// Filter by module name(s) — traces only instructions inside these modules
14TraceHook hook = emulator.traceCodeText(new String[]{"libnative.so"});
15TraceHook hook = emulator.traceCodeText(new String[]{"libnative.so", "libcrypto.so"}, "/tmp/trace.txt");
16
17// With custom TraceCodeListener
18TraceHook hook = emulator.traceCodeText(module.base, module.base + module.size,
19    System.out, new TraceCodeListener() { ... });
20
21// Stop tracing
22hook.detach();

traceCode vs traceCodeText

traceCodetraceCodeText
Disassemblyyesyes
Register read/write valuesyesyes
Memory read/write hexdumpnoyes
Syscall / JNI call parsingnoyes
Function call tracking (enter/return)noyes
SMC (self-modifying code) detectionnoyes
Filter by module namenoyes
Redirect to filenoyes
Returns detachable hookyesyes (TraceHook)
Output threadsynchronousasync (dedicated logger thread)
Instruction cachenoneL1 direct-mapped (8M slots) + L2 LRU (1M)

Performance characteristics of traceCodeText

traceCodeText is specifically engineered for high-throughput tracing:

In practice traceCodeText is faster than traceCode for large traces because traceCode calls emulator.printAssemble() (which also reads registers and computes memory addresses) synchronously on every instruction without any caching or async I/O.

Tuning options (traceCodeText only)

1TraceHook hook = emulator.traceCodeText(module.base, module.base + module.size);
2
3hook.setDisableHexdump(true);        // skip memory read/write dumps
4hook.setDisableFunctionCall(true);   // skip bl/blr/svc call tracking
5hook.setDisableSMC(true);            // skip machine-code cache validation (faster for non-SMC code)

Memory tracing

 1// Trace memory reads
 2emulator.traceRead(startAddress, endAddress);
 3
 4// Trace memory writes
 5emulator.traceWrite(startAddress, endAddress);
 6
 7// With custom listeners
 8emulator.traceRead(start, end, new TraceReadListener() {
 9    @Override
10    public boolean onRead(Emulator<?> emulator, long address, int size, long value) {
11        System.out.printf("READ  0x%x size=%d val=0x%x%n", address, size, value);
12        return true;
13    }
14});

Verbose JNI logging

1vm.setVerbose(true);  // prints every unhandled JNI call with its signature

Use this during development to discover which JNI methods the native library calls.

Debugger attachment

1// Attach GDB debugger (blocks until client connects)
2emulator.attach(DebuggerType.GDB_SERVER);
3
4// IDA debugger
5emulator.attach(DebuggerType.ANDROID_SERVER_V7);

Key listener interfaces: unidbg-api/.../listener/TraceCodeListener.java, TraceReadListener.java, TraceWriteListener.java