JavaScript Examples

Frida JavaScript Examples

Inject Arbitrary JS into a Node.js Process (V8 VM)

Prerequisites:

Pattern: Schedule JS source strings via a libuv uv_async_t handle that fires on the Node.js event loop thread, then compile and run them using V8’s Script::Compile / Script::Run.

 1const uv_default_loop = new NativeFunction(Module.getGlobalExportByName('uv_default_loop'), 'pointer', []);
 2const uv_async_init  = new NativeFunction(Module.getGlobalExportByName('uv_async_init'),  'int',     ['pointer', 'pointer', 'pointer']);
 3const uv_async_send  = new NativeFunction(Module.getGlobalExportByName('uv_async_send'),  'int',     ['pointer']);
 4const uv_close       = new NativeFunction(Module.getGlobalExportByName('uv_close'),       'void',    ['pointer', 'pointer']);
 5const uv_unref       = new NativeFunction(Module.getGlobalExportByName('uv_unref'),       'void',    ['pointer']);
 6
 7// V8 C++ API — mangled symbol names (GCC ABI)
 8const v8_Isolate_GetCurrent        = new NativeFunction(Module.getGlobalExportByName('_ZN2v87Isolate10GetCurrentEv'),             'pointer', []);
 9const v8_Isolate_GetCurrentContext = new NativeFunction(Module.getGlobalExportByName('_ZN2v87Isolate17GetCurrentContextEv'),      'pointer', ['pointer']);
10const v8_HandleScope_init          = new NativeFunction(Module.getGlobalExportByName('_ZN2v811HandleScopeC1EPNS_7IsolateE'),      'void',    ['pointer', 'pointer']);
11const v8_HandleScope_finalize      = new NativeFunction(Module.getGlobalExportByName('_ZN2v811HandleScopeD1Ev'),                  'void',    ['pointer']);
12const v8_String_NewFromUtf8        = new NativeFunction(Module.getGlobalExportByName('_ZN2v86String11NewFromUtf8EPNS_7IsolateEPKcNS_13NewStringTypeEi'), 'pointer', ['pointer', 'pointer', 'int', 'int']);
13const v8_Script_Compile            = new NativeFunction(Module.getGlobalExportByName('_ZN2v86Script7CompileENS_5LocalINS_7ContextEEENS1_INS_6StringEEEPNS_12ScriptOriginE'), 'pointer', ['pointer', 'pointer', 'pointer']);
14const v8_Script_Run                = new NativeFunction(Module.getGlobalExportByName('_ZN2v86Script3RunENS_5LocalINS_7ContextEEE'), 'pointer', ['pointer', 'pointer']);
15
16const NewStringType = { kNormal: 0, kInternalized: 1 };
17
18const pending = [];
19
20// Callback runs on the Node.js event loop thread — safe to call V8 API here
21const processPending = new NativeCallback(function () {
22  const isolate = v8_Isolate_GetCurrent();
23
24  const scope = Memory.alloc(24);          // sizeof(v8::HandleScope) ~ 24 bytes
25  v8_HandleScope_init(scope, isolate);
26
27  const context = v8_Isolate_GetCurrentContext(isolate);
28
29  while (pending.length > 0) {
30    const item   = pending.shift();
31    const source = v8_String_NewFromUtf8(isolate, Memory.allocUtf8String(item), NewStringType.kNormal, -1);
32    const script = v8_Script_Compile(context, source, NULL);
33    const result = v8_Script_Run(script, context);
34  }
35
36  v8_HandleScope_finalize(scope);
37}, 'void', ['pointer']);
38
39const onClose = new NativeCallback(function () {
40  Script.unpin();   // allow Frida script GC after handle is closed
41}, 'void', ['pointer']);
42
43// Register async handle; uv_unref so it doesn't keep the loop alive
44const handle = Memory.alloc(128);          // sizeof(uv_async_t) ≤ 128 bytes
45uv_async_init(uv_default_loop(), handle, processPending);
46uv_unref(handle);
47
48// When this Frida script is unloaded, close the handle cleanly
49Script.bindWeak(handle, () => {
50  Script.pin();
51  uv_close(handle, onClose);
52});
53
54// Public API: enqueue a JS source string for execution in Node.js V8
55function run(source) {
56  pending.push(source);
57  uv_async_send(handle);
58}
59
60// Usage
61run('console.log("Hello from Frida");');

Key points:


Trace Function Calls in a Perl 5 Process

Prerequisites:

Perl internal offsets used:

FieldOffsetNotes
SV.sv_flags (u32)pointerSize + 4Bottom byte is sv_type
PVGV.xgv_gp->gp_egv->sv_any namehek4 * pointerSizeBody pointer + NAMEHEK offset

SVt_PVGV = 9 — the SV type for a glob (named sub reference).

 1const pointerSize      = Process.pointerSize;
 2const SV_OFFSET_FLAGS  = pointerSize + 4;
 3const PVGV_OFFSET_NAMEHEK = 4 * pointerSize;
 4
 5const SVt_PVGV = 9;
 6
 7Interceptor.attach(Module.getGlobalExportByName('Perl_pp_entersub'), {
 8  onEnter(args) {
 9    // args[0] = PerlInterpreter* (aTHX)
10    const interpreter = args[0];
11    const stack = interpreter.readPointer();  // PL_stack_sp points to top of stack
12    const sub   = stack.readPointer();        // CV* (code value) at top of stack
13
14    const flags = sub.add(SV_OFFSET_FLAGS).readU32();
15    const type  = flags & 0xff;
16    if (type === SVt_PVGV) {
17      // Named sub: read GV name from HEK (hash entry key)
18      console.log(GvNAME(sub) + '()');
19      // Note: console.log() per-call is slow; for high-frequency tracing
20      // buffer events and batch-send with send() instead.
21    }
22    // Anonymous subs (type !== SVt_PVGV) are not handled here
23  }
24});
25
26// Read the name of a GV (glob value) from its embedded HEK
27// HEK layout: [len:u32][hash:u32][key:utf8...][NUL]  → string starts at offset 8
28function GvNAME(sv) {
29  const body   = sv.readPointer();
30  const nameHek = body.add(PVGV_OFFSET_NAMEHEK).readPointer();
31  return nameHek.add(8).readUtf8String();
32}

Key points: