Frida dynamic instrumentation
Local setup (verified in tool-index.md): frida 17.17.0 / frida-tools
14.10.4 installed via pip. The frida CLI scripts live in
C:\Users\Admin\AppData\Local\Python\bin\ — not on PATH; call
python -m frida_tools.repl (or ...repl -U for USB/Android) or add that
bin dir to PATH.
Session basics
python -m frida_tools.repl -f C:/target/game.exe -l hook.js --runtime=v8 # spawn
python -m frida_tools.repl -n game.exe -l hook.js # attach
python -m frida_tools.repl -U -f com.vendor.app -l hook.js # android
Spawn > attach for early code (loaders, anti-debug init). -f suspends at
entry; call resume() from the script or --pause flows when you must hook
before the first instruction.
Interceptor (the 90% tool)
const f = Module.getExportByName(null, "ws2_32!send"); // null = search all modules
Interceptor.attach(f, {
onEnter(args) { this.buf = args[1]; this.len = args[2].toInt32(); },
onLeave(rv) {
if (this.len > 0) send({tag:"send", data: hexdump(this.buf, {length: Math.min(this.len, 256)}).toString()});
}
});
Module.getExportByName(null, name) resolves across loaded modules; for
unexported targets compute the address first (pattern-scanner in-process:
Memory.scan on module ranges) and Interceptor.attach(ptr, {...}).
- Calling natives:
new NativeFunction(addr, retType, argTypes); keep
ABI defaults (Windows x64 = MSVC ABI).
- Replace vs attach:
Interceptor.replace swaps implementation (use for
neutering, e.g. anti-debug); attach preserves behavior — prefer attach
when the target must keep working.
- Always wrap per-hook logic in try/catch and keep hooks idempotent: a hook
that throws poisons every subsequent call through that site.
Memory & module APIs
Memory.scan(base, size, "48 8B ?? 05") — live pattern validation before
committing a signature to the DB (offset-dumper pipeline step).
Process.enumerateModules(), Module.load(), Process.findRangeByAddress
for VAD-ish views usermode-side.
Memory.readPointer/readByteArray/writeU… + Memory.protect for patching;
scope writes behind flags — a stray patch survives you.
Stalker (tracing, expensive)
Stalker.follow(threadId, {
events: { call: true, ret: false, exec: false },
onReceive(events) { /* parse binary array; batch-send, never per-event send() */ }
});
- Follow one thread at a time;
exec: true floods (millions/s).
- Use for: recovering dispatch tables (follow the packet-handler thread and
record call targets), locating checks (follow until a "bad" result, bisect
the trace), coverage-guided triage.
Stalker.unfollow + Stalker.flush() on teardown; leak a follow and the
process dies with it.
- Cheaper alternative first:
frida-trace with -i/-a module!function
filters, or Interceptor-only call maps.
Anti-anti-debug / analysis-resistance patterns
| Check |
Bypass shape |
IsDebuggerPresent / PEB.BeingDebugged |
Interceptor.replace → return 0, or write PEB byte once (teeb walk from gs:[0x60]) |
NtQueryInformationProcess (ProcessDebugPort/Flags/ObjectHandle) |
hook and rewrite the info buffer onLeave |
CheckRemoteDebuggerPresent |
same via API hook |
Timing (rdtsc-via-cpuid gates, GetTickCount deltas) |
hook GetSystemTimeAsFileTime/QueryPerformanceCounter families and normalize; cpuid-based gates need Stalker or a patched branch |
NtSetInformationThread(ThreadHideFromDebugger) |
hook and swallow |
Hardware bp checks (GetThreadContext Dr registers) |
rewrite CONTEXT onLeave |
| EnumerateLoadedModules / module scans for frida itself |
see OPSEC below |
Apply bypasses narrowly (one check at a time, verified) — wholesale
"anti-anti" scripts break games/apps and destroy evidence of which check
fired. For deep debugger-based work switch to x64dbg-anti-debugger.
Games (mono / il2cpp)
- Unity mono: enumerate
mono.dll exports (mono_get_root_domain,
mono_thread_attach first!), walk assemblies via mono_assembly_foreach
NativeFunctions; or use community frida-il2cpp-bridge for il2cpp games
(npm-installable; pairs with offset-dumper's il2cpp pipeline for static
ground truth).
- Dump vtables/methods at runtime when static metadata is encrypted —
often the only stable source on protected builds.
Gadget & server modes
- frida-server (remote/Android): run on device, connect
-U/-H.
OPSEC: default name/port is a first-order IOC — rename binary, non-default
port, bind localhost + adb forward (adb forward tcp:PORT tcp:PORT).
- gadget (embedded in the app you own/build): config modes
listen/connect/script/directory; use script for fully offline
instrumentation with zero server artifacts.
Reliability discipline (long sessions)
- One concern per script version; reload (
-l again / %reload) after
edits rather than stacking variants.
- Batch
send() — coalesce per 50 ms or per N events; per-event RPC kills
throughput and can desync the REPL.
- Save state: dump captured structures to files via
rpc.exports +
host-side writer; don't keep everything in JS heap.
- Teardown: unfollow Stalkers, detach Interceptors (
Interceptor.detachAll
or per-listener handles) when the mission ends — leave a target running
with hooks and your next attach inherits the mess.
- Record: target build + module hashes in every capture log header
(
windows-internals provenance rules apply).
Pair with
offset-dumper (live validation + encrypted-pointer recovery),
network-protocol-re (plaintext hooks at send/EncryptMessage),
js-reverse/mobile-reverse (ecosystem-specific flows),
x64dbg-anti-debugger (debugger-side counterpart),
malware-analysis (sample triage), edr-bypass-re (defender-side hooks
being studied).
1---2name: frida-dbi3description: Frida dynamic binary instrumentation for RE and security research: spawn/attach, Interceptor hooks, NativeFunction, Memory scanning, Stalker tracing, gadget modes, anti-anti-debug bypass patterns, il2cpp/mono bridges for games, OPSEC of frida-server, and reliability discipline for long sessions. Local stack: frida 17.17.0 + frida-tools 14.10.4 via pip.4license: GPL-3.0-or-later5---67# Frida dynamic instrumentation89Local setup (verified in `tool-index.md`): frida 17.17.0 / frida-tools1014.10.4 installed via pip. The `frida` CLI scripts live in11`C:\Users\Admin\AppData\Local\Python\bin\` — not on PATH; call12`python -m frida_tools.repl` (or `...repl -U` for USB/Android) or add that13bin dir to PATH.1415## Session basics1617```bash18python -m frida_tools.repl -f C:/target/game.exe -l hook.js --runtime=v8 # spawn19python -m frida_tools.repl -n game.exe -l hook.js # attach20python -m frida_tools.repl -U -f com.vendor.app -l hook.js # android21```2223Spawn > attach for early code (loaders, anti-debug init). `-f` suspends at24entry; call `resume()` from the script or `--pause` flows when you must hook25before the first instruction.2627## Interceptor (the 90% tool)2829```js30const f = Module.getExportByName(null, "ws2_32!send"); // null = search all modules31Interceptor.attach(f, {32 onEnter(args) { this.buf = args[1]; this.len = args[2].toInt32(); },33 onLeave(rv) {34 if (this.len > 0) send({tag:"send", data: hexdump(this.buf, {length: Math.min(this.len, 256)}).toString()});35 }36});37```3839- `Module.getExportByName(null, name)` resolves across loaded modules; for40 unexported targets compute the address first (`pattern-scanner` in-process:41 `Memory.scan` on module ranges) and `Interceptor.attach(ptr, {...})`.42- Calling natives: `new NativeFunction(addr, retType, argTypes)`; keep43 `ABI` defaults (Windows x64 = MSVC ABI).44- Replace vs attach: `Interceptor.replace` swaps implementation (use for45 neutering, e.g. anti-debug); `attach` preserves behavior — prefer attach46 when the target must keep working.47- Always wrap per-hook logic in try/catch and keep hooks idempotent: a hook48 that throws poisons every subsequent call through that site.4950## Memory & module APIs5152- `Memory.scan(base, size, "48 8B ?? 05")` — live pattern validation before53 committing a signature to the DB (`offset-dumper` pipeline step).54- `Process.enumerateModules()`, `Module.load()`, `Process.findRangeByAddress`55 for VAD-ish views usermode-side.56- `Memory.readPointer/readByteArray/writeU…` + `Memory.protect` for patching;57 scope writes behind flags — a stray patch survives you.5859## Stalker (tracing, expensive)6061```js62Stalker.follow(threadId, {63 events: { call: true, ret: false, exec: false },64 onReceive(events) { /* parse binary array; batch-send, never per-event send() */ }65});66```6768- Follow one thread at a time; `exec: true` floods (millions/s).69- Use for: recovering dispatch tables (follow the packet-handler thread and70 record call targets), locating checks (follow until a "bad" result, bisect71 the trace), coverage-guided triage.72- `Stalker.unfollow` + `Stalker.flush()` on teardown; leak a follow and the73 process dies with it.74- Cheaper alternative first: `frida-trace` with `-i`/`-a` module!function75 filters, or Interceptor-only call maps.7677## Anti-anti-debug / analysis-resistance patterns7879| Check | Bypass shape |80|---|---|81| `IsDebuggerPresent` / PEB.BeingDebugged | `Interceptor.replace` → return 0, or write PEB byte once (`teeb` walk from `gs:[0x60]`) |82| `NtQueryInformationProcess` (ProcessDebugPort/Flags/ObjectHandle) | hook and rewrite the info buffer onLeave |83| `CheckRemoteDebuggerPresent` | same via API hook |84| Timing (`rdtsc`-via-cpuid gates, `GetTickCount` deltas) | hook `GetSystemTimeAsFileTime`/`QueryPerformanceCounter` families and normalize; cpuid-based gates need Stalker or a patched branch |85| `NtSetInformationThread(ThreadHideFromDebugger)` | hook and swallow |86| Hardware bp checks (`GetThreadContext` Dr registers) | rewrite CONTEXT onLeave |87| EnumerateLoadedModules / module scans for frida itself | see OPSEC below |8889Apply bypasses narrowly (one check at a time, verified) — wholesale90"anti-anti" scripts break games/apps and destroy evidence of *which* check91fired. For deep debugger-based work switch to `x64dbg-anti-debugger`.9293## Games (mono / il2cpp)9495- Unity mono: enumerate `mono.dll` exports (`mono_get_root_domain`,96 `mono_thread_attach` first!), walk assemblies via `mono_assembly_foreach`97 NativeFunctions; or use community `frida-il2cpp-bridge` for il2cpp games98 (npm-installable; pairs with `offset-dumper`'s il2cpp pipeline for static99 ground truth).100- Dump vtables/methods at runtime when static metadata is encrypted —101 often the only stable source on protected builds.102103## Gadget & server modes104105- **frida-server (remote/Android)**: run on device, connect `-U`/`-H`.106 OPSEC: default name/port is a first-order IOC — rename binary, non-default107 port, bind localhost + adb forward (`adb forward tcp:PORT tcp:PORT`).108- **gadget** (embedded in the app you own/build): config modes109 `listen/connect/script/directory`; use `script` for fully offline110 instrumentation with zero server artifacts.111112## Reliability discipline (long sessions)1131141. One concern per script version; reload (`-l` again / `%reload`) after115 edits rather than stacking variants.1162. Batch `send()` — coalesce per 50 ms or per N events; per-event RPC kills117 throughput and can desync the REPL.1183. Save state: dump captured structures to files via `rpc.exports` +119 host-side writer; don't keep everything in JS heap.1204. Teardown: unfollow Stalkers, detach Interceptors (`Interceptor.detachAll`121 or per-listener handles) when the mission ends — leave a target running122 with hooks and your next attach inherits the mess.1235. Record: target build + module hashes in every capture log header124 (`windows-internals` provenance rules apply).125126## Pair with127128`offset-dumper` (live validation + encrypted-pointer recovery),129`network-protocol-re` (plaintext hooks at send/EncryptMessage),130`js-reverse`/`mobile-reverse` (ecosystem-specific flows),131`x64dbg-anti-debugger` (debugger-side counterpart),132`malware-analysis` (sample triage), `edr-bypass-re` (defender-side hooks133being studied).