javascriptfrida

Am I using the correct memory allocation in Frida?


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:

  1. The editor does not open /tmp/predefined and writes Could Not Open File You do not have permissions to open the file
  2. In the terminal: (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?


Solution

  • The approach to overwrite the character buffer args[0] using writeUtf8String may have side effects:

    1. If the string to be replaced is longer that the original string it will cause a buffer overflow as writeUtf8String will reuse the existing pointer and simply write the characters no matter if they fit into the buffer or not.
    2. If the character buffer 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.