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.
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
.