Best Practices

String Allocation (UTF-8 / UTF-16 / ANSI)

The Wrong Pattern

1// WRONG: do not write strings directly into existing argument pointers
2onEnter(args) {
3  args[0].writeUtf8String('mystring');
4}

Why this fails:

ProblemExplanation
Read-only memoryThe target string may reside in a .rodata section mapped read-only into the process. Writing will segfault.
Buffer overflowIf the new string is longer than the existing one, writeUtf8String() overflows the buffer and may corrupt adjacent memory.

Memory.protect() can work around the read-only issue but is fragile and not recommended.


Correct Pattern — Short-lived allocation (duration of one function call)

Store the allocation on this, which is a per-thread, per-invocation object shared between onEnter and onLeave. The object is kept alive for the entire call frame, including recursive invocations.

1Interceptor.attach(targetFunc, {
2  onEnter(args) {
3    const buf = Memory.allocUtf8String('mystring');
4    this.buf = buf;   // keeps buf alive until onLeave returns
5    args[0] = buf;    // replace the argument
6  }
7  // onLeave can access this.buf if needed
8});

Prerequisites: targetFunc is a NativePointer obtained via Module.getExportByName() or similar.

GC pitfall: Any value returned by Memory.alloc*() is freed as soon as the JavaScript value is garbage-collected. If you do not store a reference, the buffer can be freed before the callee reads it.


Correct Pattern — Long-lived allocation (survives across calls)

If the native function stores the pointer and dereferences it after the call returns, allocate outside the interceptor callback:

1// Allocated once at script load time; never garbage-collected
2const myStringBuf = Memory.allocUtf8String('mystring');
3
4Interceptor.attach(targetFunc, {
5  onEnter(args) {
6    args[0] = myStringBuf;
7  }
8});

When to use: When the callee keeps the pointer around (e.g., stores it in a struct field) and reads it after the function returns.


Allocation APIs

APIEncoding
Memory.allocUtf8String(str)UTF-8, null-terminated
Memory.allocUtf16String(str)UTF-16LE, null-terminated
Memory.allocAnsiString(str)ANSI (Windows code page), null-terminated

All return a NativePointer. The allocated buffer is freed when the returned JS value is GC’d.


Reusing Arguments

The Inefficient Pattern

1Interceptor.attach(f, {
2  onEnter(args) {
3    if (!args[0].readUtf8String(4).includes('MZ')) {
4      console.log(hexdump(args[0]));  // args[0] queried a second time
5    }
6  }
7});

Each access to args[N] issues a query to frida-gum. Accessing the same index multiple times wastes CPU cycles.

The Efficient Pattern

1Interceptor.attach(f, {
2  onEnter(args) {
3    const firstArg = args[0];   // single query
4    if (!firstArg.readUtf8String(4).includes('MZ')) {
5      console.log(hexdump(firstArg));
6    }
7  }
8});

Rule: Cache any args[N] value into a local variable whenever it is used more than once within the same callback.