diff --git a/bindings/gumjs/gumquickmemory.c b/bindings/gumjs/gumquickmemory.c index 6669b01b6..07246ec3d 100644 --- a/bindings/gumjs/gumquickmemory.c +++ b/bindings/gumjs/gumquickmemory.c @@ -157,6 +157,7 @@ GUMJS_DECLARE_FUNCTION (gumjs_memory_access_monitor_disable) static void gum_quick_memory_clear_monitor (GumQuickMemory * self, JSContext * ctx); +GUMJS_DECLARE_GETTER (gumjs_memory_access_details_get_thread_id) GUMJS_DECLARE_GETTER (gumjs_memory_access_details_get_operation) GUMJS_DECLARE_GETTER (gumjs_memory_access_details_get_from) GUMJS_DECLARE_GETTER (gumjs_memory_access_details_get_address) @@ -164,6 +165,7 @@ GUMJS_DECLARE_GETTER (gumjs_memory_access_details_get_range_index) GUMJS_DECLARE_GETTER (gumjs_memory_access_details_get_page_index) GUMJS_DECLARE_GETTER (gumjs_memory_access_details_get_pages_completed) GUMJS_DECLARE_GETTER (gumjs_memory_access_details_get_pages_total) +GUMJS_DECLARE_GETTER (gumjs_memory_access_details_get_context) static const JSCFunctionListEntry gumjs_memory_entries[] = { @@ -219,6 +221,7 @@ static const JSClassDef gumjs_memory_access_details_def = static const JSCFunctionListEntry gumjs_memory_access_details_entries[] = { + JS_CGETSET_DEF ("threadId", gumjs_memory_access_details_get_thread_id, NULL), JS_CGETSET_DEF ("operation", gumjs_memory_access_details_get_operation, NULL), JS_CGETSET_DEF ("from", gumjs_memory_access_details_get_from, NULL), JS_CGETSET_DEF ("address", gumjs_memory_access_details_get_address, NULL), @@ -230,6 +233,7 @@ static const JSCFunctionListEntry gumjs_memory_access_details_entries[] = gumjs_memory_access_details_get_pages_completed, NULL), JS_CGETSET_DEF ("pagesTotal", gumjs_memory_access_details_get_pages_total, NULL), + JS_CGETSET_DEF ("context", gumjs_memory_access_details_get_context, NULL), }; void @@ -1271,6 +1275,16 @@ gum_quick_memory_access_details_get (JSContext * ctx, return TRUE; } +GUMJS_DEFINE_GETTER (gumjs_memory_access_details_get_thread_id) +{ + const GumMemoryAccessDetails * details; + + if (!gum_quick_memory_access_details_get (ctx, this_val, core, &details)) + return JS_EXCEPTION; + + return JS_NewInt64 (ctx, details->thread_id); +} + GUMJS_DEFINE_GETTER (gumjs_memory_access_details_get_operation) { const GumMemoryAccessDetails * details; @@ -1340,3 +1354,14 @@ GUMJS_DEFINE_GETTER (gumjs_memory_access_details_get_pages_total) return JS_NewUint32 (ctx, details->pages_total); } + +GUMJS_DEFINE_GETTER (gumjs_memory_access_details_get_context) +{ + const GumMemoryAccessDetails * details; + + if (!gum_quick_memory_access_details_get (ctx, this_val, core, &details)) + return JS_EXCEPTION; + + return _gum_quick_cpu_context_new (ctx, details->context, + GUM_CPU_CONTEXT_READWRITE, core, NULL); +} \ No newline at end of file diff --git a/bindings/gumjs/gumv8memory.cpp b/bindings/gumjs/gumv8memory.cpp index c3a61fe68..e1665472c 100644 --- a/bindings/gumjs/gumv8memory.cpp +++ b/bindings/gumjs/gumv8memory.cpp @@ -1214,6 +1214,9 @@ gum_v8_memory_on_access (GumMemoryAccessMonitor * monitor, ScriptScope script_scope (core->script); auto d = Object::New (isolate); + _gum_v8_object_set (d, "threadId", Number::New (isolate, details->thread_id), + core); + _gum_v8_object_set_ascii (d, "operation", _gum_v8_memory_operation_to_string (details->operation), core); _gum_v8_object_set_pointer (d, "from", details->from, core); @@ -1224,6 +1227,9 @@ gum_v8_memory_on_access (GumMemoryAccessMonitor * monitor, _gum_v8_object_set_uint (d, "pagesCompleted", details->pages_completed, core); _gum_v8_object_set_uint (d, "pagesTotal", details->pages_total, core); + auto cpu_context = _gum_v8_cpu_context_new_mutable (details->context, core); + _gum_v8_object_set (d, "context", cpu_context, core); + auto on_access (Local::New (isolate, *self->on_access)); Local argv[] = { d }; auto result = on_access->Call (isolate->GetCurrentContext (), diff --git a/gum/backend-posix/gummemoryaccessmonitor-posix.c b/gum/backend-posix/gummemoryaccessmonitor-posix.c index 70f4d80ef..00064c50d 100644 --- a/gum/backend-posix/gummemoryaccessmonitor-posix.c +++ b/gum/backend-posix/gummemoryaccessmonitor-posix.c @@ -377,9 +377,11 @@ gum_memory_access_monitor_on_exception (GumExceptionDetails * details, if (details->type != GUM_EXCEPTION_ACCESS_VIOLATION) return FALSE; + d.thread_id = details->thread_id; d.operation = details->memory.operation; d.from = details->address; d.address = details->memory.address; + d.context = &details->context; for (i = 0; i != self->pages->len; i++) { diff --git a/gum/backend-windows/gummemoryaccessmonitor-windows.c b/gum/backend-windows/gummemoryaccessmonitor-windows.c index 1324af682..320a34b96 100644 --- a/gum/backend-windows/gummemoryaccessmonitor-windows.c +++ b/gum/backend-windows/gummemoryaccessmonitor-windows.c @@ -433,9 +433,11 @@ gum_memory_access_monitor_on_exception (GumExceptionDetails * details, self = GUM_MEMORY_ACCESS_MONITOR (user_data); + d.thread_id = details->thread_id; d.operation = details->memory.operation; d.from = details->address; d.address = details->memory.address; + d.context = &details->context; for (i = 0; i != self->num_pages; i++) { diff --git a/gum/gummemoryaccessmonitor.h b/gum/gummemoryaccessmonitor.h index 1695bcdc1..b6f91f7dd 100644 --- a/gum/gummemoryaccessmonitor.h +++ b/gum/gummemoryaccessmonitor.h @@ -8,6 +8,7 @@ #define __GUM_MEMORY_ACCESS_MONITOR_H__ #include +#include G_BEGIN_DECLS @@ -22,6 +23,7 @@ typedef void (* GumMemoryAccessNotify) (GumMemoryAccessMonitor * monitor, struct _GumMemoryAccessDetails { + GumThreadId thread_id; GumMemoryOperation operation; gpointer from; gpointer address; @@ -30,6 +32,8 @@ struct _GumMemoryAccessDetails guint page_index; guint pages_completed; guint pages_total; + + GumCpuContext * context; }; GUM_API GumMemoryAccessMonitor * gum_memory_access_monitor_new ( diff --git a/tests/core/memoryaccessmonitor.c b/tests/core/memoryaccessmonitor.c index 2d6ca7b24..c8a500bf2 100644 --- a/tests/core/memoryaccessmonitor.c +++ b/tests/core/memoryaccessmonitor.c @@ -19,16 +19,21 @@ TESTCASE (notify_on_read_access) volatile guint8 * bytes = GSIZE_TO_POINTER (fixture->range.base_address); guint8 val; volatile GumMemoryAccessDetails * d = &fixture->last_details; + GumThreadId thread_id; bytes[fixture->offset_in_first_page] = 0x13; bytes[fixture->offset_in_second_page] = 0x37; ENABLE_MONITOR (); - val = bytes[fixture->offset_in_first_page]; + + thread_id = gum_process_get_current_thread_id (); + g_assert_cmpuint (d->thread_id, ==, thread_id); + g_assert_cmpuint (fixture->number_of_notifies, ==, 1); g_assert_cmpint (d->operation, ==, GUM_MEMOP_READ); g_assert_true (d->from != NULL && d->from != d->address); + g_assert_true (d->address == bytes + fixture->offset_in_first_page); g_assert_cmpuint (val, ==, 0x13); @@ -46,6 +51,22 @@ TESTCASE (notify_on_read_access) val = bytes[fixture->offset_in_second_page]; g_assert_cmpuint (fixture->number_of_notifies, ==, 2); g_assert_cmpuint (val, ==, 0x37); + + /* + * Perform some basic architecture agnostic checks on the Cpu Context, the + * program counter should match the `from` field and the stack pointer + * should be non-zero. + */ +#if defined (HAVE_I386) && GLIB_SIZEOF_VOID_P == 4 + g_assert_true (d->from == GSIZE_TO_POINTER (d->context->eip)); + g_assert_true (0 != GSIZE_TO_POINTER (d->context->esp)); +#elif defined (HAVE_I386) && GLIB_SIZEOF_VOID_P == 8 + g_assert_true (d->from == GSIZE_TO_POINTER (d->context->rip)); + g_assert_true (0 != GSIZE_TO_POINTER (d->context->rsp)); +#else + g_assert_true (d->from == GSIZE_TO_POINTER (d->context->pc)); + g_assert_true (0 != GSIZE_TO_POINTER (d->context->sp)); +#endif } TESTCASE (notify_on_write_access) @@ -53,12 +74,17 @@ TESTCASE (notify_on_write_access) volatile guint8 * bytes = GSIZE_TO_POINTER (fixture->range.base_address); guint8 val; volatile GumMemoryAccessDetails * d = &fixture->last_details; + GumThreadId thread_id; bytes[fixture->offset_in_first_page] = 0x13; ENABLE_MONITOR (); bytes[fixture->offset_in_first_page] = 0x14; + + thread_id = gum_process_get_current_thread_id (); + g_assert_cmpuint (d->thread_id, ==, thread_id); + g_assert_cmpuint (fixture->number_of_notifies, ==, 1); g_assert_cmpint (d->operation, ==, GUM_MEMOP_WRITE); g_assert_true (d->from != NULL && d->from != d->address); @@ -67,21 +93,58 @@ TESTCASE (notify_on_write_access) val = bytes[fixture->offset_in_first_page]; g_assert_cmpuint (fixture->number_of_notifies, ==, 1); g_assert_cmpuint (val, ==, 0x14); + + /* + * Perform some basic architecture agnostic checks on the Cpu Context, the + * program counter should match the `from` field and the stack pointer + * should be non-zero. + */ +#if defined (HAVE_I386) && GLIB_SIZEOF_VOID_P == 4 + g_assert_true (d->from == GSIZE_TO_POINTER (d->context->eip)); + g_assert_true (0 != GSIZE_TO_POINTER (d->context->esp)); +#elif defined (HAVE_I386) && GLIB_SIZEOF_VOID_P == 8 + g_assert_true (d->from == GSIZE_TO_POINTER (d->context->rip)); + g_assert_true (0 != GSIZE_TO_POINTER (d->context->rsp)); +#else + g_assert_true (d->from == GSIZE_TO_POINTER (d->context->pc)); + g_assert_true (0 != GSIZE_TO_POINTER (d->context->sp)); +#endif } TESTCASE (notify_on_execute_access) { volatile GumMemoryAccessDetails * d = &fixture->last_details; + GumThreadId thread_id; ENABLE_MONITOR (); fixture->nop_function_in_third_page (); + + thread_id = gum_process_get_current_thread_id (); + g_assert_cmpuint (d->thread_id, ==, thread_id); + g_assert_cmpuint (fixture->number_of_notifies, ==, 1); g_assert_cmpint (d->operation, ==, GUM_MEMOP_EXECUTE); g_assert_true (d->from != NULL && d->from == d->address); fixture->nop_function_in_third_page (); g_assert_cmpuint (fixture->number_of_notifies, ==, 1); + + /* + * Perform some basic architecture agnostic checks on the Cpu Context, the + * program counter should match the `from` field and the stack pointer + * should be non-zero. + */ +#if defined (HAVE_I386) && GLIB_SIZEOF_VOID_P == 4 + g_assert_true (d->from == GSIZE_TO_POINTER (d->context->eip)); + g_assert_true (0 != GSIZE_TO_POINTER (d->context->esp)); +#elif defined (HAVE_I386) && GLIB_SIZEOF_VOID_P == 8 + g_assert_true (d->from == GSIZE_TO_POINTER (d->context->rip)); + g_assert_true (0 != GSIZE_TO_POINTER (d->context->rsp)); +#else + g_assert_true (d->from == GSIZE_TO_POINTER (d->context->pc)); + g_assert_true (0 != GSIZE_TO_POINTER (d->context->sp)); +#endif } TESTCASE (notify_should_include_progress) diff --git a/tests/gumjs/script.c b/tests/gumjs/script.c index d0a13ce16..b95b4600e 100644 --- a/tests/gumjs/script.c +++ b/tests/gumjs/script.c @@ -181,6 +181,9 @@ TESTLIST_BEGIN (script) TESTENTRY (memory_scan_handles_bad_arguments) TESTENTRY (memory_access_can_be_monitored) TESTENTRY (memory_access_can_be_monitored_one_range) + TESTENTRY (memory_access_monitor_provides_cpu_context) + TESTENTRY (memory_access_monitor_cpu_context_can_be_modified) + TESTENTRY (memory_access_monitor_provides_thread_id) TESTGROUP_END () TESTENTRY (frida_version_is_available) @@ -616,6 +619,7 @@ static int target_function_int_replacement (int arg); static void measure_target_function_int_overhead (void); static int compare_measurements (gconstpointer element_a, gconstpointer element_b); +static void put_return_instruction (gpointer mem, gpointer user_data); static gboolean check_exception_handling_testable (void); @@ -8166,6 +8170,132 @@ TESTCASE (memory_access_can_be_monitored_one_range) gum_free_pages ((gpointer) a); } +TESTCASE (memory_access_monitor_provides_cpu_context) +{ + volatile guint8 * a; + guint page_size; + + if (!check_exception_handling_testable ()) + return; + + a = gum_alloc_n_pages (1, GUM_PAGE_RW); + page_size = gum_query_page_size (); + + COMPILE_AND_LOAD_SCRIPT ( + "MemoryAccessMonitor.enable({ base: " GUM_PTR_CONST ", size: %u }, {" + "onAccess(details) {" + "send([details.context.pc.equals(details.from)," + "details.context.sp.equals(ptr(0))]);" + "}" + "});", + a, page_size); + EXPECT_NO_MESSAGES (); + + a[0] = 1; + EXPECT_SEND_MESSAGE_WITH ("[true,false]"); + + gum_free_pages ((gpointer) a); +} + +TESTCASE (memory_access_monitor_cpu_context_can_be_modified) +{ + volatile guint8 * a; + guint page_size; + gsize (*func)(void); + const gsize expected_ret = 0xdeadface; + gsize ret; + + if (!check_exception_handling_testable ()) + return; + a = gum_alloc_n_pages (1, GUM_PAGE_RX); + page_size = gum_query_page_size (); + + gum_memory_patch_code ((gpointer) a, 4, put_return_instruction, NULL); + func = (gsize (*)(void)) (gum_sign_code_pointer ((gpointer) a)); + + COMPILE_AND_LOAD_SCRIPT ( + "MemoryAccessMonitor.enable({ base: " GUM_PTR_CONST ", size: %u }, {" + "onAccess(details) {" + /* + * Set the value of the register used to hold the return value. + * Note that this has to be correct for all calling conventions + * (e.g. different operating systems) on all of their supported + * architectures. + */ +#if defined (HAVE_I386) && GLIB_SIZEOF_VOID_P == 4 + "details.context.eax = " GUM_PTR_CONST ";" +#elif defined (HAVE_I386) && GLIB_SIZEOF_VOID_P == 8 + "details.context.rax = " GUM_PTR_CONST ";" +#elif defined (HAVE_ARM) + "details.context.r0 = " GUM_PTR_CONST ";" +#elif defined (HAVE_ARM64) + "details.context.x0 = " GUM_PTR_CONST ";" +#elif defined (HAVE_MIPS) + "details.context.ra = " GUM_PTR_CONST ";" +#endif + "send(1337);" + "}" + "});", + a, page_size, expected_ret); + EXPECT_NO_MESSAGES (); + + ret = func (); + EXPECT_SEND_MESSAGE_WITH ("1337"); + + g_assert_true (ret == expected_ret); + + gum_free_pages ((gpointer) a); +} + +static void +put_return_instruction (gpointer mem, + gpointer user_data) +{ +#if defined (HAVE_I386) + *((guint8 *) mem) = 0xc3; +#elif defined (HAVE_ARM) +# if G_BYTE_ORDER == G_LITTLE_ENDIAN + /* mov pc, lr */ + *((guint32 *) mem) = 0xe1a0f00e; +# else + *((guint32 *) mem) = 0x0ef0a0e1; +# endif +#elif defined (HAVE_ARM64) + *((guint32 *) mem) = 0xd65f03c0; +#else +# error Unsupported architecture +#endif +} + +TESTCASE (memory_access_monitor_provides_thread_id) +{ + volatile guint8 * a; + guint page_size; + GumThreadId thread_id; + + if (!check_exception_handling_testable ()) + return; + + a = gum_alloc_n_pages (1, GUM_PAGE_RW); + page_size = gum_query_page_size (); + + COMPILE_AND_LOAD_SCRIPT ( + "MemoryAccessMonitor.enable({ base: " GUM_PTR_CONST ", size: %u }, {" + "onAccess(details) {" + "send([details.threadId]);" + "}" + "});", + a, page_size); + EXPECT_NO_MESSAGES (); + + a[0] = 1; + + thread_id = gum_process_get_current_thread_id (); + EXPECT_SEND_MESSAGE_WITH ("[%" G_GSIZE_MODIFIER "u]", thread_id); + + gum_free_pages ((gpointer) a); +} + TESTCASE (pointer_can_be_read) { gpointer val = GSIZE_TO_POINTER (0x1337000);