Last active
October 1, 2026 10:58
-
-
Save vadim-a-yegorov/6dad7d8a1e73ad5b211032f6e6bbeead to your computer and use it in GitHub Desktop.
Hook, intercept, trace, or patch a live binary across Mach-O, ELF, and PE — Frida Interceptor/Stalker/Memory, ELF LD_PRELOAD + dlsym(RTLD_NEXT) + GOT/PLT + LD_AUDIT, Mach-O DYLD_INSERT_LIBRARIES + __interpose + fishhook, Windows Detours + MinHook + IAT/EAT, ObjC/Java swizzle, inline trampolines, eBPF uprobes. Plus a cheat-code block of fast hook…
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| frida | |
| Attach: frida -U -n App -l s.js | frida -U -f com.x.app --no-pause -l s.js | frida -p PID. | |
| 1 Frida Interceptor.attach — read/modify args + return [cross] | |
| inspect or tweak inputs/outputs of a known function without replacing it. | |
| 1 resolve the address (Module.getExportByName(null,...) searches all modules; pass a module name to scope). | |
| 2 onEnter runs before the body: args is a NativePointer[]; read with .readUtf8String()/.readU32()/.readPointer(); edit by assigning args[i]=ptr(x) (writes the register/stack slot). | |
| 3 stash per-call state on this (a fresh object per invocation) to use it in onLeave. | |
| 4 this.context is the CpuContext (this.context.rdi / .x0 ...) for register reads and backtraces. | |
| 5 onLeave gets the return value; retval.replace(ptr) overwrites it after the body ran. | |
| const open = Module.getExportByName(null, 'open'); | |
| Interceptor.attach(open, { | |
| onEnter(args) { // args[i] = NativePointer of the i-th arg | |
| this.path = args[0].readUtf8String(); | |
| this.flags = args[1].toInt32(); | |
| if (this.path && this.path.includes('secret')) args[1] = ptr(this.flags & ~2); // O_RDWR->drop write | |
| console.log('open', this.path, this.flags, 'from\n' + | |
| Thread.backtrace(this.context, Backtracer.ACCURATE).map(DebugSymbol.fromAddress).join('\n')); | |
| }, | |
| onLeave(retval) { // retval is a NativePointer; .replace() to change it | |
| if (this.path === '/etc/blocked') retval.replace(ptr(-1)); | |
| } | |
| }); | |
| attaching to a tiny leaf that is inlined misses callers -> attach the caller or use Stalker. On arm64 PAC targets Frida handles signing; on a stripped binary use T4 to find the address. Interceptor cannot attach mid-instruction; it needs a function entry or a valid instruction boundary. Verify: trigger the call path; the console.log fires with the live args + backtrace. | |
| 2 Frida Interceptor.replace — swap the whole implementation [cross] | |
| fully control a function's behavior, or stub it. | |
| 1 build a NativeFunction over the original FIRST (before replacing) so you keep a call-through. | |
| 2 NativeCallback(fn, retType, argTypes) wraps a JS fn as native; the signature MUST match the ABI. | |
| 3 Interceptor.replace swaps the entry; keep a reference to the NativeCallback alive (store it globally) or GC frees it and the target crashes. | |
| const t = Module.getExportByName(null, 'strcmp'); | |
| const orig = new NativeFunction(t, 'int', ['pointer','pointer']); | |
| Interceptor.replace(t, new NativeCallback((a, b) => { | |
| const r = orig(a, b); // call-through when you still want the real result | |
| console.log('strcmp', a.readUtf8String(), b.readUtf8String(), '=', r); | |
| return 0; // or force a verdict | |
| }, 'int', ['pointer','pointer'])); | |
| replace is heavier than attach; use attach if you only need to observe/patch args. Signature mismatch = stack corruption. Revert with Interceptor.revert(t). | |
| verify: the call returns your forced value; call-through log matches. | |
| 3 Frida call native code + allocate args [cross] | |
| invoke target functions directly (decrypt a buffer, call a getter, resolve an object). | |
| 1 Memory.alloc(n) returns a NativePointer to GC-managed memory; Memory.allocUtf8String for C strings. | |
| 2 wrap the target with NativeFunction(addr, ret, [args]); addr = Module.findBaseAddress('lib').add(off) for a non-exported fn. | |
| 3 call like a JS function; read results from the out pointer. | |
| const malloc = new NativeFunction(Module.getExportByName(null,'malloc'), 'pointer', ['size_t']); | |
| const dec = new NativeFunction(ptr('0x100abc'), 'int', ['pointer','int','pointer']); // off from base below | |
| const buf = Memory.alloc(64); buf.writeByteArray([0xde,0xad]); // Frida-owned, auto-freed | |
| const cstr = Memory.allocUtf8String('/bin/sh'); | |
| const out = malloc(0x100); | |
| const n = dec(buf, 2, out); // call it | |
| console.log('dec->', n, out.readByteArray(n)); | |
| a non-exported fn needs base+offset (rebased per run; compute from Module base, never a hardcoded VA). 'int64'/'uint64' args take a Frida Int64. Calling a fn that expects a live this/context will fault without it. | |
| verify: compare the output to a known plaintext/expected result. | |
| 4 Frida find functions (export / symbol / signature) [cross] | |
| resolve an address on a stripped or dynamically-loaded target. | |
| 1 prefer exports (getExportByName) > debug symbols (DebugSymbol) > signature scan > ObjC/Java reflection. | |
| 2 for a signature: take ~8-16 bytes of the prologue from your disasm, wildcard the relocated/variable bytes (??). | |
| 3 Memory.scan is async; act in onMatch. | |
| Module.getExportByName('libssl.so','SSL_read'); // exported | |
| Process.getModuleByName('libfoo.so').enumerateExports().filter(e=>e.name.includes('parse')); | |
| DebugSymbol.fromName('mangled_or_dwarf_name').address; // if symbols present | |
| // signature scan for a stripped fn: | |
| const m = Process.getModuleByName('app'); | |
| Memory.scan(m.base, m.size, '55 48 89 e5 ?? ?? ?? e8', { onMatch(a){ console.log('hit', a); }, onComplete(){} }); | |
| // ObjC/Java resolvers: ObjC.classes / Java.enumerateLoadedClasses (T7/T8) | |
| exports are rebased at runtime -> always relative to Module.base. A signature that includes a relocated operand breaks across builds; wildcard it. Multiple matches -> tighten the pattern. | |
| verify: attach to the resolved address and confirm the expected args appear. | |
| 5 Frida Memory read/write/scan/patchCode [cross] | |
| dump/edit process memory, or patch instructions in place. | |
| 1 read: p.readByteArray(n) / p.readU64() / hexdump(p,{length}). | |
| 2 write code: Memory.patchCode handles W^X + i-cache flush for you; use the arch Writer to emit instructions. | |
| 3 raw data write: Memory.protect(p,n,'rw'); p.writeByteArray([...]). | |
| const p = Module.findBaseAddress('app').add(0x4000); | |
| console.log(hexdump(p, {length:64})); | |
| Memory.protect(p, 16, 'rwx'); | |
| Memory.patchCode(p, 8, code => { const w = new Arm64Writer(code, {pc:p}); w.putRet(); w.flush(); }); // arm64 | |
| // x86: new X86Writer(code,{pc:p}); w.putNop(); ... w.flush(); | |
| const found = Memory.scanSync(p, 0x1000, '41 42 43 44'); // sync variant returns [{address,size}] | |
| patchCode's callback gets a scratch buffer mapped at the right PC; pass {pc:p} so relative branches assemble correctly. On hardened-runtime iOS, code pages may resist rwx -> use a tracer/breakpoint (dynamic-memory-access) instead. | |
| verify: re-hexdump; the bytes changed; the patched path takes the new branch. | |
| 6 Frida Stalker — coverage + call trace [cross] | |
| record executed basic blocks (drcov for a fuzzer) or trace calls in a region. | |
| 1 follow the thread doing the work; events.compile gives block edges (feed to a coverage map). | |
| 2 transform() lets you insert putCallout before/after chosen instructions (bl/blr/ret/cmp). | |
| 3 unfollow + flush to drain buffered events. | |
| const mod = Process.getModuleByName('app'); | |
| Stalker.follow(Process.getCurrentThreadId(), { | |
| events: { compile: true }, // block-granularity coverage | |
| onReceive(ev) { /* parse Stalker.parse(ev) -> [{type:'compile',start,end}] */ }, | |
| transform(iterator) { // per-instruction instrumentation | |
| let insn; while ((insn = iterator.next()) !== null) { | |
| if (insn.mnemonic === 'bl') iterator.putCallout(ctx => console.log('call', ctx.pc)); | |
| iterator.keep(); | |
| } | |
| } | |
| }); | |
| // ... run target work ... | |
| Stalker.unfollow(); Stalker.flush(); | |
| Stalker is heavy (JIT re-compile) -> scope to one module/thread and the smallest region. Excludes self; do not Stalker.follow during heavy GC. For drcov output use frida-drcov.py and load in lighthouse. | |
| verify: block count rises as new input exercises new paths (coverage feedback for fuzz-target-synth). | |
| 7 Frida Objective-C hooking [macOS/iOS] | |
| hook app logic / crypto / auth written in ObjC. | |
| 1 method pointer = ObjC.classes.Cls['- selector:'] (note the '- '/'+ ' and trailing ':'). | |
| 2 args in an ObjC method: a[0]=self, a[1]=_cmd(selector), a[2..]=real args; wrap objects with new ObjC.Object(ptr). | |
| 3 to replace: set .implementation = ObjC.implement(method, jsfn); keep the method object alive. | |
| 4 ObjC.choose scans the heap for live instances of a class -> call their methods / read ivars. | |
| if (ObjC.available) { | |
| const NSURL = ObjC.classes.NSURL; | |
| Interceptor.attach(NSURL['+ URLWithString:'].implementation, { | |
| onEnter(a){ this.s = new ObjC.Object(a[2]).toString(); console.log('URL', this.s); } | |
| }); | |
| // swizzle via setImplementation: | |
| const m = ObjC.classes.MyAuth['- verify:']; | |
| const orig = m.implementation; | |
| m.implementation = ObjC.implement(m, (self, sel, token) => { console.log('verify', new ObjC.Object(token)); return 1; }); | |
| // enumerate live instances: | |
| ObjC.choose(ObjC.classes.Session, { onMatch(o){ console.log('sess', o.token()); }, onComplete(){} }); | |
| } | |
| Swift without @objc has no ObjC method -> hook the lower mangled native symbol (T4) or inline-trampoline. Autorelease: do not over-retain returned objects. | |
| verify: the log prints decoded ObjC objects; forced verify:1 lets you in. | |
| 8 Frida Java/ART hooking [Android] | |
| hook app Java/Kotlin (crypto, root checks, cert pinning). | |
| 1 all ART work runs inside Java.perform (attaches to the VM thread). | |
| 2 Java.use('fqcn') yields a wrapper; override .method.implementation; disambiguate with .overload('argType', ...). | |
| 3 $new() constructs; static methods call directly on the wrapper. | |
| 4 arg types: '[B'=byte[], 'int', 'java.lang.String', etc. | |
| Java.perform(() => { | |
| const C = Java.use('com.x.Crypto'); | |
| C.decrypt.overload('[B').implementation = function(b) { // pick the overload by arg types | |
| const out = this.decrypt(b); // call-through | |
| console.log('pt=', Java.use('java.lang.String').$new(out)); | |
| return out; | |
| }; | |
| // defeat a root check: | |
| Java.use('com.x.Security').isRooted.implementation = () => false; | |
| // construct objects + call statics: | |
| const URL = Java.use('java.net.URL'); const u = URL.$new('https://x'); | |
| // find loaded classes: | |
| Java.enumerateLoadedClasses({ onMatch(n){ if(n.includes('Pinner')) console.log(n); }, onComplete(){} }); | |
| }); | |
| a method with overloads throws unless you pick one with .overload(...). Early classes (before load) -> hook the classloader or use Java.enumerateClassLoaders. Obfuscated names change per build -> match by structure/strings. | |
| verify: plaintext prints at the crypto boundary; the pinning/root check returns your value. | |
| 9 Frida host<->agent control (rpc + messages) [cross] | |
| drive the target from python (replay, bulk extract, fuzz loop). | |
| 1 export agent functions via rpc.exports; call from python as script.exports_sync.name(...). | |
| 2 send()/recv() for async events/backpressure; post() from host to the agent. | |
| (agent.js): | |
| rpc.exports = { | |
| read(addr, n){ return Array.from(new Uint8Array(ptr(addr).readByteArray(n))); }, | |
| call(addr, x){ return new NativeFunction(ptr(addr),'int',['int'])(x); } | |
| }; | |
| send({event:'ready'}); recv('go', m => { /* ... */ }); | |
| (host.py): | |
| import frida; s=frida.get_usb_device().attach('App'); a=s.create_script(open('agent.js').read()) | |
| a.on('message', lambda m,d: print(m)); a.load(); print(a.exports_sync.read(0x100,16)); a.post({'type':'go'}) | |
| rpc return values must be JSON-serializable (convert byte arrays to Array). Large transfers: use send(obj, data) with a binary payload. frida-trace -i "CCCrypt*" auto-generates per-fn handlers for quick coverage. | |
| verify: python receives the bytes/return; the loop drives the target. | |
| 10 Capture plaintext at a crypto/unpack boundary [cross] | |
| recover decrypted data without breaking the crypto (pairs with unpack-runtime-protected). | |
| 1 identify the decrypt fn and which arg is the output buffer + the written-length out-param (from the prototype/disasm). | |
| 2 stash out ptr + len-ptr onEnter; read the buffer onLeave after it's filled. | |
| // hook the decrypt routine; dump its OUTPUT buffer at onLeave | |
| const dec = Module.getExportByName(null,'CCCrypt'); // or the app's own fn (T4) | |
| Interceptor.attach(dec, { | |
| onEnter(a){ this.out = a[8]; this.outLen = a[9]; /* CCCrypt(..., dataOut, dataOutAvail, *dataOutMoved) */ this.moved = a[10]; }, | |
| onLeave(r){ const n = this.moved.readU32(); console.log(hexdump(this.out.readByteArray(n))); } | |
| }); | |
| some APIs write length to a pointer (deref it onLeave), others return it. For a streaming cipher, hook the final/flush call or accumulate per chunk. | |
| verify: the dumped bytes are valid plaintext (entropy drops, structure appears). | |
| 11 ELF LD_PRELOAD interpose + call-through [Linux] | |
| wrap a libc/shared-lib call process-wide without Frida. | |
| 1 define a same-signature symbol; resolve the genuine one with dlsym(RTLD_NEXT,"name") on first call (cache it). | |
| 2 build -shared -fPIC; export it ahead of libc via LD_PRELOAD. | |
| // gcc -shared -fPIC -o hook.so hook.c -ldl ; LD_PRELOAD=./hook.so ./target | |
| #define _GNU_SOURCE | |
| #include <dlfcn.h> | |
| #include <string.h> | |
| ssize_t read(int fd, void *b, size_t n){ | |
| static ssize_t (*real)(int,void*,size_t); | |
| if(!real) real = dlsym(RTLD_NEXT, "read"); | |
| ssize_t r = real(fd,b,n); | |
| /* inspect/modify b here */ | |
| return r; | |
| } | |
| statically-linked or SUID targets ignore LD_PRELOAD (SUID drops it). Infinite recursion if you call the libc name instead of the RTLD_NEXT pointer. Constructor: __attribute__((constructor)) void i(){} runs at load for setup. | |
| verify: ltrace/strace shows your wrapper; logs fire on the call. | |
| 12 ELF GOT/PLT overwrite — redirect an import [Linux] | |
| intercept one module's external call with no preload, or from inside the process. | |
| 1 find the GOT slot: parse .rela.plt (binary-format-anatomy) for the symbol's R_*_JUMP_SLOT -> r_offset = slot RVA; add the runtime base. | |
| 2 mprotect the slot's page RW (full RELRO makes .got.plt read-only -> not viable; then hook the caller or use a trampoline). | |
| 3 save the old pointer, write the detour; call *orig from the detour to chain. | |
| // after locating the .got.plt slot for "malloc" (readelf -r / from the rela.plt R_X86_64_JUMP_SLOT) | |
| #include <sys/mman.h> | |
| extern void *__start; // module base known at runtime via dladdr/link_map | |
| void hook_got(void **slot, void *detour, void **orig){ | |
| *orig = *slot; | |
| long pg = sysconf(_SC_PAGESIZE); | |
| void *base = (void*)((uintptr_t)slot & ~(pg-1)); | |
| mprotect(base, pg, PROT_READ|PROT_WRITE); | |
| *slot = detour; // next call to the import goes to detour | |
| } | |
| full RELRO -> GOT read-only (check checksec). Partial RELRO or lazy binding -> writable. The plthook library automates slot discovery across formats. | |
| verify: the import's calls land in the detour; *orig call-through returns normally. | |
| 13 Mach-O DYLD interpose + fishhook [macOS] | |
| wrap symbols in a dylib you inject (DYLD_INSERT_LIBRARIES) or from a loaded dylib. | |
| 1 interpose: a __DATA,__interpose array of {replacement,original} pairs; dyld swaps them at load (only for the inserted image's view). | |
| 2 fishhook: rebind_symbols rewrites lazy/non-lazy symbol pointers at runtime (struct rebinding {name, replacement, &orig}). | |
| (interpose): | |
| // clang -dynamiclib -o hook.dylib hook.c ; DYLD_INSERT_LIBRARIES=./hook.dylib ./target | |
| #include <stdio.h> | |
| static int my_open(const char*p,int f,...){ printf("open %s\n",p); return open(p,f); } | |
| __attribute__((used)) static struct { const void*r; const void*o; } | |
| _interpose_open __attribute__((section("__DATA,__interpose"))) = { (const void*)my_open, (const void*)open }; | |
| (fishhook, runtime rebind): | |
| #include "fishhook.h" | |
| static int (*orig_close)(int); | |
| static int my_close(int fd){ return orig_close(fd); } | |
| rebind_symbols((struct rebinding[1]){{"close", my_close, (void**)&orig_close}}, 1); | |
| hardened-runtime / library-validation blocks DYLD_INSERT unless the target lacks the flag or you co-sign. SIP protects system binaries. fishhook only touches the indirect symbol tables (imports), not direct calls. | |
| verify: the wrapped symbol logs; orig call-through works. | |
| 14 Windows inline hook — MinHook / Detours [Windows] | |
| hook an API or internal fn in a loaded module. | |
| 1 MinHook: Initialize once; CreateHook(target, detour, &trampoline); EnableHook. Call through the trampoline (Real_*). | |
| 2 Detours: wrap Attach/Detach in a transaction + UpdateThread so in-flight threads are fixed up. | |
| (MinHook): | |
| // link minhook | |
| static int (WINAPI *Real_MessageBoxA)(HWND,LPCSTR,LPCSTR,UINT) = MessageBoxA; | |
| int WINAPI My_MessageBoxA(HWND h,LPCSTR t,LPCSTR c,UINT u){ return Real_MessageBoxA(h,"hooked",c,u); } | |
| MH_Initialize(); | |
| MH_CreateHook(&MessageBoxA, &My_MessageBoxA, (LPVOID*)&Real_MessageBoxA); | |
| MH_EnableHook(MH_ALL_HOOKS); | |
| (Detours): | |
| DetourTransactionBegin(); DetourUpdateThread(GetCurrentThread()); | |
| DetourAttach(&(PVOID&)Real_MessageBoxA, My_MessageBoxA); DetourTransactionCommit(); | |
| match calling convention (WINAPI/__stdcall). CFG/CET on newer Windows can trip indirect-call guards -> prefer trampoline hooks that keep valid targets. ASLR -> resolve via GetProcAddress, never a hardcoded VA. | |
| verify: the hooked API shows your behavior; trampoline call-through returns the original result. | |
| 15 Windows IAT hook — redirect a module's import [Windows] | |
| intercept a specific module's call to an imported API (lighter than inline). | |
| 1 parse the import directory (binary-format-anatomy) to find the resolved thunk for dll!fn. | |
| 2 VirtualProtect the thunk RW; save + overwrite the pointer; restore protection. | |
| // walk DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT]; for the target dll+fn, patch FirstThunk | |
| PIMAGE_THUNK_DATA th = (PIMAGE_THUNK_DATA)(base + imp->FirstThunk); | |
| for (; th->u1.Function; th++){ | |
| if ((FARPROC)th->u1.Function == (FARPROC)target){ | |
| DWORD old; VirtualProtect(&th->u1.Function, sizeof(PVOID), PAGE_READWRITE, &old); | |
| *orig = (FARPROC)th->u1.Function; th->u1.Function = (ULONG_PTR)detour; | |
| VirtualProtect(&th->u1.Function, sizeof(PVOID), old, &old); break; | |
| } | |
| } | |
| only intercepts calls that go through that module's IAT (not GetProcAddress-resolved or inlined). Bound imports / delay-load need the matching table. | |
| verify: the module's calls to the import hit the detour. | |
| 16 Inline trampoline mechanics (any native) [cross] | |
| no framework; overwrite a prologue and keep the original callable. | |
| 1 disassemble the prologue; copy enough whole instructions (>= jump size: 5 bytes x86 rel32, or 16 bytes arm64 for a far branch) into a gateway buffer (RWX). | |
| 2 append a jump from the gateway back to target+stolen_len. | |
| 3 overwrite target's prologue with a jump to your detour (x86: E9 rel32; arm64: a far branch via ADRP+BR xN or LDR xN,#8;BR xN;.quad dest). | |
| 4 relocate PC-relative operands in the stolen bytes (RIP-relative on x86; ADR/ADRP/B on arm64). | |
| 5 the detour calls the gateway to run the original. | |
| arm64 B reaches +-128MB only -> use the register far-branch for distant detours. Patch under mprotect/VirtualProtect rwx + i-cache flush (__builtin___clear_cache / FlushInstructionCache). A self-checksum guard detects the prologue change -> use a hardware breakpoint instead (dynamic-memory-access). | |
| verify: the detour runs; the gateway call-through executes the original behavior. | |
| 17 Code injection (load your module into a target) [cross] | |
| get your agent/dylib/.so running inside another process. | |
| 1 write the module path into the target's memory. | |
| 2 make a target thread call the loader (LoadLibraryA / dlopen) with that path. | |
| 3 the module's constructor/DllMain runs your setup inside the target. | |
| Linux (ptrace): attach; set regs to call __libc_dlopen_mode(remote_path, RTLD_NOW); restore + detach. | |
| (or: gdb -p PID ; call (void*)dlopen("/tmp/e.so",2)) | |
| macOS: task_for_pid + thread_create_running a bootstrap that calls dlopen (or use Frida). | |
| Windows: p=VirtualAllocEx(h,0,len,MEM_COMMIT,PAGE_READWRITE); WriteProcessMemory(h,p,path,len,0); | |
| CreateRemoteThread(h,0,0,(LPTHREAD_START_ROUTINE)LoadLibraryA,p,0,0); | |
| // stealth: QueueUserAPC((PAPCFUNC)LoadLibraryA,hAlertableThread,p) | |
| cross-process needs elevation (root / SeDebugPrivilege / an entitlement) — when a privileged op is blocked, wait for re-authorization, no blind retries. Hardened-runtime/WDAC can block unsigned modules -> co-sign or use a tracer. | |
| verify: the module appears in the target's module list; its constructor log fires. | |
| 18 System tracing (no target rebuild, read-only fast paths) | |
| uses: trace an interface's args ; force a check by rewriting the return (1/2) ; capture plaintext at a decrypt boundary (10) ; coverage for a fuzzer (6) ; stub a syscall to feed a corpus. | |
| bpftrace -e 'uprobe:/bin/t:parse { printf("%s\n", str(arg0)) }' | |
| perf probe -x /lib/libc.so.6 'malloc size=%di' ; perf record -e probe_libc:malloc -p PID | |
| echo 'p:myp vfs_read' > /sys/kernel/debug/tracing/kprobe_events ; echo 1 > events/kprobes/myp/enable ; cat trace_pipe | |
| dtrace -n 'pid$target::CCCrypt:entry{ ustack(); }' -p PID (macOS) | |
| strace -f -e trace=network -p PID ; ltrace -e 'malloc+free' ./t | |
| 19 Attach fast: | |
| frida -n proc -l h.js; spawn: frida -f ./x -l h.js --no-pause; trace: frida-trace -i "recv*" -i "strcmp" -n proc → editable JS stubs per matched function. | |
| 20 One-liner read-arg hook: | |
| Interceptor.attach(Module.getExportByName(null,'strcmp'),{onEnter(a){console.log(a[0].readUtf8String(),a[1].readUtf8String())}}). | |
| 21 Force a return: | |
| Interceptor.replace(p,new NativeCallback(()=>1,'int',['pointer','pointer'])); or inside a hook: onLeave(r){r.replace(ptr(0))}. | |
| 22 Scan + patch bytes: | |
| Memory.scan(base,sz,'74 ?? 90',{onMatch(a){Memory.patchCode(a,1,c=>c.writeU8(0xeb))}}) (je→jmp). | |
| 23 Coverage via Stalker: | |
| Stalker.follow(tid,{events:{compile:true}}); or: frida-drcov → open the .drcov in Lighthouse (IDA/Ghidra/Binja). | |
| 24 ELF preload wrap: | |
| LD_PRELOAD=./h.so ./x with a same-signature symbol + dlsym(RTLD_NEXT,...); trace binds with: LD_AUDIT=./audit.so ./x (la_symbind hooks every PLT bind, no target patch). | |
| 25 Mac interpose: | |
| DYLD_INSERT_LIBRARIES=./h.dylib ./x (needs a non-hardened target); check with: otool -l h.dylib | grep interpose. | |
| 26 GOT flip in gdb (no source): | |
| set {long}&'x'::got_fn = detour_addr; or: plthook_replace lib; verify with pwndbg got. | |
| 27 Windows quick: | |
| Detours withdll.exe /d:h.dll x.exe; MinHook for inline; API Monitor / Frida for tracing; Detours syelog for call logs. | |
| 28 ObjC one-liner: | |
| ObjC.classes.NSURLSession['- dataTaskWithRequest:completionHandler:'].implementation = ...; frida-trace -m '-[NSString stringWithUTF8String:]'. | |
| 29 Android/Java: | |
| Java.perform(()=>{Java.use('com.x.Y').chk.implementation=function(){return true}}); objection -g pkg explore for speed; frida-trace -j 'com.x.*!*'. | |
| 30 Syscall-level trace (no libc / packer-agnostic): | |
| strace -f -e trace=network ./x; ltrace -S ./x; macOS: dtruss -f ./x. | |
| 31 eBPF uprobe any userland function, no patching: | |
| sudo bpftrace -e 'uprobe:/lib/x86_64-linux-gnu/libc.so.6:getenv{printf("%s\n",str(arg0))}'. | |
| 32 eBPF kernel tracepoint: | |
| sudo bpftrace -e 'tracepoint:syscalls:sys_enter_openat{printf("%s %s\n",comm,str(args->filename))}'. | |
| 33 Capture a decrypt output: | |
| hook the crypto/unpack routine, onLeave(){ console.log(hexdump(outPtr.readByteArray(len))) } → plaintext at the boundary. | |
| 34 Frida RPC to drive from python: | |
| agent rpc.exports={rd:(a,n)=>Array.from(new Uint8Array(ptr(a).readByteArray(n)))}; host: script.exports_sync.rd(addr,64). | |
| 35 Patch a function to no-op: | |
| overwrite its prologue with ret — gdb set {char}fn=0xc3 (x86) / frida: Memory.patchCode(fn,4,c=>c.writeU32(0xd65f03c0)) (arm64 ret). | |
| 36 Trace malloc/free sizes live: | |
| bpftrace -e 'uprobe:libc:malloc{printf("%d\n",arg0)}'; or frida: Interceptor.attach(Module.getExportByName(null,'malloc'),{onEnter(a){this.n=a[0].toInt32()},onLeave(r){console.log(this.n,r)}}). | |
| Sources: frida.re docs (Interceptor / Stalker / Memory / gum / frida-trace); facebook/fishhook fishhook.h (BSD); microsoft/Detours samples (MIT); TsudaKageyu/minhook (BSD-2 / MIT); Apple objc runtime (APSL); bpftrace reference + Linux uprobe docs; glibc rtld-audit (LD_AUDIT, la_symbind). API names + invocations verified against these. |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment