L’interface est localisée ; les présentations et documents des Skills sont traduits automatiquement par IA dans votre langue. Le code, les commandes, chemins et termes techniques restent en version originale, et le document de l’auteur fait foi.
Explorer les Skills
Données et recherche · SKILL.md

frida

frida

Use this skill whenever writing, debugging, or reviewing Frida instrumentation scripts targeting iOS ARM64 or Android. Triggers include: any mention of 'Frida', 'dynamic instrumentation', 'hooking', 'Interceptor', 'Stalker', 'ObjC bridge', 'Java bridge', 'frida-server', 'frida-gadget', 'NativeFunction', or requests to hook/trace/patch native or managed func…

Independent GitHub snapshot · Automated classification · Original documentation governs

Aas0ler / skillsApache-2.0

Documentation de la Skill

GitHub

name: frida description: "Use this skill whenever writing, debugging, or reviewing Frida instrumentation scripts targeting iOS ARM64 or Android. Triggers include: any mention of 'Frida', 'dynamic instrumentation', 'hooking', 'Interceptor', 'Stalker', 'ObjC bridge', 'Java bridge', 'frida-server', 'frida-gadget', 'NativeFunction', or requests to hook/trace/patch native or managed functions at runtime. Also use when bypassing SSL pinning, root detection, anti-cheat systems, or Frida self-detection. Apply for both JavaScript and TypeScript agents targeting frida-tools 14+ and Frida 17.x. Do NOT use for static binary analysis (use radare2/Ghidra skills instead), build-time patching, or non-Frida instrumentation frameworks."

Frida 17 JavaScript API Reference

Version: Frida 17.x (latest: 17.6.2, Feb 2026) · frida-tools 14+ Runtimes: QuickJS (default), V8 (opt-in) · TypeScript supported via frida-compile Script templates: See EXAMPLES.md for ready-to-use iOS ARM64 & Android scripts.


Table of Contents

  1. Setup & Tooling
  2. Migration Notes from Frida 16.x
  3. Global / Script Scope
  4. Process
  5. Module
  6. Memory
  7. NativePointer
  8. NativeFunction & NativeCallback
  9. Interceptor
  10. Stalker
  11. Thread
  12. DebugSymbol
  13. CModule
  14. ObjC Bridge (iOS/macOS)
  15. Java Bridge (Android)
  16. Socket
  17. File & I/O
  18. Hexdump & Utilities
  19. Script IPC (send / recv)
  20. Frida.Compiler
  21. Arm64Writer / X86Writer / ThumbWriter
  22. Platform Tips: iOS ARM64
  23. Platform Tips: Android
  24. Quick Reference Table

1. Setup & Tooling

Install Frida and launch an agent against a target process over USB, TCP, or locally.

# Minimal working setup
pip install frida frida-tools
frida -U -f com.example.App -l script.js   # spawn via USB
frida -U -F -l script.js                   # attach to frontmost iOS app
# Full tooling reference
pip install frida frida-tools

# Spawn and inject
frida -U -f com.example.App -l script.js          # USB, spawn
frida -U -F -l script.js                           # USB, frontmost app (iOS)
frida -H 192.168.1.10:27042 -f com.example.App -l script.js  # remote TCP

# Interactive REPL (ObjC/Java bridges auto-loaded in frida-tools 14+)
frida -U -F

# Compile TypeScript agent
frida-compile agent.ts -o agent.js
frida-compile agent.ts -o agent.js -w   # watch mode

# Auto-trace functions by name or glob pattern
frida-trace -U -F -i "open" -I "NSURLSession*"

# Push frida-server to Android device
adb push frida-server /data/local/tmp/
adb shell "chmod +x /data/local/tmp/frida-server && /data/local/tmp/frida-server &"

# iOS (jailbroken): install via Cydia/Sileo — package: re.frida.server

2. Migration Notes from Frida 16.x

Frida 17 removed three categories of long-deprecated APIs — update existing scripts before running them against a 17.x agent.

// Quick migration checklist — if your script uses any of these patterns, update it:
// ❌ enumerateModulesSync() or callback-style enumerate  → ✅ returns array directly
// ❌ Memory.readU32(ptr)                                 → ✅ ptr.readU32()
// ❌ Module.getExportByName('lib', 'fn')                 → ✅ Process.getModuleByName('lib').getExportByName('fn')
// ❌ ObjC/Java globals without import in TS agents       → ✅ import "frida-objc-bridge"
// ── Enumeration APIs ──────────────────────────────────────────────────────
// ❌ OLD
Process.enumerateModules({ onMatch(m) { ... }, onComplete() { } });
Process.enumerateModulesSync();
// ✅ NEW — returns array directly
for (const mod of Process.enumerateModules()) { console.log(mod.name); }

// ── Memory read/write ─────────────────────────────────────────────────────
// ❌ OLD
Memory.readU32(ptr('0x1234'));
Memory.writeU32(ptr('0x1234'), 100);
// ✅ NEW — methods on NativePointer, chainable
ptr('0x1234').readU32();
ptr('0x1234').writeU32(100);
ptr('0x1234').add(4).writeU32(10).add(4).writeU16(20);

// ── Static Module helpers ─────────────────────────────────────────────────
// ❌ OLD
Module.getBaseAddress('libc.so');
Module.getExportByName('libc.so', 'open');
Module.findExportByName('libc.so', 'open');
Module.ensureInitialized('libc.so');
// ✅ NEW
Process.getModuleByName('libc.so').base;
Process.getModuleByName('libc.so').getExportByName('open');
Process.getModuleByName('libc.so').findExportByName('open');
Module.getGlobalExportByName('open');   // replaces getExportByName(null, 'open')

