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:
| Problem | Explanation |
|---|---|
| Read-only memory | The target string may reside in a .rodata section mapped read-only into the process. Writing will segfault. |
| Buffer overflow | If 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
| API | Encoding |
|---|---|
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.