From 949f9298519ca6bb0b8e7760bedb91c7a2b7683a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole=20Andr=C3=A9=20Vadla=20Ravn=C3=A5s?= Date: Mon, 16 Dec 2024 14:42:53 +0100 Subject: [PATCH] gumjs: Add findSymbolByName()/getSymbolByName() Provide direct, native lookups for symbols by name instead of enumerating all symbols and filtering them in JavaScript. On ELF-based backends, we can potentially leverage binary search for faster performance. --- bindings/gumjs/gumquickmodule.c | 25 +++++++++++++++++++- bindings/gumjs/gumv8module.cpp | 31 ++++++++++++++++++++++++- bindings/gumjs/runtime/core.js | 23 ++++++++++++++++++ tests/gumjs/script.c | 41 +++++++++++++++++++++++++++++++++ 4 files changed, 118 insertions(+), 2 deletions(-) diff --git a/bindings/gumjs/gumquickmodule.c b/bindings/gumjs/gumquickmodule.c index a22e39424..d4845284f 100644 --- a/bindings/gumjs/gumquickmodule.c +++ b/bindings/gumjs/gumquickmodule.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020-2023 Ole André Vadla Ravnås + * Copyright (C) 2020-2024 Ole André Vadla Ravnås * * Licence: wxWindows Library Licence, Version 3.1 */ @@ -51,6 +51,7 @@ static gboolean gum_emit_dependency (const GumDependencyDetails * details, GumQuickMatchContext * mc); GUMJS_DECLARE_FUNCTION (gumjs_module_find_base_address) GUMJS_DECLARE_FUNCTION (gumjs_module_find_export_by_name) +GUMJS_DECLARE_FUNCTION (gumjs_module_find_symbol_by_name) GUMJS_DECLARE_CONSTRUCTOR (gumjs_module_map_construct) GUMJS_DECLARE_FINALIZER (gumjs_module_map_finalize) @@ -84,6 +85,7 @@ static const JSCFunctionListEntry gumjs_module_entries[] = gumjs_module_enumerate_dependencies), JS_CFUNC_DEF ("findBaseAddress", 0, gumjs_module_find_base_address), JS_CFUNC_DEF ("findExportByName", 0, gumjs_module_find_export_by_name), + JS_CFUNC_DEF ("findSymbolByName", 0, gumjs_module_find_symbol_by_name), }; static const JSClassDef gumjs_module_map_def = @@ -599,6 +601,27 @@ GUMJS_DEFINE_FUNCTION (gumjs_module_find_export_by_name) return _gum_quick_native_pointer_new (ctx, GSIZE_TO_POINTER (address), core); } +GUMJS_DEFINE_FUNCTION (gumjs_module_find_symbol_by_name) +{ + const gchar * module_name, * symbol_name; + GumQuickScope scope = GUM_QUICK_SCOPE_INIT (core); + GumAddress address; + + if (!_gum_quick_args_parse (args, "s?s", &module_name, &symbol_name)) + return JS_EXCEPTION; + + _gum_quick_scope_suspend (&scope); + + address = gum_module_find_symbol_by_name (module_name, symbol_name); + + _gum_quick_scope_resume (&scope); + + if (address == 0) + return JS_NULL; + + return _gum_quick_native_pointer_new (ctx, GSIZE_TO_POINTER (address), core); +} + static gboolean gum_quick_module_map_get (JSContext * ctx, JSValueConst val, diff --git a/bindings/gumjs/gumv8module.cpp b/bindings/gumjs/gumv8module.cpp index ed727632c..162c33d17 100644 --- a/bindings/gumjs/gumv8module.cpp +++ b/bindings/gumjs/gumv8module.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010-2023 Ole André Vadla Ravnås + * Copyright (C) 2010-2024 Ole André Vadla Ravnås * * Licence: wxWindows Library Licence, Version 3.1 */ @@ -85,6 +85,7 @@ static gboolean gum_emit_dependency (const GumDependencyDetails * details, GumV8MatchContext * mc); GUMJS_DECLARE_FUNCTION (gumjs_module_find_base_address) GUMJS_DECLARE_FUNCTION (gumjs_module_find_export_by_name) +GUMJS_DECLARE_FUNCTION (gumjs_module_find_symbol_by_name) GUMJS_DECLARE_CONSTRUCTOR (gumjs_module_map_construct) GUMJS_DECLARE_GETTER (gumjs_module_map_get_handle) @@ -117,6 +118,7 @@ static const GumV8Function gumjs_module_static_functions[] = { "_enumerateDependencies", gumjs_module_enumerate_dependencies }, { "findBaseAddress", gumjs_module_find_base_address }, { "findExportByName", gumjs_module_find_export_by_name }, + { "findSymbolByName", gumjs_module_find_symbol_by_name }, { NULL, NULL } }; @@ -630,6 +632,33 @@ GUMJS_DEFINE_FUNCTION (gumjs_module_find_export_by_name) g_free (symbol_name); } +GUMJS_DEFINE_FUNCTION (gumjs_module_find_symbol_by_name) +{ + gchar * module_name, * symbol_name; + if (!_gum_v8_args_parse (args, "s?s", &module_name, &symbol_name)) + return; + + GumAddress address; + { + ScriptUnlocker unlocker (core); + + address = gum_module_find_symbol_by_name (module_name, symbol_name); + } + + if (address != 0) + { + info.GetReturnValue ().Set ( + _gum_v8_native_pointer_new (GSIZE_TO_POINTER (address), core)); + } + else + { + info.GetReturnValue ().SetNull (); + } + + g_free (module_name); + g_free (symbol_name); +} + GUMJS_DEFINE_CONSTRUCTOR (gumjs_module_map_construct) { if (!info.IsConstructCall ()) diff --git a/bindings/gumjs/runtime/core.js b/bindings/gumjs/runtime/core.js index f1c1417e7..1b3041791 100644 --- a/bindings/gumjs/runtime/core.js +++ b/bindings/gumjs/runtime/core.js @@ -314,6 +314,17 @@ Object.defineProperties(Module, { return address; } }, + getSymbolByName: { + enumerable: true, + value: function (moduleName, symbolName) { + const address = Module.findSymbolByName(moduleName, symbolName); + if (address === null) { + const prefix = (moduleName !== null) ? (moduleName + ': ') : ''; + throw new Error(prefix + "unable to find symbol '" + symbolName + "'"); + } + return address; + } + }, }); Object.defineProperties(Module.prototype, { @@ -365,6 +376,18 @@ Object.defineProperties(Module.prototype, { return Module.getExportByName(this.path, exportName); } }, + findSymbolByName: { + enumerable: true, + value: function (symbolName) { + return Module.findSymbolByName(this.path, symbolName); + } + }, + getSymbolByName: { + enumerable: true, + value: function (symbolName) { + return Module.getSymbolByName(this.path, symbolName); + } + }, }); Object.defineProperties(ModuleMap.prototype, { diff --git a/tests/gumjs/script.c b/tests/gumjs/script.c index bb1854043..1658fcc6c 100644 --- a/tests/gumjs/script.c +++ b/tests/gumjs/script.c @@ -256,6 +256,7 @@ TESTLIST_BEGIN (script) TESTENTRY (module_dependencies_can_be_enumerated) TESTENTRY (module_base_address_can_be_found) TESTENTRY (module_export_can_be_found_by_name) + TESTENTRY (module_symbol_can_be_found_by_name) TESTENTRY (module_can_be_loaded) TESTENTRY (module_can_be_forcibly_initialized) TESTGROUP_END () @@ -5872,6 +5873,46 @@ TESTCASE (module_export_can_be_found_by_name) #endif } +TESTCASE (module_symbol_can_be_found_by_name) +{ + if (!g_test_slow ()) + { + g_print (" "); + return; + } + + COMPILE_AND_LOAD_SCRIPT ( + "const sysModuleName = '%s';" + + "const allSymbols = Process.getModuleByName(sysModuleName).enumerateSymbols();" + "const sysModuleExport = allSymbols.find(s => !s.address.isNull()).name;" + + "const badModuleName = 'nope_' + sysModuleName;" + "const badModuleExport = sysModuleExport + '_does_not_exist';" + + "const impl = Module.findSymbolByName(sysModuleName, sysModuleExport);" + "send(impl !== null);" + + "send(Module.findSymbolByName(badModuleName, badModuleExport) === null);" + + "try {" + "send(Module.getSymbolByName(sysModuleName, sysModuleExport)" + ".equals(impl));" + + "Module.getSymbolByName(badModuleName, badModuleExport);" + "send('should not get here');" + "} catch (e) {" + "send(/unable to find symbol/.test(e.message));" + "}", + SYSTEM_MODULE_NAME); + + EXPECT_SEND_MESSAGE_WITH ("true"); + EXPECT_SEND_MESSAGE_WITH ("true"); + + EXPECT_SEND_MESSAGE_WITH ("true"); + EXPECT_SEND_MESSAGE_WITH ("true"); +} + TESTCASE (module_can_be_loaded) { COMPILE_AND_LOAD_SCRIPT (