// ── Runtime bridges ───────────────────────────────────────────────────────
// ObjC / Java / Swift no longer bundled in Frida core.
// frida-tools 14+ REPL auto-loads them. TypeScript agents must import explicitly:
import "frida-objc-bridge";   // provides ObjC global
import "frida-java-bridge";   // provides Java global

3. Global / Script Scope

Top-level globals and built-ins available in every Frida agent without any import.

// Typical agent preamble
const base     = Process.mainModule.base;
const libc     = Process.getModuleByName('libc.so');
const openAddr = libc.getExportByName('open');
send({ type: 'ready', base: base.toString() });
// Full global reference
ptr(val)           // Create NativePointer from hex string or number
NULL               // NativePointer(0x0)
Process            // Current process introspection
Memory             // Memory utilities (alloc, scan, patch, protect)
Interceptor        // Function hook engine
Stalker            // Code tracing engine
Thread             // Thread enumeration + backtrace
DebugSymbol        // Address ↔ symbol resolution
CModule            // Inline C compilation
Module             // Static module helpers (getGlobalExportByName, load)
ObjC               // Objective-C runtime bridge (iOS/macOS)
Java               // Android ART/JVM bridge
console            // console.log / console.warn / console.error
hexdump(buf)       // Pretty hex dump to string
send(msg, data?)   // Send message + optional binary to Python host
recv(type, cb)     // Register handler for message from Python host
rpc.exports        // Expose JS functions as RPC callable from host

// Script lifecycle
Script.pin();      // Prevent GC of this script object
Script.unpin();

// Top-level await is supported
const result = await SomeAsyncOperation();

4. Process

Provides identity, module lookup, memory range enumeration, thread listing, and exception handling for the instrumented process.

// Log process info and look up a module
console.log(Process.id, Process.arch, Process.platform);
const libc = Process.getModuleByName('libc.so');
console.log('libc base:', libc.base, 'size:', libc.size);
// ── Identity ──────────────────────────────────────────────────────────────
Process.id                  // PID (number)
Process.arch                // 'arm64' | 'x64' | 'arm' | 'ia32'
Process.platform            // 'darwin' | 'linux' | 'windows' | 'freebsd'
Process.pageSize            // page size in bytes
Process.pointerSize         // 4 or 8
Process.codeSigningPolicy   // 'optional' | 'required'
Process.mainModule          // Module object for the main executable
Process.currentDir          // working directory string

// ── Module lookup ─────────────────────────────────────────────────────────
Process.enumerateModules()                   // → Module[]
Process.getModuleByName('libc.so')           // throws if not found
Process.findModuleByName('libc.so')          // → Module | null
Process.getModuleByAddress(ptr('0x1234'))
Process.findModuleByAddress(ptr('0x1234'))

// ── Memory ranges ─────────────────────────────────────────────────────────
Process.enumerateRanges('r-x')                             // → RangeDetails[]
Process.enumerateRanges({ protection: 'r-x', coalesce: true })
Process.enumerateMallocRanges()                            // heap chunks

// RangeDetails: { base: NativePointer, size: number, protection: string,
//                 file?: { path, offset, size } }

// ── Threads ───────────────────────────────────────────────────────────────
Process.enumerateThreads()          // → ThreadDetails[] (Frida threads hidden)
Process.getCurrentThreadId()        // → number

// ── Exception handler ─────────────────────────────────────────────────────
Process.setExceptionHandler((details) => {
  console.log('Exception:', details.type, details.address);
  return false; // false = propagate to OS
});

5. Module

Represents a loaded binary (.so, .dylib, or executable) and exposes its exports, symbols, imports, and sections.

// Look up a module and find two exports without redundant lookups
const libc   = Process.getModuleByName('libc.so');
const openFn = libc.getExportByName('open');
const readFn = libc.getExportByName('read');
console.log('open @', openFn, 'read @', readFn);
// ── Module object properties ──────────────────────────────────────────────
mod.name    // 'libgame.so'
mod.path    // full filesystem path
mod.base    // NativePointer — load address
mod.size    // size in bytes

// ── Instance methods (Frida 17+) ──────────────────────────────────────────
mod.enumerateImports()        // → ImportDetails[]
mod.enumerateExports()        // → ExportDetails[]
mod.enumerateSymbols()        // → SymbolDetails[]
mod.enumerateSections()       // → SectionDetails[]
mod.enumerateDependencies()   // → string[]

mod.findExportByName('fn')    // → NativePointer | null
mod.getExportByName('fn')     // → NativePointer (throws if missing)
mod.findSymbolByName('_ZN…')  // → NativePointer | null
mod.getSymbolByName('_ZN…')   // → NativePointer

// ExportDetails: { type: 'function'|'variable', name: string, address: NativePointer }

// ── Static global helpers ─────────────────────────────────────────────────
Module.getGlobalExportByName('open')    // search all loaded modules; throws if missing
Module.findGlobalExportByName('open')   // → NativePointer | null
Module.load('/path/to/lib.so')          // dlopen and return Module

6. Memory

Allocates, copies, protects, patches, and scans process memory.

// Allocate a C string and scan for its bytes
const buf = Memory.allocUtf8String('FIND_ME');
Memory.scan(buf, 8, '46 49 4e 44', {
  onMatch(addr) { console.log('found at', addr); },
  onError() {},
  onComplete() {}
});
// ── Allocation ────────────────────────────────────────────────────────────
Memory.alloc(256)                            // zeroed buffer → NativePointer
Memory.allocUtf8String('hello')              // null-terminated UTF-8
Memory.allocUtf16String('hello')             // null-terminated UTF-16
Memory.allocByteArray(new Uint8Array([0x90, 0x90]))

