Tuesday, February 8, 2022

Operating with ArrayBuffers in Frida

 Lots of situations will require working directly with ArrayBuffers but operating with them might not be straightforward because data might not always be simple strings. To get a better understanding of how to operate with them in Frida we will use the `fprintf` function and replace the contents of the second argument (our aim is to replace "target" to "foobar").

int main(int argc, char *argv[])

{

if(argc < 3){


        fprintf(stderr, "Usage: %s <target> <port>\n", argv[0]);

        exit(1);


}

return 0;

}

This C program calls the `fprintf` function that is defined as: 

int fprintf(FILE *stream, const char *format, ...);

Usually, functions will provide us with the length of the string but this is not the case so to get around this limitation `.readCString()` will provide us the length of the `*format` parameter.

const fprintfPtr = Module.getExportByName(null, "fprintf");


function str2ab(str) {


  var buf = new ArrayBuffer(str.length);


  var bufView = new Uint8Array(buf);


  for (var i=0, strLen=str.length; i < strLen; i++) {


    bufView[i] = str.charCodeAt(i);


  }


  return buf;


}


Interceptor.attach(fPrintfPtr, {


onEnter: function (args) {


  let buffer_size = args[1].readCString().length + 1;


  let arrayBuf = args[1].readByteArray(buffer_size);


  let str = String.fromCharCode.apply(null, new Uint8Array(arrayBuf));


  str = str.replace("target", "foobar");


  args[1] = str2ab(str).unwrap();


},


});

To understand how this instrumentation code works we will examine it step by step.

str2ab is an auxiliary function that converts a string back to an Uint8Array.

let buffer_size = args[1].readCString().length + 1;

console.log("buffer_size:" + buffer_size);

let arrayBuf = args[1].readByteArray(buffer_size);

readCString() returns the contents of the format* string and gives us the length of the content (+ 1 for the null terminator). Having the size allows us to call readByteArray with the correct size.

let str = String.fromCharCode.apply(null, new Uint8Array(arrayBuf));

str = str.replace("target", "foobar");

When the ArrayBuffer is obtained by calling readByteArray we use String.fromCharCode.apply(null, new Uint8Array) to convert it to a human-readable string (you can skip this step and modify the ArrayBuffer directly).

args[1] = str2ab(str).unwrap();

Once the string is modified the str2ab function transforms the string back to an Uint8Array but we cannot just reassign this Uint8Array to args[1] because it is expecting a pointer. To do so, Frida has an auxiliary method called .unwrap() that returns a pointer to the first element of the `ArrayBuffer`.

Then, it is possible to verify the output:

frida -f a.out -l ins.js --no-pause -q

. . . .   Connected to Local System (id=local)

Spawned a.out. Resuming main thread!

Usage: a.out <foobar> <port>

The str2ab (String to ArrayBuffer) function is a slightly modified version of the one found in developers.google.com adapted to Uint8Arrays.

No comments:

Post a Comment

2023

Every year I start writing about a wrap-up of my year but I never end up finishing it. Hope this year is different. I'm starting with th...