From 75519073abd098fcb01fe45c7f1961343edf1cb5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole=20Andr=C3=A9=20Vadla=20Ravn=C3=A5s?= Date: Fri, 31 May 2024 11:38:54 +0200 Subject: [PATCH 1/4] tests: Split RPC test into a group of tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Håvard Sørbø --- tests/gumjs/script.c | 113 +++++++++++++++++++++++++------------------ 1 file changed, 66 insertions(+), 47 deletions(-) diff --git a/tests/gumjs/script.c b/tests/gumjs/script.c index 676a8d577..1fa9ac210 100644 --- a/tests/gumjs/script.c +++ b/tests/gumjs/script.c @@ -6,6 +6,7 @@ * Copyright (C) 2021 Abdelrahman Eid * Copyright (C) 2023 Grant Douglas * Copyright (C) 2024 Hillel Pinto + * Copyright (C) 2024 Håvard Sørbø * * Licence: wxWindows Library Licence, Version 3.1 */ @@ -27,7 +28,6 @@ TESTLIST_BEGIN (script) TESTENTRY (recv_wait_in_an_application_thread_should_throw_on_unload) TESTENTRY (recv_wait_in_our_js_thread_should_throw_on_unload) TESTENTRY (recv_wait_should_not_leak) - TESTENTRY (rpc_can_be_performed) TESTENTRY (message_can_be_logged) TESTENTRY (thread_can_be_forced_to_sleep) TESTENTRY (thread_backtrace_can_be_captured_with_limit) @@ -44,6 +44,17 @@ TESTLIST_BEGIN (script) TESTENTRY (crash_on_thread_holding_js_lock_should_not_deadlock) #endif + TESTGROUP_BEGIN ("RPC") + TESTENTRY (method_can_be_called_sync) + TESTENTRY (method_can_be_called_async) + TESTENTRY (method_can_throw_sync) + TESTENTRY (method_can_throw_async) + TESTENTRY (method_can_return_null) + TESTENTRY (method_can_return_binary_data) + TESTENTRY (method_list_can_be_queried) + TESTENTRY (calling_inexistent_method_should_throw_error) + TESTGROUP_END () + TESTGROUP_BEGIN ("WeakRef") TESTENTRY (weak_ref_api_should_be_supported) TESTENTRY (weak_callback_is_triggered_on_gc) @@ -5992,60 +6003,68 @@ TESTCASE (array_buffer_can_be_created) EXPECT_NO_MESSAGES (); } -TESTCASE (rpc_can_be_performed) +TESTCASE (method_can_be_called_sync) { - COMPILE_AND_LOAD_SCRIPT ( - "rpc.exports.foo = (a, b) => {" - "const result = a + b;" - "if (result >= 0)" - "return result;" - "else " - "throw new Error('no');" - "};" - "rpc.exports.bar = (a, b) => {" - "return new Promise((resolve, reject) => {" - "const result = a + b;" - "if (result >= 0)" - "resolve(result);" - "else " - "reject(new Error('nope'));" - "});" - "};" - "rpc.exports.badger = () => {" - "const buf = Memory.allocUtf8String(\"Yo\");" - "return buf.readByteArray(2);" - "};" - "rpc.exports.returnNull = () => {" - "return null;" - "};"); - EXPECT_NO_MESSAGES (); - - POST_MESSAGE ("[\"frida:rpc\",1,\"list\"]"); - EXPECT_SEND_MESSAGE_WITH ("[\"frida:rpc\",1,\"ok\"," - "[\"foo\",\"bar\",\"badger\",\"returnNull\"]]"); - - POST_MESSAGE ("[\"frida:rpc\",2,\"call\",\"foo\",[1,2]]"); - EXPECT_SEND_MESSAGE_WITH ("[\"frida:rpc\",2,\"ok\",3]"); + COMPILE_AND_LOAD_SCRIPT ("rpc.exports.add = (a, b) => a + b;"); + POST_MESSAGE ("[\"frida:rpc\",42,\"call\",\"add\",[1,2]]"); + EXPECT_SEND_MESSAGE_WITH ("[\"frida:rpc\",42,\"ok\",3]"); +} - POST_MESSAGE ("[\"frida:rpc\",3,\"call\",\"foo\",[1,-2]]"); - EXPECT_SEND_MESSAGE_WITH_PREFIX ("[\"frida:rpc\",3,\"error\",\"no\","); +TESTCASE (method_can_be_called_async) +{ + COMPILE_AND_LOAD_SCRIPT ("rpc.exports.add = async (a, b) => a + b;"); + POST_MESSAGE ("[\"frida:rpc\",42,\"call\",\"add\",[1,2]]"); + EXPECT_SEND_MESSAGE_WITH ("[\"frida:rpc\",42,\"ok\",3]"); +} - POST_MESSAGE ("[\"frida:rpc\",4,\"call\",\"bar\",[3,4]]"); - EXPECT_SEND_MESSAGE_WITH ("[\"frida:rpc\",4,\"ok\",7]"); +TESTCASE (method_can_throw_sync) +{ + COMPILE_AND_LOAD_SCRIPT ( + "rpc.exports.raise = () => { throw new Error('no'); }"); + POST_MESSAGE ("[\"frida:rpc\",42,\"call\",\"raise\",[]]"); + EXPECT_SEND_MESSAGE_WITH_PREFIX ("[\"frida:rpc\",42,\"error\",\"no\","); +} - POST_MESSAGE ("[\"frida:rpc\",5,\"call\",\"bar\",[3,-4]]"); - EXPECT_SEND_MESSAGE_WITH_PREFIX ("[\"frida:rpc\",5,\"error\",\"nope\","); +TESTCASE (method_can_throw_async) +{ + COMPILE_AND_LOAD_SCRIPT ( + "rpc.exports.raise = async () => { throw new Error('no'); }"); + POST_MESSAGE ("[\"frida:rpc\",42,\"call\",\"raise\",[]]"); + EXPECT_SEND_MESSAGE_WITH_PREFIX ("[\"frida:rpc\",42,\"error\",\"no\","); +} - POST_MESSAGE ("[\"frida:rpc\",6,\"call\",\"baz\",[]]"); - EXPECT_SEND_MESSAGE_WITH ("[\"frida:rpc\",6,\"error\"," - "\"unable to find method 'baz'\"]"); +TESTCASE (method_can_return_null) +{ + COMPILE_AND_LOAD_SCRIPT ("rpc.exports.returnNull = () => null;"); + POST_MESSAGE ("[\"frida:rpc\",42,\"call\",\"returnNull\",[]]"); + EXPECT_SEND_MESSAGE_WITH ("[\"frida:rpc\",42,\"ok\",null]"); +} - POST_MESSAGE ("[\"frida:rpc\",7,\"call\",\"badger\",[]]"); - EXPECT_SEND_MESSAGE_WITH_PAYLOAD_AND_DATA ("[\"frida:rpc\",7,\"ok\",{}]", +TESTCASE (method_can_return_binary_data) +{ + COMPILE_AND_LOAD_SCRIPT ( + "rpc.exports.read = () => {" + "const buf = Memory.allocUtf8String(\"Yo\");" + "return buf.readByteArray(2);" + "};"); + POST_MESSAGE ("[\"frida:rpc\",42,\"call\",\"read\",[]]"); + EXPECT_SEND_MESSAGE_WITH_PAYLOAD_AND_DATA ("[\"frida:rpc\",42,\"ok\",{}]", "59 6f"); +} - POST_MESSAGE ("[\"frida:rpc\",8,\"call\",\"returnNull\",[]]"); - EXPECT_SEND_MESSAGE_WITH ("[\"frida:rpc\",8,\"ok\",null]"); +TESTCASE (method_list_can_be_queried) +{ + COMPILE_AND_LOAD_SCRIPT ("rpc.exports = { a() {}, b() {}, c() {} };"); + POST_MESSAGE ("[\"frida:rpc\",42,\"list\"]"); + EXPECT_SEND_MESSAGE_WITH ("[\"frida:rpc\",42,\"ok\",[\"a\",\"b\",\"c\"]]"); +} + +TESTCASE (calling_inexistent_method_should_throw_error) +{ + COMPILE_AND_LOAD_SCRIPT (""); + POST_MESSAGE ("[\"frida:rpc\",42,\"call\",\"banana\",[]]"); + EXPECT_SEND_MESSAGE_WITH ("[\"frida:rpc\",42,\"error\"," + "\"unable to find method 'banana'\"]"); } TESTCASE (message_can_be_sent) From b47d3c3cdee87770b38eae5edc29935983372768 Mon Sep 17 00:00:00 2001 From: WorksButNotTested <62701594+WorksButNotTested@users.noreply.github.com> Date: Thu, 16 May 2024 10:17:39 +0100 Subject: [PATCH 2/4] gumjs: Support receiving binary data in RPC methods MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Håvard Sørbø Co-authored-by: Ole André Vadla Ravnås --- bindings/gumjs/runtime/message-dispatcher.js | 6 +++--- tests/gumjs/script.c | 20 ++++++++++++++++++++ 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/bindings/gumjs/runtime/message-dispatcher.js b/bindings/gumjs/runtime/message-dispatcher.js index e9539b6fc..035fe03db 100644 --- a/bindings/gumjs/runtime/message-dispatcher.js +++ b/bindings/gumjs/runtime/message-dispatcher.js @@ -26,14 +26,14 @@ function MessageDispatcher() { function handleMessage(rawMessage, data) { const message = JSON.parse(rawMessage); if (message instanceof Array && message[0] === 'frida:rpc') { - handleRpcMessage(message[1], message[2], message.slice(3)); + handleRpcMessage(message[1], message[2], message.slice(3), data); } else { messages.push([message, data]); dispatchMessages(); } } - function handleRpcMessage(id, operation, params) { + function handleRpcMessage(id, operation, params, data) { const exports = rpc.exports; if (operation === 'call') { @@ -46,7 +46,7 @@ function MessageDispatcher() { } try { - const result = exports[method].apply(exports, args); + const result = exports[method].call(exports, ...args, data); if (typeof result === 'object' && result !== null && typeof result.then === 'function') { result diff --git a/tests/gumjs/script.c b/tests/gumjs/script.c index 1fa9ac210..32916329d 100644 --- a/tests/gumjs/script.c +++ b/tests/gumjs/script.c @@ -50,6 +50,7 @@ TESTLIST_BEGIN (script) TESTENTRY (method_can_throw_sync) TESTENTRY (method_can_throw_async) TESTENTRY (method_can_return_null) + TESTENTRY (method_can_receive_binary_data) TESTENTRY (method_can_return_binary_data) TESTENTRY (method_list_can_be_queried) TESTENTRY (calling_inexistent_method_should_throw_error) @@ -6040,6 +6041,25 @@ TESTCASE (method_can_return_null) EXPECT_SEND_MESSAGE_WITH ("[\"frida:rpc\",42,\"ok\",null]"); } +TESTCASE (method_can_receive_binary_data) +{ + const guint8 data_to_send[2] = { 0x13, 0x37 }; + GBytes * bytes; + + COMPILE_AND_LOAD_SCRIPT ( + "rpc.exports.eat = (str, data) => {" + "send(str, data);" + "}"); + + bytes = g_bytes_new_static (data_to_send, sizeof (data_to_send)); + gum_script_post (fixture->script, + "[\"frida:rpc\",42,\"call\",\"eat\",[\"yoghurt\"]]", bytes); + g_bytes_unref (bytes); + + EXPECT_SEND_MESSAGE_WITH_PAYLOAD_AND_DATA ("\"yoghurt\"", "13 37"); + EXPECT_SEND_MESSAGE_WITH ("[\"frida:rpc\",42,\"ok\",null]"); +} + TESTCASE (method_can_return_binary_data) { COMPILE_AND_LOAD_SCRIPT ( From 52fa04d9e59564c0196f0065af7128cdc149946d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole=20Andr=C3=A9=20Vadla=20Ravn=C3=A5s?= Date: Fri, 31 May 2024 12:07:35 +0200 Subject: [PATCH 3/4] gumjs: Reply with undefined when RPC method returns binary data MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Instead of an empty object. This isn't used by clients, so this is only for semantic consistency with a method not returning any value. Co-authored-by: Håvard Sørbø --- bindings/gumjs/runtime/message-dispatcher.js | 2 +- tests/gumjs/script.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bindings/gumjs/runtime/message-dispatcher.js b/bindings/gumjs/runtime/message-dispatcher.js index 035fe03db..6661d0414 100644 --- a/bindings/gumjs/runtime/message-dispatcher.js +++ b/bindings/gumjs/runtime/message-dispatcher.js @@ -71,7 +71,7 @@ function MessageDispatcher() { params = params || []; if (result instanceof ArrayBuffer) - send(['frida:rpc', id, type, {}].concat(params), result); + send(['frida:rpc', id, type, undefined].concat(params), result); else send(['frida:rpc', id, type, result].concat(params)); } diff --git a/tests/gumjs/script.c b/tests/gumjs/script.c index 32916329d..59600eed7 100644 --- a/tests/gumjs/script.c +++ b/tests/gumjs/script.c @@ -6068,7 +6068,7 @@ TESTCASE (method_can_return_binary_data) "return buf.readByteArray(2);" "};"); POST_MESSAGE ("[\"frida:rpc\",42,\"call\",\"read\",[]]"); - EXPECT_SEND_MESSAGE_WITH_PAYLOAD_AND_DATA ("[\"frida:rpc\",42,\"ok\",{}]", + EXPECT_SEND_MESSAGE_WITH_PAYLOAD_AND_DATA ("[\"frida:rpc\",42,\"ok\",null]", "59 6f"); } From e4f72cf3cec4c018b180fe53b427fedc3883cc6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole=20Andr=C3=A9=20Vadla=20Ravn=C3=A5s?= Date: Fri, 31 May 2024 12:14:59 +0200 Subject: [PATCH 4/4] gumjs: Support returning value and binary data from RPC methods MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Håvard Sørbø --- bindings/gumjs/runtime/message-dispatcher.js | 16 +++++++++------- tests/gumjs/script.c | 14 ++++++++++++++ 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/bindings/gumjs/runtime/message-dispatcher.js b/bindings/gumjs/runtime/message-dispatcher.js index 6661d0414..c1d1ba2fc 100644 --- a/bindings/gumjs/runtime/message-dispatcher.js +++ b/bindings/gumjs/runtime/message-dispatcher.js @@ -67,13 +67,15 @@ function MessageDispatcher() { } } - function reply(id, type, result, params) { - params = params || []; - - if (result instanceof ArrayBuffer) - send(['frida:rpc', id, type, undefined].concat(params), result); - else - send(['frida:rpc', id, type, result].concat(params)); + function reply(id, type, result, params = []) { + if (Array.isArray(result) && result.length === 2 && result[1] instanceof ArrayBuffer) { + const [value, data] = result; + send(['frida:rpc', id, type, value, ...params], data); + } else if (result instanceof ArrayBuffer) { + send(['frida:rpc', id, type, undefined, ...params], result); + } else { + send(['frida:rpc', id, type, result, ...params]); + } } function dispatchMessages() { diff --git a/tests/gumjs/script.c b/tests/gumjs/script.c index 59600eed7..b442a428b 100644 --- a/tests/gumjs/script.c +++ b/tests/gumjs/script.c @@ -52,6 +52,7 @@ TESTLIST_BEGIN (script) TESTENTRY (method_can_return_null) TESTENTRY (method_can_receive_binary_data) TESTENTRY (method_can_return_binary_data) + TESTENTRY (method_can_return_value_and_binary_data) TESTENTRY (method_list_can_be_queried) TESTENTRY (calling_inexistent_method_should_throw_error) TESTGROUP_END () @@ -6072,6 +6073,19 @@ TESTCASE (method_can_return_binary_data) "59 6f"); } +TESTCASE (method_can_return_value_and_binary_data) +{ + COMPILE_AND_LOAD_SCRIPT ( + "rpc.exports.read = () => {" + "const buf = Memory.allocUtf8String(\"Yo\");" + "return [{meta: 'data'}, buf.readByteArray(2)];" + "};"); + POST_MESSAGE ("[\"frida:rpc\",42,\"call\",\"read\",[]]"); + EXPECT_SEND_MESSAGE_WITH_PAYLOAD_AND_DATA ( + "[\"frida:rpc\",42,\"ok\",{\"meta\":\"data\"}]", + "59 6f"); +} + TESTCASE (method_list_can_be_queried) { COMPILE_AND_LOAD_SCRIPT ("rpc.exports = { a() {}, b() {}, c() {} };");