// ── Copy / duplicate ──────────────────────────────────────────────────────
Memory.copy(dst, src, n)       // copy n bytes src → dst
Memory.dup(src, n)             // allocate + copy → new NativePointer

// ── Protection ────────────────────────────────────────────────────────────
Memory.protect(addr, size, 'rwx')          // change page protection
Memory.queryProtection(addr)               // → 'r-x' | 'rwx' | etc.

// ── Code patching (W^X safe) ──────────────────────────────────────────────
Memory.patchCode(addr, size, (code) => {
  const w = new Arm64Writer(code, { pc: addr });
  w.putNop();
  w.flush();
});

// ── Scanning (async, callback-based — remains async in Frida 17) ──────────
// Pattern format: 'AA BB ?? DD'  — ?? is a wildcard byte
Memory.scan(base, size, 'DE AD BE EF', {
  onMatch(address, size)  { console.log('found @', address); },
  onError(reason)         { /* skip unreadable pages */ },
  onComplete()            { }
});

// NOTE: Memory.readXxx() / Memory.writeXxx() static methods REMOVED in 17.
// Use NativePointer instance methods instead — see §7.

7. NativePointer

The universal address type in Frida — every memory address is a NativePointer with built-in arithmetic, read, and write methods.

// Read a simple struct: vtable pointer + two 32-bit fields
const obj  = ptr('0x200000');
const vtbl = obj.readPointer();
const hp   = obj.add(8).readU32();
const mp   = obj.add(12).readU32();
console.log('vtable:', vtbl, 'hp:', hp, 'mp:', mp);
const p = ptr('0x100400000');

// ── Arithmetic (returns new NativePointer) ────────────────────────────────
p.add(0x10)   p.sub(4)
p.and(0xFFF)  p.or(1)   p.xor(0xFF)
p.shl(2)      p.shr(1)  p.not()

// ── Comparison ────────────────────────────────────────────────────────────
p.equals(q)          // boolean
p.compare(q)         // -1 | 0 | 1
p.isNull()

// ── Conversion ────────────────────────────────────────────────────────────
p.toInt32()   p.toUInt32()
p.toString()  p.toString(16)
p.toMatchPattern()   // → 'AA BB CC DD' byte string for use in Memory.scan

// ── Read ──────────────────────────────────────────────────────────────────
p.readPointer()      // → NativePointer
p.readS8()   p.readU8()
p.readS16()  p.readU16()
p.readS32()  p.readU32()
p.readS64()  p.readU64()
p.readFloat()   p.readDouble()
p.readByteArray(n)    // → ArrayBuffer
p.readCString()       // null-terminated ASCII/UTF-8
p.readUtf8String(len?)
p.readUtf16String(len?)
p.readAnsiString(len?)  // Windows only

// ── Write (returns same NativePointer for chaining) ───────────────────────
p.writePointer(q)
p.writeS8(v)   p.writeU8(v)
p.writeS16(v)  p.writeU16(v)
p.writeS32(v)  p.writeU32(v)
p.writeS64(v)  p.writeU64(v)
p.writeFloat(v)   p.writeDouble(v)
p.writeByteArray(arrayBuffer)
p.writeUtf8String('hello')
p.writeUtf16String('hello')

// ── Chaining ──────────────────────────────────────────────────────────────
ptr('0x5000')
  .add(8).writeU32(100)
  .add(4).writeU16(42)
  .add(2).writeU8(1);

8. NativeFunction & NativeCallback

Wraps a native C function so it can be called directly from JavaScript, and creates a native-callable stub backed by a JavaScript function.

// Call strlen directly from JS
const strlen = new NativeFunction(
  Module.getGlobalExportByName('strlen'), 'int', ['pointer']
);
console.log('length:', strlen(Memory.allocUtf8String('hello'))); // → 5
// ── NativeFunction: call any native function from JS ─────────────────────
const open = new NativeFunction(
  Process.getModuleByName('libc.so').getExportByName('open'),
  'int',               // return type
  ['pointer', 'int'],  // argument types
  { abi: 'default' }   // optional: 'win64' | 'sysv' | 'stdcall' | 'fastcall'
);
const fd = open(Memory.allocUtf8String('/etc/hosts'), 0 /* O_RDONLY */);

// Supported type strings:
// 'void' 'bool' 'char' 'uchar' 'short' 'ushort' 'int' 'uint'
// 'long' 'ulong' 'longlong' 'ulonglong' 'float' 'double'
// 'pointer' 'size_t' 'ssize_t' 'int8_t' … 'uint64_t'

// ── NativeCallback: expose a JS function as a native C function ───────────
const myHook = new NativeCallback(
  (a, b) => {
    console.log('called with:', a, b);
    return a + b;
  },
  'int',          // return type
  ['int', 'int']  // argument types
);
// myHook is a NativePointer — pass it to Interceptor.replace() or any C API

9. Interceptor

The primary hooking engine — attaches onEnter/onLeave callbacks to observe any native function, or replaces it entirely with a new implementation.

// Log every call to open() and override the return value
Interceptor.attach(Module.getGlobalExportByName('open'), {
  onEnter(args) {
    console.log('[open]', args[0].readCString(), 'flags:', args[1].toInt32());
  },
  onLeave(retval) {
    console.log('[open] fd =', retval.toInt32());
  }
});
// ── attach: observe without replacing ─────────────────────────────────────
const hook = Interceptor.attach(targetPtr, {
  onEnter(args) {
    // args[n]       → NativePointer (n-th argument)
    // this.context  → CpuContext: pc, sp, fp, x0–x28 (ARM64) / rax… (x64)
    // this.threadId, this.returnAddress, this.depth, this.errno
    this.savedArg = args[0].readUtf8String();  // stash state for onLeave
  },
  onLeave(retval) {
    retval.replace(ptr(1));   // override return value
  }
});
hook.detach();   // remove this specific hook

