From 243cce22e6db8d6aa6ae861e71e87fe00f73dc68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole=20Andr=C3=A9=20Vadla=20Ravn=C3=A5s?= Date: Fri, 31 May 2024 15:08:14 +0200 Subject: [PATCH] rpc: Add support for new binary data passing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Which means: - Pass in data to RPC method. - Return value and data from RPC method. Co-authored-by: Håvard Sørbø --- frida/core.py | 39 ++++++++++++++++++++++++++------------- tests/test_rpc.py | 29 +++++++++++++++++++++-------- 2 files changed, 47 insertions(+), 21 deletions(-) diff --git a/frida/core.py b/frida/core.py index 7736d21..76a32a0 100644 --- a/frida/core.py +++ b/frida/core.py @@ -176,7 +176,8 @@ class ScriptExportsSync: js_name = _to_camel_case(name) def method(*args: Any, **kwargs: Any) -> Any: - return script._rpc_request("call", js_name, args, **kwargs) + request, data = make_rpc_call_request(js_name, args) + return script._rpc_request(request, data, **kwargs) return method @@ -202,7 +203,8 @@ class ScriptExportsAsync: js_name = _to_camel_case(name) async def method(*args: Any, **kwargs: Any) -> Any: - return await script._rpc_request_async("call", js_name, args, **kwargs) + request, data = make_rpc_call_request(js_name, args) + return await script._rpc_request_async(request, data, **kwargs) return method @@ -210,6 +212,16 @@ class ScriptExportsAsync: return self._script.list_exports_sync() +def make_rpc_call_request(js_name: str, args: List[Any]) -> List[Any]: + if args and isinstance(args[-1], bytes): + raw_args = args[:-1] + data = args[-1] + else: + raw_args = args + data = None + return (["call", js_name, raw_args], data) + + class ScriptErrorMessage(TypedDict): type: Literal["error"] description: str @@ -404,7 +416,7 @@ class Script: Asynchronously list all the exported attributes from the script's rpc """ - result = await self._rpc_request_async("list") + result = await self._rpc_request_async(["list"]) assert isinstance(result, list) return result @@ -413,7 +425,7 @@ class Script: List all the exported attributes from the script's rpc """ - result = self._rpc_request("list") + result = self._rpc_request(["list"]) assert isinstance(result, list) return result @@ -429,7 +441,7 @@ class Script: ) return self.list_exports_sync() - def _rpc_request_async(self, *args: Any) -> asyncio.Future[Any]: + def _rpc_request_async(self, args: Any, data: Optional[bytes] = None) -> asyncio.Future[Any]: loop = asyncio.get_event_loop() future: asyncio.Future[Any] = asyncio.Future() @@ -442,14 +454,14 @@ class Script: request_id = self._append_pending(on_complete) if not self.is_destroyed: - self._send_rpc_call(request_id, *args) + self._send_rpc_call(request_id, args, data) else: self._on_destroyed() return future @cancellable - def _rpc_request(self, *args: Any) -> Any: + def _rpc_request(self, args: Any, data: Optional[bytes] = None) -> Any: result = RPCResult() def on_complete(value: Any, error: Optional[Union[RPCException, _frida.InvalidOperationError]]) -> None: @@ -466,7 +478,7 @@ class Script: request_id = self._append_pending(on_complete) if not self.is_destroyed: - self._send_rpc_call(request_id, *args) + self._send_rpc_call(request_id, args, data) cancellable = Cancellable.get_current() cancel_handler = cancellable.connect(on_cancelled) @@ -495,10 +507,8 @@ class Script: self._pending[request_id] = callback return request_id - def _send_rpc_call(self, request_id: int, *args: Any) -> None: - message = ["frida:rpc", request_id] - message.extend(args) - self.post(message) + def _send_rpc_call(self, request_id: int, args: Any, data: Optional[bytes]) -> None: + self.post(["frida:rpc", request_id, *args], data) def _on_rpc_message(self, request_id: int, operation: str, params: List[Any], data: Optional[Any]) -> None: if operation in ("ok", "error"): @@ -509,7 +519,10 @@ class Script: value = None error = None if operation == "ok": - value = params[0] if data is None else data + if data is not None: + value = (params[1], data) if len(params) > 1 else data + else: + value = params[0] else: error = RPCException(*params[0:3]) diff --git a/tests/test_rpc.py b/tests/test_rpc.py index 7b2a70e..318c81e 100644 --- a/tests/test_rpc.py +++ b/tests/test_rpc.py @@ -31,19 +31,26 @@ class TestRpc(unittest.TestCase): name="test-rpc", source="""\ rpc.exports = { - add: function (a, b) { - var result = a + b; + add(a, b) { + const result = a + b; if (result < 0) - throw new Error("No"); + throw new Error("No"); return result; }, - sub: function (a, b) { + sub(a, b) { return a - b; }, - speak: function () { - var buf = Memory.allocUtf8String("Yo"); + speak() { + const buf = Memory.allocUtf8String("Yo"); return Memory.readByteArray(buf, 2); - } + }, + speakWithMetadata() { + const buf = Memory.allocUtf8String("Yo"); + return ['soft', Memory.readByteArray(buf, 2)]; + }, + processData(val, data) { + return { val, dump: hexdump(data, { header: false }) }; + }, }; """, ) @@ -52,7 +59,13 @@ rpc.exports = { self.assertEqual(agent.add(2, 3), 5) self.assertEqual(agent.sub(5, 3), 2) self.assertRaises(Exception, lambda: agent.add(1, -2)) - self.assertListEqual([x for x in iter(agent.speak())], [0x59, 0x6F]) + self.assertEqual(agent.speak(), b"\x59\x6f") + meta, data = agent.speak_with_metadata() + self.assertEqual(meta, "soft") + self.assertEqual(data, b"\x59\x6f") + result = agent.process_data(1337, b"\x13\x37") + self.assertEqual(result["val"], 1337) + self.assertEqual(result["dump"], "00000000 13 37 .7") def test_post_failure(self): script = self.session.create_script(