I want to patch a text-editor like application in runtime in the following way: when a user opens any file, my predefined file (say /tmp/predefined
) should be opened instead of the users's original file. For this task I use the Frida toolkit with JavaScript API. I intercept all open()
system calls, decide if I need to make a substitution, and if so, replace the first open()
parameter (the path to the file to be opened) with my predefined one.
The script patcher.js
:
function main()
{
Interceptor.attach(Module.getExportByName(null, 'open'), {
onEnter(args) {
const originalPath = args[0].readCString();
if (shouldReplace(originalPath)){
args[0].writeUtf8String('/tmp/predefined'); // (1)
//args[0] = Memory.allocUtf8String("/tmp/predefined"); (2)
}
},
});
}
function shouldReplace(path) {
if (!path) return false;
// Do not replace essential system or config files:
if (path.startsWith("/usr/") || path.startsWith("/etc/") || path.startsWith("/lib") ||
path.startsWith("/var/") || path.startsWith("/proc/") || path.startsWith("/sys/") ||
path.startsWith("/dev/") || path.startsWith("/run/") || path.startsWith("/home/user/.config/")
|| path.startsWith("/home/user/.cache") || path.startsWith("/home/user/.local") ) {
return false;
}
// Avoid replacing if it's already the predefined file (prevent infinite loop)
if (path === "/tmp/predefined") {
return false;
}
// Otherwise, assume it's a user-requested file and should be repalced
return true;
}
main()
As a text-editor I use gnome-text-editor
Run frida as: ./frida -l patcher.js -f /usr/bin/gnome-text-editor /tmp/originalFile
This seems to works: /tmp/predefined
is opened instead of /tmp/originalFile
. But if I want to allocate new memory for string "/tmp/predefined" and assign a pointer to this new memory to args[0], rather than rewriting the contents of the memory pointed to by args[0] (uncomment (2) line, comment out (1) line), I get errors:
/tmp/predefined
and writes Could Not Open File You do not have permissions to open the file
(gnome-text-editor:9036): editor-document-WARNING **: 11:11:33.542: Failed to load file: Error opening file /tmp/originalFile: No such file or directory
, however /tmp/originalFile
exists.Since these errors only occur when I try to allocate new memory for a string and change the value of the args[0]
pointer, I'm wondering if I'm using the memory allocation in Frida's JavaScript API correctly?
The approach to overwrite the character buffer args[0]
using writeUtf8String
may have side effects:
writeUtf8String
will reuse the existing pointer and simply write the characters no matter if they fit into the buffer or not.args[0]
points to is used at different locations within the program this may have side effects, depending on the program hooked with frida.You can avoid that by pointing args[0]
to a self-allocated string. However do not simply allocate it in inside the onEnter
method and return it as it will be freed by the JavaScript garbage collector at the end of onEnter
.
Instead you can make it a global constant string. This example is also presented on the Frida best practices web page.
const myStringBuf = Memory.allocUtf8String('mystring');
Interceptor.attach(f, {
onEnter(args) {
args[0] = myStringBuf;
}
});
In case the modified string to be returned is not static you can use this approach:
const globalStringHolder = [];
Interceptor.attach(f, {
onEnter(args) {
let myStringBuf = Memory.allocUtf8String('mystring' + <something dynamic>);
globalStringHolder.push(myStringBuf);
args[0] = myStringBuf;
}
});
Adding it to a global array will preserver the buffer allocated by Memory.allocUtf8String
so that it is not discarded by JavaScript garbage collector.
Note that of course if the method should be called very often this memory may grow. Thus this approach is preferred for methods not called very often or in scenarios where frida is not attached to the process very long. Otherwise you may need to implement some clean-up system for free no longer used allocated strings.