// ── replace: swap implementation entirely ────────────────────────────────
Interceptor.replace(targetPtr,
  new NativeCallback((arg0) => {
    console.log('replaced, arg0 =', arg0);
    // To call original: new NativeFunction(targetPtr, 'void', ['pointer'])(arg0)
  }, 'void', ['pointer'])
);

// ── replaceFast: lower overhead, no InvocationContext, returns trampoline ─
const origPtr = Interceptor.replaceFast(targetPtr,
  new NativeCallback((arg0) => {
    return new NativeFunction(origPtr, 'int', ['pointer'])(arg0);
  }, 'int', ['pointer'])
);

// ── Housekeeping ──────────────────────────────────────────────────────────
Interceptor.revert(targetPtr);   // undo a replace / replaceFast
Interceptor.flush();              // apply all pending patches immediately

10. Stalker

A dynamic code tracing engine that follows thread execution at the basic-block or instruction level, enabling coverage collection, call tracing, and inline code transformation.

// Collect basic-block addresses while a function executes
Interceptor.attach(targetFn, {
  onEnter() {
    Stalker.follow(this.threadId, {
      events: { block: true },
      onReceive(events) {
        for (const [, start] of Stalker.parse(events))
          console.log('block @', start);
      }
    });
  },
  onLeave() {
    Stalker.unfollow(this.threadId);
    Stalker.flush();
    Stalker.garbageCollect();
  }
});
// ── Follow a thread ───────────────────────────────────────────────────────
Stalker.follow(threadId, {
  events: {
    call:    true,   // CALL instructions
    ret:     false,  // RET instructions
    exec:    false,  // every instruction (very high overhead)
    block:   true,   // basic block start addresses
    compile: false   // each time a block is JIT-compiled
  },
  // transform: inline instrumentation mode (alternative to onReceive)
  transform(iterator) {
    let instr = iterator.next();
    while (instr !== null) {
      iterator.putCallout((ctx) => { /* called at this instruction */ });
      iterator.keep();
      instr = iterator.next();
    }
  },
  onReceive(events) {
    // events: raw binary buffer
    const parsed = Stalker.parse(events, { annotate: true, stringify: false });
    // → [[type, from, to], …] or [[type, start, end], …] for blocks
  },
  onCallSummary(summary) {
    // summary: { [addressHex]: hitCount }
  }
});

Stalker.unfollow(threadId)
Stalker.flush()                               // drain buffered events
Stalker.garbageCollect()                      // free compiled code for unfollowed threads
Stalker.isFollowing(threadId)                 // → boolean
Stalker.exclude({ base: mod.base, size: mod.size })  // skip a memory range

Stalker.queueCapacity = 16384                 // event ring buffer size (entries)
Stalker.queueDrainInterval = 250              // drain interval in ms

11. Thread

Enumerates process threads, generates symbolicated backtraces, and provides sleep control for the current thread.

// Print a symbolicated backtrace from inside a hook
Interceptor.attach(targetFn, {
  onEnter(args) {
    Thread.backtrace(this.context, Backtracer.ACCURATE)
      .map(DebugSymbol.fromAddress)
      .forEach(s => console.log(' ', s.toString()));
  }
});
// ── Enumeration ───────────────────────────────────────────────────────────
// Frida's own threads are automatically hidden from results
Process.enumerateThreads()
// → [{ id: number, name?: string, state: string, context: CpuContext }, …]
// state: 'running' | 'stopped' | 'waiting' | 'uninterruptible' | 'halted'

Process.getCurrentThreadId()   // → number

// ── Timing ────────────────────────────────────────────────────────────────
Thread.sleep(0.5)   // sleep current thread for 0.5 seconds (float)

// ── Backtrace ─────────────────────────────────────────────────────────────
Thread.backtrace(context, Backtracer.ACCURATE)  // → NativePointer[]
Thread.backtrace(context, Backtracer.FUZZY)     // faster but less precise
// ACCURATE uses libunwind; FUZZY uses a heuristic frame scan

12. DebugSymbol

Resolves memory addresses to human-readable symbol names and source locations, and looks up addresses by symbol name.

// Annotate a backtrace with symbol names
Thread.backtrace(this.context, Backtracer.FUZZY)
  .forEach(addr => console.log(DebugSymbol.fromAddress(addr).toString()));
// Example output: "0x100403f10 libgame!GameManager::isCheatEnabled()"
// ── Address → symbol ──────────────────────────────────────────────────────
const sym = DebugSymbol.fromAddress(ptr('0x100403f10'));
sym.address      // NativePointer
sym.name         // mangled name string, or null if stripped
sym.moduleName   // 'libgame.so'
sym.fileName     // source file path (only if DWARF info is present)
sym.lineNumber   // number
sym.toString()   // one-line human-readable string

// ── Name → address ────────────────────────────────────────────────────────
const sym2 = DebugSymbol.fromName('_ZN7UObjectL13ProcessEventEPS_P9UFunctionPv');
// Use sym2.address to get the NativePointer

// ── Load external debug info ──────────────────────────────────────────────
DebugSymbol.load(Process.getModuleByName('libgame.so').path);
// Forces DWARF parsing for modules not yet fully analysed

13. CModule

Compiles and links a snippet of C source code into the process at runtime, producing native callbacks with zero JavaScript overhead — ideal for hot hooks and performance-critical instrumentation.

