Frida GumJS cheatsheet¶
Use this for writing agent scripts (code that runs inside the target process) with Frida 17.5.2.
0) “Frida 17+” compatibility landmines (so old blog-post code doesn’t mislead you)¶
- Enumeration APIs are “modern” now: e.g.
Process.enumerateModules()returns an array you can iterate; the old callback-style enumeration is gone. ([Frida][1]) - Memory read/write moved onto pointers:
p.readU32()/p.writeU32(v)(legacyMemory.readU32(p)/Memory.writeU32(p, v)removed). ([Frida][1]) - Static
Module.*(moduleName, symbol)helpers are gone: e.g.Module.getExportByName('libc.so', 'open')→Process.getModuleByName('libc.so').getExportByName('open'). Global lookup isModule.getGlobalExportByName('open'). ([Frida][1]) - Java/ObjC bridges aren’t bundled into GumJS anymore (when you bundle with
frida-compile/ ESM): install andimportthem (frida-java-bridge,frida-objc-bridge). REPL +frida-tracestill include them for convenience. ([Frida][1]) - 17.5.2 Windows note: export metadata’s
typeproperty is fixed so it reflects the real export type (instead of always reporting functions). This matters if you enumerate exports and branch on.type. ([Frida][2])
1) Tiny agent template (drop-in)¶
'use strict';
function log(...a) { console.log('[*]', ...a); }
setImmediate(() => {
log('Frida', Frida.version, 'runtime', Script.runtime);
log('Process', Process.id, Process.platform, Process.arch);
});
Frida.versionandScript.runtime(QJS or V8) exist for runtime introspection. ([Frida][3])Process.id,Process.platform,Process.archare documented properties. ([Frida][3])
2) Quick “where am I?” primitives¶
Process / safety knobs¶
Process.pageSize,Process.pointerSizefor portability. ([Frida][3])Process.codeSigningPolicytells you if code-modifying features may be off-limits (e.g. Interceptor) in code-signing constrained environments. ([Frida][3])
3) Find targets to hook¶
Enumerate and look up modules¶
for (const m of Process.enumerateModules()) {
console.log(m.name, m.base, m.size);
}
const libc = Process.getModuleByName('libc.so'); // throws if missing
const maybe = Process.findModuleByName('libfoo.so'); // null if missing
Relevant APIs: Process.enumerateModules(), findModuleByName() / getModuleByName(). ([Frida][3])
Find exports/symbols inside a module¶
const libc = Process.getModuleByName('libc.so');
const openPtr = libc.getExportByName('open'); // throws if absent
const openPtr2 = libc.findExportByName('open'); // null if absent
Module instance methods include findExportByName() / getExportByName() (and same pattern for symbols). ([Frida][3])
Global export lookup (slower, but convenient)¶
Docs warn this global search can be costly and should be avoided when possible. ([Frida][3])
Find things by glob/pattern: ApiResolver¶
This is the go-to when you don’t know which module, or names are mangled:
const r = new ApiResolver('module');
const hits = r.enumerateMatches('exports:*!open*');
hits.slice(0, 10).forEach(h => console.log(h.name, h.address));
ApiResolver types include module, plus swift and objc when those runtimes are present. ([Frida][3])
Hook as soon as a module loads: attachModuleObserver¶
const obs = Process.attachModuleObserver({
onAdded(m) {
if (m.name === 'libtarget.so') {
console.log('Loaded:', m.name, m.base);
// install hooks now (before app uses it)
}
}
});
// obs.detach();
Process.attachModuleObserver() calls onAdded for existing modules right away and then synchronously as new modules load. ([Frida][3])
4) Hook native functions¶
Interceptor.attach (the “daily driver”)¶
const libc = Process.getModuleByName('libc.so');
const readPtr = libc.getExportByName('read');
Interceptor.attach(readPtr, {
onEnter(args) {
this.fd = args[0].toInt32();
this.buf = args[1];
this.count = args[2].toInt32();
},
onLeave(retval) {
const n = retval.toInt32();
if (n > 0) {
console.log('fd', this.fd, 'read', n);
console.log(hexdump(this.buf, { length: n }));
}
}
});
What you get:
onEnter(args)whereargs[i]areNativePointers.onLeave(retval)whereretval.replace(...)can change the return value.thisis a per-invocation object (thread-local storage for your hook). ([Frida][3])
Extra context inside callbacks:
this.context,this.returnAddress,this.threadId,this.depth, pluserrno(UNIX) /lastError(Windows). ([Frida][3])
Interceptor.replace (override the function)¶
const libc = Process.getModuleByName('libc.so');
const openPtr = libc.getExportByName('open');
const openOrig = new NativeFunction(openPtr, 'int', ['pointer', 'int']);
Interceptor.replace(openPtr, new NativeCallback((pathPtr, flags) => {
const path = pathPtr.readUtf8String();
console.log('open(', path, flags, ')');
return openOrig(pathPtr, flags);
}, 'int', ['pointer', 'int']));
- Replacement is typically a
NativeCallback. - Calling the “original” is done via
NativeFunction(it bypasses the replacement). ([Frida][3])
replaceFast / revert / flush¶
Interceptor.replaceFast()is likereplace()but lower overhead (and you must use the returned pointer to call the original). ([Frida][3])Interceptor.revert(target)restores the original. ([Frida][3])Interceptor.flush()is only needed in rare cases where you hook/replace and then immediately call the function before auto-flush kicks in. ([Frida][3])
Performance notes (important in real targets)¶
- Omit
onLeaveif you don’t need it (empty callbacks still cost). send()is async, but high-frequency sends are expensive—batch your data. ([Frida][3])- For very hot hooks, move logic to C using
CModulecallbacks. ([Frida][3])
5) Pointers, memory, and types¶
NativePointer essentials¶
const p = ptr('0x1234'); // or new NativePointer('0x1234')
if (p.isNull()) return;
const q = p.add(0x10);
console.log(q.readU32());
q.writeU32(0x1337);
Pointer creation + arithmetic + typed reads/writes are part of NativePointer. ([Frida][3])
Strings at pointers¶
const s = p.readUtf8String(); // NUL-terminated by default
p.writeUtf8String("hello"); // writes NUL-terminated string
String helpers: readUtf8String, readUtf16String, writeUtf8String, etc. ([Frida][3])
Byte arrays + send to host¶
readByteArray() returns an ArrayBuffer; send(message[, data]) supports sending bytes as data. ([Frida][3])
Allocate memory (keep references!)¶
Memory.alloc() returns a NativePointer; the allocation is freed when JS handles are gone—keep a reference while native code still uses it. ([Frida][3])
NativeFunction quick ref¶
const libc = Process.getModuleByName('libc.so');
const strlenPtr = libc.getExportByName('strlen');
const strlen = new NativeFunction(strlenPtr, 'ulong', ['pointer']);
const s = Memory.alloc("hello".length + 1);
s.writeUtf8String("hello");
console.log('strlen:', strlen(s));
NativeFunction(address, returnType, argTypes[, abi/options]) + supported types/ABIs are listed in the API reference. ([Frida][3])
6) Memory scanning and patching¶
Scan for byte patterns¶
const m = Process.enumerateModules()[0];
const pattern = '13 37 ?? ff';
Memory.scan(m.base, m.size, pattern, {
onMatch(address, size) { console.log('hit', address); },
onError(reason) { console.error('scan error', reason); },
onComplete() { console.log('done'); }
});
Pattern syntax + callbacks are documented. ([Frida][3])
Patch code safely¶
Use Memory.patchCode(address, size, apply) when you need to modify code bytes. It’s designed to work on platforms that require special handling for code pages. ([Frida][3])
7) Backtraces, symbols, and exceptions¶
Pretty backtraces with DebugSymbol¶
Interceptor.attach(Module.getGlobalExportByName('open'), {
onEnter(args) {
console.log(Thread.backtrace(this.context, Backtracer.ACCURATE)
.map(DebugSymbol.fromAddress)
.join('\n'));
}
});
The docs explicitly show Thread.backtrace(...).map(DebugSymbol.fromAddress) as a recommended pattern. ([Frida][3])
Exception handler (crash triage / possible recovery)¶
Process.setExceptionHandler(details => {
console.error('Exception', details.type, 'at', details.address);
// return true to claim it + resume; false to let the process handle/terminate
return false;
});
Process.setExceptionHandler() and the details fields are documented. ([Frida][3])
8) Messaging & RPC (agent ↔ host)¶
send()¶
send(message[, data])sends a JSON-serializable object + optional bytes. ([Frida][3])
rpc.exports (call agent functions from Python/Node)¶
rpc.exports functions can return immediate values or Promises. ([Frida][3])
Worker (move heavy work off the hook thread)¶
worker.js
export const url = import.meta.url;
rpc.exports = {
heavy(x) {
// do heavy parsing here
return x * 2;
}
};
main agent
import { url as workerUrl } from './worker.js';
const w = new Worker(workerUrl, {
onMessage(m) { console.log('worker msg', m); }
});
(async () => {
const out = await w.exports.heavy(21);
console.log('heavy =>', out);
})();
Worker API (new Worker(url[, options]), post, exports) and the import.meta.url pattern are documented. ([Frida][3])
9) Android (Java) quick reference¶
Import note (Frida 17+ bundled agents)¶
If you bundle with frida-compile: npm install frida-java-bridge and:
Not needed in scripts run through the Frida REPL / frida-trace. ([Frida][3])
Basic hook pattern¶
if (!Java.available) return;
Java.perform(() => {
const Activity = Java.use('android.app.Activity');
Activity.onResume.implementation = function () {
send('Activity.onResume()');
return this.onResume();
};
});
Java.availableandJava.perform()are the standard entry points. ([Frida][3])Java.use()creates wrappers; methods have.implementation. ([Frida][3])
Method discovery by pattern¶
Java.perform(() => {
const groups = Java.enumerateMethods('*youtube*!on*');
console.log(JSON.stringify(groups, null, 2));
});
Java.enumerateMethods() example is in the docs. ([Frida][3])
10) iOS/macOS (ObjC) quick reference¶
Import note (Frida 17+ bundled agents)¶
If you bundle with frida-compile: npm install frida-objc-bridge and:
Not needed in scripts run through the Frida REPL / frida-trace. ([Frida][3])
Availability + calling ObjC methods¶
if (!ObjC.available) return;
const { NSString } = ObjC.classes;
const s = NSString.stringWithString_("Hello");
console.log(s.toString());
ObjC.availablegate is required.ObjC.classesand selector mapping (:→_) are documented. ([Frida][3])
Hook an ObjC method (common pattern)¶
if (!ObjC.available) return;
const cls = ObjC.classes.NSURLRequest;
const impl = cls['- valueForHTTPHeaderField:'].implementation;
Interceptor.attach(impl, {
onEnter(args) {
const self = new ObjC.Object(args[0]);
const sel = ObjC.selectorAsString(args[1]);
const headerName = new ObjC.Object(args[2]).toString();
console.log(sel, 'arg:', headerName, 'self:', self.$className);
}
});
- ObjC bridge exposes method
.implementationpointers usable withInterceptor.attach. ([Frida][3]) ObjC.selectorAsString()exists for selector formatting. ([Frida][3])
11) “Hot path” acceleration: CModule (optional but powerful)¶
Use CModule to move the heaviest hook logic into C (less JS overhead):
new CModule(code[, symbols, options])compiles & maps a C module, exporting functions asNativePointers you can call or use as hook callbacks. ([Frida][3])- The docs specifically call out using it for hot callbacks (Interceptor/Stalker) and show REPL loading patterns. ([Frida][3])
12) One-page “old → new” migration crib (Frida 17+)¶
-
Enumerate modules
-
Old:
Process.enumerateModules({ onMatch, onComplete }) -
New:
for (const m of Process.enumerateModules()) { ... }([Frida][1]) -
Read/write memory
-
Old:
Memory.readU32(p)/Memory.writeU32(p, v) -
New:
p.readU32()/p.writeU32(v)([Frida][1]) -
Export lookup
-
Old:
Module.getExportByName('libc.so', 'open') - New:
Process.getModuleByName('libc.so').getExportByName('open')([Frida][1])