javascriptpythonandroidfrida

Frida: How to send byte[] array from JavaScript to Python


I have a Frida JS script inside a Python session, and I'm trying to pass an array of bytes (from a Bitmap image) from the JavaScript environment back to the Python environment. Here is my attempt:

import frida
import sys
import os

JS_SCRIPT = '''
setTimeout(function () {{
  Java.perform(function () {{
      // declare dependencies on necessary Java classes
      const File = Java.use("java.io.File");
      const Bitmap = Java.use("android.graphics.Bitmap");
      const BitmapCompressFormat = Java.use("android.graphics.Bitmap$CompressFormat");
      const BitmapConfig = Java.use("android.graphics.Bitmap$Config");
      const ByteArrayOutputStream = Java.use("java.io.ByteArrayOutputStream");

      // instantiate a new Bitmap object
      const bitmap = Bitmap.createBitmap(100, 100, BitmapConfig.ARGB_8888.value);

      // output bitmap to a byte stream in PNG format
      const stream = ByteArrayOutputStream.$new();
      const saved = bitmap.compress(BitmapCompressFormat.PNG.value, 100, stream);
      console.log("[*] Compressed as PNG:", saved);

      // get byte array from byte stream
      const byteArray = stream.toByteArray();
      console.log("[*] Byte array length:", byteArray.length);

      // send the byte stream to the Python layer
      send({{ type: "bitmap", page: pageNum }}, byteArray);
      stream.close();
  }});
}}, 1000);
'''

def on_message(message, data):
    if message["type"] == "send" and message["payload"].get("type") == "bitmap":
        page = message["payload"].get("page")
        with open(OUTPUT_FILENAME, "wb") as f:
            f.write(data)
        print(f"[+] Saved page {page} as {OUTPUT_FILENAME}")
    else:
        print(f"[?] Unknown message: {message}")

def main():
    device = frida.get_usb_device(timeout=5)
    session = device.attach(pid)
    script = session.create_script(JS_SCRIPT)
    script.on("message", on_message)
    script.load()
    device.resume(pid)

if __name__ == "__main__":
    main()

The problem happens on the call to send() because the second argument byteArray is not a pointer:

Error: expected a pointer

It's unclear to me how to get byteArray into a format that can be sent using the send() function, and I'm having trouble finding the solution in the Frida API docs.


Solution

  • Frida provides out of the box only methods for sending native byte arrays, thus raw data stored in ArrayBuffer or data at a certain NativePointer. Sending Java byte arrays in an efficient way requires a bit more work as you first have o convert the byte[] into a form that can b serialized by send().

    The most simplest approach would is to convert byte[] to a String and send it in the first argument of send().

    Luckily the hooked process seems to be an Android app, thus we can make use of the Android API to do the conversion:

    const base64 = Java.use('android.util.Base64');
    const base64Data = base64.encodeToString(byteArray , 2)); // 2 = Base64.NO_WRAP flag
    send("BITMAP#" + pageNum + "#" + sendStr);
    

    On Python side you can then split the received string on the # characters and convert the third part from base64 to a byte string.

    This solution is simple but has a drawback: As the byte array is converted to base64 it exists at least twice in the memory of the Android app. For large byte arrays this can cause problems if the Android app is running out of RAM, thus in such cases you may need to use the second variant of encodeToString (byte[] input, int offset, int len, int flags) that allows to convert a byte array to at once but process it in multiple blocks by specifying offset and length.