// Count hook hits with no JS round-trip overhead
const cm = new CModule(`
  #include <stdint.h>
  static uint32_t hits = 0;
  void on_enter(void) { hits++; }
  uint32_t get_hits(void) { return hits; }
`);
Interceptor.attach(targetFn, { onEnter: cm.on_enter });
// Read the counter from JS:
const getHits = new NativeFunction(cm.get_hits, 'uint32', []);
console.log('hits:', getHits());
// ── Construction ──────────────────────────────────────────────────────────
const cm = new CModule(`
  #include <gum/guminterceptor.h>   // GumInvocationContext etc.
  #include <stdint.h>
  // Available: GumStalker, GumInterceptor, gum_* APIs, standard C headers

  extern void imported_fn(void);    // symbol imported from JS

  static uint32_t call_count = 0;
  void on_enter(GumInvocationContext * ic) { call_count++; }
  uint32_t get_count(void) { return call_count; }
`,
  // Map C extern names to JS NativePointers
  { imported_fn: someNativeFuncPtr }
);

// ── Access exported C symbols (each is a NativePointer) ──────────────────
cm.on_enter    // → NativePointer
cm.get_count   // → NativePointer

// Use as a native Interceptor callback (no JS round-trip)
Interceptor.attach(target, { onEnter: cm.on_enter });

// Call the C function from JS
const getCount = new NativeFunction(cm.get_count, 'uint32', []);
console.log('calls:', getCount());

cm.dispose();  // unmap the module when no longer needed

// NOTE: CModule is unavailable on unsupported architectures — wrap in try/catch

14. ObjC Bridge (iOS/macOS)

Exposes the Objective-C runtime, letting you look up classes and protocols, hook instance and class methods, intercept and replace blocks, and schedule work on the main queue.

// Hook NSURLSession and log every request URL
Interceptor.attach(
  ObjC.classes.NSURLSession['- dataTaskWithRequest:completionHandler:'].implementation,
  {
    onEnter(args) {
      const req = new ObjC.Object(args[2]);
      console.log('[NSURLSession] URL:', req.URL().absoluteString().toString());
    }
  }
);
// ── Availability ──────────────────────────────────────────────────────────
ObjC.available   // boolean — false on Android / non-ObjC processes

// frida-tools 14+ REPL auto-loads ObjC.
// TypeScript agent: import "frida-objc-bridge";

// ── Class access ──────────────────────────────────────────────────────────
const NSString = ObjC.classes.NSString;
NSString['+ stringWithUTF8String:']    // class method wrapper
NSString['- length']                   // instance method wrapper

// ── ObjC.Object ───────────────────────────────────────────────────────────
const obj = new ObjC.Object(ptr);
obj.$className      // class name string
obj.$methods        // all methods including inherited
obj.$ownMethods     // own methods only
obj.description()   // call -description → ObjC.Object
obj.retain()  obj.release()  obj.autorelease()

// ── Enumerate classes / protocols ─────────────────────────────────────────
ObjC.enumerateLoadedClasses()   // → string[]
const proto = ObjC.protocols.NSURLSessionDelegate;
proto.methods   // → { methodName: { types, read, write } }

// ── Blocks ────────────────────────────────────────────────────────────────
// Create a new ObjC block from JS
const block = new ObjC.Block({
  retType: 'void',
  argTypes: ['pointer'],
  implementation(arg) { console.log('block called', arg); }
});
// Intercept an existing block passed as a function argument
const existing = new ObjC.Block(args[3]);
existing.implementation = (arg) => { /* replace body */ };

// ── Selectors ─────────────────────────────────────────────────────────────
ObjC.selector('viewDidLoad')         // JS string → SEL (NativePointer)
ObjC.selectorAsString(selPtr)        // SEL → JS string

// ── Main queue scheduling ─────────────────────────────────────────────────
ObjC.schedule(ObjC.mainQueue, () => {
  const pool = ObjC.classes.NSAutoreleasePool.alloc().init();
  // ObjC code here runs on the main thread
  pool.release();
});

15. Java Bridge (Android)

Exposes the Android ART/JVM runtime, enabling you to hook Java methods, access static fields, instantiate classes, enumerate live objects on the heap, and work with custom class loaders.

// Force isUserPremium() to always return true
Java.perform(() => {
  const GameManager = Java.use('com.example.game.GameManager');
  GameManager.isUserPremium.implementation = function() {
    console.log('[+] isUserPremium hooked → true');
    return true;
  };
});
// ── Availability ──────────────────────────────────────────────────────────
Java.available   // boolean

// frida-tools 14+ REPL auto-loads Java.
// TypeScript agent: import "frida-java-bridge";

// ── All Java API work must be wrapped in Java.perform() ───────────────────
Java.perform(() => {

  // Get a class wrapper
  const Activity = Java.use('android.app.Activity');

  // Hook instance method (simple overwrite)
  Activity.onResume.implementation = function() {
    console.log('[+] onResume:', this.getClass().getName());
    this.onResume();   // call original
  };

  // Hook overloaded method (specify full signature)
  Activity.startActivity
    .overload('android.content.Intent')
    .implementation = function(intent) {
      console.log('[+] intent:', intent.toString());
      this.startActivity(intent);
    };

  // Read / write a static field
  const Build = Java.use('android.os.Build');
  console.log('Model:', Build.MODEL.value);
  Build.MODEL.value = 'Pixel 9 Pro';

  // Instantiate a class
  const Intent = Java.use('android.content.Intent');
  const i = Intent.$new('android.intent.action.VIEW');

  // Cast a raw pointer to a Java type
  const Runnable = Java.use('java.lang.Runnable');
  const r = Java.cast(somePtr, Runnable);

  // Find all live instances of a class on the heap
  Java.choose('com.example.game.GameManager', {
    onMatch(instance) { console.log('[+] found:', instance); },
    onComplete() {}
  });

  // Prevent GC of a Java object across callbacks
  const retained = Java.retain(instance);
  // Java.release(retained) when done

  // Run code on the ART main thread
  Java.scheduleOnMainThread(() => { /* UI-safe code here */ });

  // Iterate all class loaders (useful for obfuscated / split APKs)
  Java.enumerateClassLoaders({
    onMatch(loader) {
      try { console.log(loader.loadClass('com.hidden.X')); } catch(e) {}
    },
    onComplete() {}
  });

  // List all currently loaded class names
  Java.enumerateLoadedClasses({
    onMatch(name) { if (name.includes('cheat')) console.log(name); },
    onComplete() {}
  });
});

// Access the raw JNI environment (outside Java.perform)
const env     = Java.vm.getEnv();
const jstring = env.newStringUtf('hello from JNI');

16. Socket

Creates outbound TCP/UDP connections or inbound listeners directly from within the agent, useful for building a side-channel without going through send()/recv().

// Connect to localhost and write a line
const conn = await Socket.connect({ family: 'ipv4', host: '127.0.0.1', port: 9999 });
await conn.output.writeAll(new TextEncoder().encode('hello\n'));
await conn.close();
// ── Outbound connection ───────────────────────────────────────────────────
const conn = await Socket.connect({
  family: 'ipv4',     // 'ipv4' | 'ipv6' | 'unix'
  host:   '127.0.0.1',
  port:   1337
});
// conn: IOStream { input: InputStream, output: OutputStream }
await conn.output.writeAll(new Uint8Array([0x01, 0x02, 0x03]));
const data = await conn.input.read(256);   // → ArrayBuffer
await conn.close();

// ── Inbound listener ──────────────────────────────────────────────────────
const listener = await Socket.listen({ family: 'ipv4', host: '0.0.0.0', port: 9999 });
const accepted  = await listener.accept();
// accepted has the same IOStream shape as conn

17. File & I/O

Reads and writes files on the device filesystem directly from the agent — useful for dumping memory regions, reading /proc entries, or writing logs without going through the host.

// Dump /proc/self/maps to the console
console.log(new File('/proc/self/maps', 'r').readText());
// ── Open ──────────────────────────────────────────────────────────────────
const f = new File('/path/to/file', 'r');  // mode: 'r' 'w' 'rb' 'wb' 'ab' etc.

// ── Read ──────────────────────────────────────────────────────────────────
f.read(n)        // → ArrayBuffer (up to n bytes)
f.readText()     // → string (entire file)
f.readLine()     // → string (one line, including \n)

// ── Write ─────────────────────────────────────────────────────────────────
f.write(data)    // data: string | ArrayBuffer
f.flush()        // flush OS write buffer

// ── Seek / tell ───────────────────────────────────────────────────────────
f.seek(offset, whence)   // whence: 0=SEEK_SET, 1=SEEK_CUR, 2=SEEK_END
f.tell()                 // → current offset (number)

f.close()

// ── Common pattern: dump a memory region to disk ──────────────────────────
const out = new File('/tmp/memdump.bin', 'wb');
out.write(ptr('0x100000000').readByteArray(0x1000));
out.flush();
out.close();

18. Hexdump & Utilities

Pretty-prints memory as a hex + ASCII dump, and provides Int64/UInt64 types for safely handling 64-bit values beyond JavaScript's safe integer range.

// Dump 64 bytes from the main module base
console.log(hexdump(Process.mainModule.base, { length: 64, header: true, ansi: true }));
// ── hexdump ───────────────────────────────────────────────────────────────
hexdump(target, options)
// target:  NativePointer | ArrayBuffer
// options: { offset?: number, length?: number, header?: boolean, ansi?: boolean }
// Returns a formatted string — pass directly to console.log()

// ── Int64 / UInt64 ────────────────────────────────────────────────────────
// Use when values exceed Number.MAX_SAFE_INTEGER (2^53 − 1)
const big = new Int64('0xDEADBEEFCAFEBABE');
const u   = new UInt64('18446744073709551615');

big.add(1)  big.sub(1)  big.and(mask)
big.or(v)   big.xor(v)  big.shl(n)  big.shr(n)  big.not()
big.compare(other)       // -1 | 0 | 1
big.toNumber()  big.toString()  big.toString(16)

// ── ArrayBuffer helpers ───────────────────────────────────────────────────
const ab  = somePtr.readByteArray(16);   // NativePointer → ArrayBuffer
const buf = Memory.alloc(16);
buf.writeByteArray(ab);                  // copy ArrayBuffer back to native memory

19. Script IPC (send / recv)

Provides bidirectional messaging between the in-process JS agent and the Python/Node.js host, and an rpc.exports object for synchronous remote procedure calls.

// Agent reports every hook hit; host can query modules via RPC
Interceptor.attach(targetFn, {
  onEnter(args) { send({ type: 'hit', arg: args[0].readCString() }); }
});
rpc.exports = {
  getModules() {
    return Process.enumerateModules().map(m => ({ name: m.name, base: m.base.toString() }));
  }
};
// ── Agent → Host ──────────────────────────────────────────────────────────
send({ type: 'log', msg: 'hello' })                // message only
send({ type: 'dump' }, somePtr.readByteArray(64))  // message + binary payload

// ── Host → Agent ──────────────────────────────────────────────────────────
recv('command', (msg) => {
  console.log('got command:', msg.payload.action);
  recv('command', arguments.callee);   // re-arm for next message
});

// ── RPC exports (synchronous call from host) ──────────────────────────────
rpc.exports = {
  dumpMemory(addrHex, size) {
    return ptr(addrHex).readByteArray(size);   // returned as bytes to Python
  },
  patchAddress(addrHex, value) {
    ptr(addrHex).writeU32(value);
  }
};
# Python host side
import frida, sys

def on_message(msg, data):
    if msg['type'] == 'send':
        print('[agent]', msg['payload'])

device  = frida.get_usb_device()
session = device.attach('com.example.game')
script  = session.create_script(open('agent.js').read())
script.on('message', on_message)
script.load()

modules = script.exports.get_modules()                  # RPC call
dump    = script.exports.dump_memory('0x12345678', 256) # → bytes
script.post({'type': 'command', 'payload': {'action': 'start'}})
sys.stdin.read()

20. Frida.Compiler

Compiles TypeScript (or modern JavaScript with ESM imports) into a self-contained bundle loadable as a Frida agent, replacing any manual Node.js build step.

# Compile once
frida-compile agent/index.ts -o bundle.js

# Watch mode — recompile on save
frida-compile agent/index.ts -o bundle.js -w
# Compile and inject programmatically from Python
import frida, asyncio

async def main():
    compiler = frida.Compiler()
    bundle   = await compiler.build('agent/index.ts', compression='terser')
    session  = await frida.get_usb_device_async().attach_async('com.example.App')
    script   = await session.create_script_async(bundle)
    await script.load_async()

asyncio.run(main())
// agent/index.ts — minimal TypeScript agent structure
import "frida-objc-bridge";   // provides ObjC global
import "frida-java-bridge";   // provides Java global

const openAddr: NativePointer = Module.getGlobalExportByName('open');
Interceptor.attach(openAddr, {
  onEnter(args: InvocationArguments) {
    console.log('[open]', args[0].readCString());
  }
});

21. Arm64Writer / X86Writer / ThumbWriter

Emit machine instructions into a writable code buffer — used inside Memory.patchCode() to build patches, trampolines, or inline stubs without hand-encoding raw bytes.

// NOP out 4 ARM64 instructions at a target address
Memory.patchCode(targetAddr, 16, (code) => {
  const w = new Arm64Writer(code, { pc: targetAddr });
  w.putNop(); w.putNop(); w.putNop(); w.putNop();
  w.flush();
});
// ── Arm64Writer (iOS ARM64, Android ARM64) ────────────────────────────────
Memory.patchCode(targetAddr, 64, (code) => {
  const w = new Arm64Writer(code, { pc: targetAddr });

  w.putNop()                            // NOP
  w.putBImm(destAddr)                   // B <addr>  (unconditional branch)
  w.putBCondImm('eq', destAddr)         // B.EQ <addr>
  w.putBlImm(funcAddr)                  // BL <addr>  (branch with link / call)
  w.putLdrRegAddress('x0', dataAddr)    // LDR x0, =<addr>
  w.putMovRegReg('x1', 'x0')            // MOV x1, x0
  w.putRetReg('x30')                    // RET  (BR x30)
  w.putInstruction(0xD503201F)          // emit raw 32-bit encoding

  // Local labels for loops / conditional branches
  w.putLabel('loop_top')
  w.putBCondLabel('ne', 'loop_top')

  w.flush()   // MUST call flush() to finalise the patch
});

// ── X86Writer (Android x86 / x86_64) ─────────────────────────────────────
Memory.patchCode(targetAddr, 32, (code) => {
  const w = new X86Writer(code, { pc: targetAddr });
  w.putNop()
  w.putRet()
  w.putJmpAddress(destAddr)    // JMP <addr>
  w.putCallAddress(funcAddr)   // CALL <addr>
  w.flush()
});

// ── ThumbWriter (Android ARMv7 Thumb) ────────────────────────────────────
Memory.patchCode(targetAddr, 8, (code) => {
  const w = new ThumbWriter(code, { pc: targetAddr });
  w.putNop()
  w.putBImm(destAddr)
  w.flush()
});

22. Platform Tips: iOS ARM64

Practical patterns for working with iOS binaries: ASLR slide calculation, converting IDA Pro offsets to runtime addresses, Swift symbol demangling, and ObjC class discovery.

// Convert an IDA Pro virtual address to its runtime address (ASLR aware)
const runtimeAddr = Process.mainModule.base.add(ptr('0x1000403F0').sub(ptr('0x100000000')));
Interceptor.attach(runtimeAddr, { onEnter() { console.log('hit'); } });
// ── ASLR slide ────────────────────────────────────────────────────────────
const app   = Process.mainModule;
const slide = app.base.sub(ptr('0x100000000'));
console.log('App:', app.name, '@ base', app.base, '(slide', slide, ')');

// ── IDA offset → runtime address helper ───────────────────────────────────
const IDA_BASE = ptr('0x100000000');
const fromIDA  = (va) => app.base.add(ptr(va).sub(IDA_BASE));
Interceptor.attach(fromIDA('0x100123456'), { onEnter() { console.log('hit'); } });

// ── ObjC class discovery ──────────────────────────────────────────────────
Object.keys(ObjC.classes)
  .filter(n => n.startsWith('Game'))
  .forEach(n => console.log(n, '→', ObjC.classes[n].$ownMethods.slice(0, 5)));

// ── Swift symbol demangling ───────────────────────────────────────────────
const swiftCore   = Process.getModuleByName('libswiftCore.dylib');
const demangleFn  = swiftCore.findExportByName('swift_demangle_getDemangledName')
  ?? swiftCore.findExportByName('swift_demangle');
if (demangleFn) {
  const demangle = new NativeFunction(demangleFn, 'pointer',
    ['pointer', 'pointer', 'pointer', 'pointer', 'uint32']);
  const sym    = Memory.allocUtf8String('_$s9TargetApp14GameControllerC10checkCheatyyF');
  const outBuf = Memory.alloc(256);
  const result = demangle(sym, NULL, outBuf, ptr(256), 0);
  if (!result.isNull()) console.log('demangled:', result.readCString());
}

// ── Hook Swift method via mangled symbol ──────────────────────────────────
const swiftFn = app.findSymbolByName('_$s9TargetApp14GameControllerC10checkCheatyyF');
if (swiftFn) {
  Interceptor.attach(swiftFn, { onLeave(retval) { retval.replace(ptr(0)); } });
}

// ── Code signature note ────────────────────────────────────────────────────
// On jailbroken devices memory is already writable — no extra steps needed

23. Platform Tips: Android

Practical patterns for Android: waiting for native library load via dlopen, intercepting RegisterNatives to discover JNI bindings, and accessing ART internals.

// Hook a native export as soon as its library loads
function whenLoaded(libName, cb) {
  if (Process.findModuleByName(libName)) { cb(Process.getModuleByName(libName)); return; }
  const dlopen = Module.getGlobalExportByName('android_dlopen_ext')
    ?? Module.getGlobalExportByName('dlopen');
  let done = false;
  Interceptor.attach(dlopen, {
    onLeave() {
      if (!done && Process.findModuleByName(libName)) {
        done = true; cb(Process.getModuleByName(libName));
      }
    }
  });
}
whenLoaded('libgame.so', mod => {
  const check = mod.findExportByName('checkLicense');
  if (check) Interceptor.attach(check, { onLeave(r) { r.replace(ptr(1)); } });
});
// ── Read /proc/self/maps ──────────────────────────────────────────────────
console.log(new File('/proc/self/maps', 'r').readText());

// ── Intercept JNI RegisterNatives (discover dynamic JNI bindings) ─────────
const libart  = Process.getModuleByName('libart.so');
const regNat  = libart.findExportByName(
  '_ZN3art3JNI15RegisterNativesEP7_JNIEnvP7_jclassPK15JNINativeMethodi');
if (regNat) {
  Interceptor.attach(regNat, {
    onEnter(args) {
      const count = args[3].toInt32();
      for (let i = 0; i < count; i++) {
        const entry = args[2].add(i * 3 * Process.pointerSize);
        const name  = entry.readPointer().readCString();
        const sig   = entry.add(Process.pointerSize).readPointer().readCString();
        const fn    = entry.add(Process.pointerSize * 2).readPointer();
        console.log(`[RegisterNatives] ${name}${sig} → ${fn}`);
      }
    }
  });
}

// ── ART internals via JNI ─────────────────────────────────────────────────
Java.perform(() => {
  const env = Java.vm.getEnv();
  const cls = env.findClass('com/example/Target');
  const mid = env.getMethodID(cls, 'secretMethod', '()V');
  console.log('Method ID:', mid);

  // Hook the generic JNI trampoline (covers @FastNative / @CriticalNative)
  const tramp = Process.getModuleByName('libart.so')
    .findExportByName('artQuickGenericJniTrampoline');
  if (tramp) Interceptor.attach(tramp, { onEnter(args) { /* … */ } });
});

// ── ART SDK version ───────────────────────────────────────────────────────
Java.perform(() => {
  const VMRuntime = Java.use('dalvik.system.VMRuntime');
  console.log('Target SDK:', VMRuntime.getRuntime().targetSdkVersion());
});

24. Quick Reference Table

GoalFrida 17 API
Get module baseProcess.getModuleByName('lib.so').base
Get export addressProcess.getModuleByName('lib.so').getExportByName('fn')
Global export (any module)Module.getGlobalExportByName('open')
Hook function (observe)Interceptor.attach(addr, { onEnter, onLeave })
Replace functionInterceptor.replace(addr, new NativeCallback(…))
Fast replace (no overhead)Interceptor.replaceFast(addr, new NativeCallback(…))
Read memoryptr('0x…').readU32()
Write memoryptr('0x…').writeU32(val)
Chain writesptr('0x…').add(4).writeU32(x).add(4).writeU16(y)
Allocate bufferMemory.alloc(size)
Patch code (W^X safe)Memory.patchCode(addr, size, writerCb)
Scan memory for bytesMemory.scan(base, size, 'AA BB ?? DD', callbacks)
Enumerate modulesProcess.enumerateModules()
Enumerate memory rangesProcess.enumerateRanges('r-x')
Call native functionnew NativeFunction(ptr, retType, argTypes)(…)
Hook ObjC methodInterceptor.attach(ObjC.classes.Cls['- m'].implementation, …)
Hook Java methodJava.perform(() => Java.use('Cls').method.implementation = fn)
BacktraceThread.backtrace(this.context, Backtracer.ACCURATE)
Resolve symbolDebugSymbol.fromAddress(addr).toString()
Trace executionStalker.follow(tid, { events: { block: true }, onReceive })
Send to Python hostsend({ type: 'x', data: val })
Receive from Python hostrecv('type', callback)
Expose function as RPCrpc.exports = { fnName(args) { … } }
Inline C for hot hooksnew CModule('void f() { … }')
Emit ARM64 instructionsnew Arm64Writer(code, { pc: addr }).putNop().flush()
IDA offset → runtime addrapp.base.add(ptr(ida_va).sub(ptr('0x100000000')))

For ready-to-use script templates, see EXAMPLES.md.

Skills enregistrées