GumDukNativePointer * _gum_duk_require_native_pointer (duk_context * ctx, duk_idx_t index, GumDukCore * core) { duk_dup (ctx, index); duk_push_heapptr (ctx, core->native_pointer); if (!duk_instanceof (ctx, -2, -1)) _gum_duk_throw (ctx, "expected NativePointer"); duk_pop_2 (ctx); return _gum_duk_require_data (ctx, index); }
guint _gum_duk_require_index (duk_context * ctx, duk_idx_t index) { if (duk_is_number (ctx, index)) { guint value; if (_gum_duk_get_uint (ctx, index, &value)) { return value; } else { _gum_duk_throw (ctx, "invalid index"); return 0; /* unreachable */ } } else { const gchar * str; gchar * endptr; glong value; gboolean valid; str = duk_require_string (ctx, index); value = strtol (str, &endptr, 10); valid = *str != '\0' && *endptr == '\0' && value >= 0; if (!valid) _gum_duk_throw (ctx, "invalid index"); return value; } }
guint64 _gum_duk_require_uint64 (duk_context * ctx, duk_idx_t index, GumDukCore * core) { GumDukUInt64 * object; duk_dup (ctx, index); duk_push_heapptr (ctx, core->uint64); if (!duk_instanceof (ctx, -2, -1)) _gum_duk_throw (ctx, "expected UInt64"); duk_pop_2 (ctx); object = _gum_duk_require_data (ctx, index); return object->value; }
void _gum_duk_args_parse (const GumDukArgs * args, const gchar * format, ...) { duk_context * ctx = args->ctx; GumDukCore * core = args->core; va_list ap; duk_idx_t arg_index; const gchar * t; gboolean is_required; GSList * byte_arrays = NULL; const gchar * error_message = NULL; va_start (ap, format); arg_index = 0; is_required = TRUE; for (t = format; *t != '\0'; t++) { if (*t == '|') { is_required = FALSE; continue; } if (arg_index >= duk_get_top (ctx) || duk_is_undefined (ctx, arg_index)) { if (is_required) goto missing_argument; else break; } switch (*t) { case 'i': { if (!duk_is_number (ctx, arg_index)) goto expected_int; *va_arg (ap, gint *) = duk_require_int (ctx, arg_index); break; } case 'u': { guint u; if (!_gum_duk_get_uint (ctx, arg_index, &u)) goto expected_uint; *va_arg (ap, guint *) = (guint) u; break; } case 'q': { gint64 i; gboolean is_fuzzy; is_fuzzy = t[1] == '~'; if (is_fuzzy) t++; if (is_fuzzy) { if (!_gum_duk_parse_int64 (ctx, arg_index, core, &i)) goto expected_int; } else { if (!_gum_duk_get_int64 (ctx, arg_index, core, &i)) goto expected_int; } *va_arg (ap, gint64 *) = i; break; } case 'Q': { guint64 u; gboolean is_fuzzy; is_fuzzy = t[1] == '~'; if (is_fuzzy) t++; if (is_fuzzy) { if (!_gum_duk_parse_uint64 (ctx, arg_index, core, &u)) goto expected_uint; } else { if (!_gum_duk_get_uint64 (ctx, arg_index, core, &u)) goto expected_uint; } *va_arg (ap, guint64 *) = u; break; } case 'z': { gssize value; if (duk_is_number (ctx, arg_index)) { value = (gssize) duk_require_int (ctx, arg_index); } else { duk_push_heapptr (ctx, core->int64); duk_push_heapptr (ctx, core->uint64); if (duk_instanceof (ctx, arg_index, -2)) { GumDukInt64 * object; object = _gum_duk_require_data (ctx, arg_index); value = (gssize) object->value; } else if (duk_instanceof (ctx, arg_index, -1)) { GumDukUInt64 * object; object = _gum_duk_require_data (ctx, arg_index); value = (gssize) object->value; } else { goto expected_int; } duk_pop_2 (ctx); } *va_arg (ap, gssize *) = value; break; } case 'Z': { gsize value; if (duk_is_number (ctx, arg_index)) { duk_double_t number; number = duk_require_number (ctx, arg_index); if (number < 0) goto expected_uint; value = (gsize) number; } else { duk_push_heapptr (ctx, core->int64); duk_push_heapptr (ctx, core->uint64); if (duk_instanceof (ctx, arg_index, -1)) { GumDukUInt64 * object; object = _gum_duk_require_data (ctx, arg_index); value = (gsize) object->value; } else if (duk_instanceof (ctx, arg_index, -2)) { GumDukInt64 * object; object = _gum_duk_require_data (ctx, arg_index); if (object->value < 0) goto expected_uint; value = (gsize) object->value; } else { goto expected_uint; } duk_pop_2 (ctx); } *va_arg (ap, gsize *) = value; break; } case 'n': { if (!duk_is_number (ctx, arg_index)) goto expected_number; *va_arg (ap, gdouble *) = duk_require_number (ctx, arg_index); break; } case 'p': { gpointer ptr; gboolean is_fuzzy; is_fuzzy = t[1] == '~'; if (is_fuzzy) t++; if (is_fuzzy) { if (!_gum_duk_parse_pointer (ctx, arg_index, core, &ptr)) goto expected_pointer; } else { if (!_gum_duk_get_pointer (ctx, arg_index, core, &ptr)) goto expected_pointer; } *va_arg (ap, gpointer *) = ptr; break; } case 's': { const gchar * str; gboolean is_nullable; is_nullable = t[1] == '?'; if (is_nullable) t++; if (is_nullable && duk_is_null (ctx, arg_index)) str = NULL; else if ((str = duk_get_string (ctx, arg_index)) == NULL) goto expected_string; *va_arg (ap, const gchar **) = str; break; } case 'm': { GumPageProtection prot; if (!_gum_duk_parse_protection (ctx, arg_index, &prot)) goto expected_protection; *va_arg (ap, GumPageProtection *) = prot; break; } case 'V': { GumDukHeapPtr value; value = duk_get_heapptr (ctx, arg_index); if (value == NULL) goto expected_heap_pointer; *va_arg (ap, GumDukHeapPtr *) = value; break; } case 'O': { if (!duk_is_object (ctx, arg_index)) goto expected_object; *va_arg (ap, GumDukHeapPtr *) = duk_require_heapptr (ctx, arg_index); break; } case 'A': { GumDukHeapPtr array; gboolean is_nullable; is_nullable = t[1] == '?'; if (is_nullable) t++; if (duk_is_array (ctx, arg_index)) array = duk_require_heapptr (ctx, arg_index); else if (is_nullable && duk_is_null (ctx, arg_index)) array = NULL; else goto expected_array; *va_arg (ap, GumDukHeapPtr *) = array; break; } case 'F': { GumDukHeapPtr func; gboolean is_expecting_object, is_nullable; is_expecting_object = t[1] == '{'; if (is_expecting_object) t += 2; if (is_expecting_object) { const gchar * next, * end, * t_end; if (!duk_is_object (ctx, arg_index)) goto expected_callback_object; do { gchar name[64]; gsize length; next = strchr (t, ','); end = strchr (t, '}'); t_end = (next != NULL && next < end) ? next : end; length = t_end - t; strncpy (name, t, length); is_nullable = name[length - 1] == '?'; if (is_nullable) name[length - 1] = '\0'; else name[length] = '\0'; duk_get_prop_string (ctx, arg_index, name); if (duk_is_function (ctx, -1)) { func = duk_require_heapptr (ctx, -1); } else if (is_nullable && duk_is_null_or_undefined (ctx, -1)) { func = NULL; } else { duk_pop (ctx); goto expected_callback_value; } duk_pop (ctx); *va_arg (ap, GumDukHeapPtr *) = func; t = t_end + 1; } while (t_end != end); t--; } else { is_nullable = t[1] == '?'; if (is_nullable) t++; if (duk_is_function (ctx, arg_index)) func = duk_require_heapptr (ctx, arg_index); else if (is_nullable && duk_is_null (ctx, arg_index)) func = NULL; else goto expected_function; *va_arg (ap, GumDukHeapPtr *) = func; } break; } case 'B': { GBytes * bytes; gboolean is_nullable; is_nullable = t[1] == '?'; if (is_nullable) t++; if (is_nullable && duk_is_null (ctx, arg_index)) bytes = NULL; else if (!_gum_duk_parse_bytes (ctx, arg_index, &bytes)) goto expected_bytes; *va_arg (ap, GBytes **) = bytes; if (bytes != NULL) byte_arrays = g_slist_prepend (byte_arrays, bytes); break; } case 'C': { GumCpuContext * cpu_context; gboolean is_nullable; is_nullable = t[1] == '?'; if (is_nullable) t++; if (is_nullable && duk_is_null (ctx, arg_index)) cpu_context = NULL; else if ((cpu_context = _gum_duk_get_cpu_context (ctx, arg_index, core)) == NULL) goto expected_cpu_context; *va_arg (ap, GumCpuContext **) = cpu_context; break; } default: g_assert_not_reached (); } arg_index++; } va_end (ap); g_slist_free (byte_arrays); return; missing_argument: { error_message = "missing argument"; goto error; } expected_int: { error_message = "expected an integer"; goto error; } expected_uint: { error_message = "expected an unsigned integer"; goto error; } expected_number: { error_message = "expected a number"; goto error; } expected_pointer: { error_message = "expected a pointer"; goto error; } expected_string: { error_message = "expected a string"; goto error; } expected_protection: { error_message = "expected a string specifying memory protection"; goto error; } expected_heap_pointer: { error_message = "expected a heap-allocated object"; goto error; } expected_object: { error_message = "expected an object"; goto error; } expected_array: { error_message = "expected an array"; goto error; } expected_callback_object: { error_message = "expected an object containing callbacks"; goto error; } expected_callback_value: { error_message = "expected a callback value"; goto error; } expected_function: { error_message = "expected a function"; goto error; } expected_bytes: { error_message = "expected a buffer-like object"; goto error; } expected_cpu_context: { error_message = "expected a CpuContext object"; goto error; } error: { va_end (ap); g_slist_foreach (byte_arrays, (GFunc) g_bytes_unref, NULL); g_slist_free (byte_arrays); g_assert (error_message != NULL); _gum_duk_throw (ctx, error_message); } }
static void gum_duk_kernel_check_api_available (duk_context * ctx) { if (!gum_kernel_api_is_available ()) _gum_duk_throw (ctx, "Kernel API is not available on this system"); }
static int gum_duk_kernel_write (GumMemoryValueType type, const GumDukArgs * args) { duk_context * ctx = args->ctx; GumAddress address = 0; gssize s = 0; gsize u = 0; gint64 s64 = 0; guint64 u64 = 0; gdouble number = 0; gfloat number32 = 0; GBytes * bytes = NULL; const gchar * str = NULL; gunichar2 * str_utf16 = NULL; const guint8 * data = NULL; gsize str_length = 0; gsize length = 0; gboolean success; gum_duk_kernel_check_api_available (ctx); switch (type) { case GUM_MEMORY_VALUE_S8: case GUM_MEMORY_VALUE_S16: case GUM_MEMORY_VALUE_S32: _gum_duk_args_parse (args, "Qz", &address, &s); break; case GUM_MEMORY_VALUE_U8: case GUM_MEMORY_VALUE_U16: case GUM_MEMORY_VALUE_U32: _gum_duk_args_parse (args, "QZ", &address, &u); break; case GUM_MEMORY_VALUE_S64: case GUM_MEMORY_VALUE_LONG: _gum_duk_args_parse (args, "Qq", &address, &s64); break; case GUM_MEMORY_VALUE_U64: case GUM_MEMORY_VALUE_ULONG: _gum_duk_args_parse (args, "QQ", &address, &u64); break; case GUM_MEMORY_VALUE_FLOAT: case GUM_MEMORY_VALUE_DOUBLE: _gum_duk_args_parse (args, "Qn", &address, &number); number32 = (gfloat) number; break; case GUM_MEMORY_VALUE_BYTE_ARRAY: _gum_duk_args_parse (args, "QB", &address, &bytes); break; case GUM_MEMORY_VALUE_UTF8_STRING: case GUM_MEMORY_VALUE_UTF16_STRING: _gum_duk_args_parse (args, "Qs", &address, &str); str_length = g_utf8_strlen (str, -1); if (type == GUM_MEMORY_VALUE_UTF16_STRING) str_utf16 = g_utf8_to_utf16 (str, -1, NULL, NULL, NULL); break; default: g_assert_not_reached (); } switch (type) { case GUM_MEMORY_VALUE_S8: data = (guint8 *) &s; length = 1; break; case GUM_MEMORY_VALUE_U8: data = (guint8 *) &u; length = 1; break; case GUM_MEMORY_VALUE_S16: data = (guint8 *) &s; length = 2; break; case GUM_MEMORY_VALUE_U16: data = (guint8 *) &u; length = 2; break; case GUM_MEMORY_VALUE_S32: data = (guint8 *) &s; length = 4; break; case GUM_MEMORY_VALUE_U32: data = (guint8 *) &u; length = 4; break; case GUM_MEMORY_VALUE_LONG: case GUM_MEMORY_VALUE_S64: data = (guint8 *) &s64; length = 8; break; case GUM_MEMORY_VALUE_ULONG: case GUM_MEMORY_VALUE_U64: data = (guint8 *) &u64; length = 8; break; case GUM_MEMORY_VALUE_FLOAT: data = (guint8 *) &number32; length = 4; break; case GUM_MEMORY_VALUE_DOUBLE: data = (guint8 *) &number; length = 8; break; case GUM_MEMORY_VALUE_BYTE_ARRAY: data = g_bytes_get_data (bytes, &length); break; case GUM_MEMORY_VALUE_UTF8_STRING: data = (guint8 *) str; length = g_utf8_offset_to_pointer (str, str_length) - str + 1; break; case GUM_MEMORY_VALUE_UTF16_STRING: data = (guint8 *) str_utf16; length = (str_length + 1) * sizeof (gunichar2); break; default: g_assert_not_reached (); } if (length <= 0) _gum_duk_throw (ctx, "please provide a length > 0"); success = gum_kernel_write (address, data, length); g_bytes_unref (bytes); g_free (str_utf16); if (!success) { _gum_duk_throw (ctx, "access violation writing to 0x%" G_GINT64_MODIFIER "x", address); } return 0; }
static int gum_duk_kernel_read (GumMemoryValueType type, const GumDukArgs * args) { duk_context * ctx = args->ctx; GumDukCore * core = args->core; GumAddress address; gssize length = 0; gsize n_bytes_read; gum_duk_kernel_check_api_available (ctx); switch (type) { case GUM_MEMORY_VALUE_BYTE_ARRAY: case GUM_MEMORY_VALUE_C_STRING: case GUM_MEMORY_VALUE_UTF8_STRING: case GUM_MEMORY_VALUE_UTF16_STRING: _gum_duk_args_parse (args, "QZ", &address, &length); break; default: _gum_duk_args_parse (args, "Q", &address); break; } if (address == 0) { duk_push_null (ctx); return 1; } if (length == 0) { switch (type) { case GUM_MEMORY_VALUE_S8: case GUM_MEMORY_VALUE_U8: length = 1; break; case GUM_MEMORY_VALUE_S16: case GUM_MEMORY_VALUE_U16: length = 2; break; case GUM_MEMORY_VALUE_S32: case GUM_MEMORY_VALUE_U32: case GUM_MEMORY_VALUE_FLOAT: length = 4; break; case GUM_MEMORY_VALUE_S64: case GUM_MEMORY_VALUE_U64: case GUM_MEMORY_VALUE_LONG: case GUM_MEMORY_VALUE_ULONG: case GUM_MEMORY_VALUE_DOUBLE: length = 8; break; default: break; } } if (length > 0) { guint8 * data; gpointer buffer_data; data = gum_kernel_read (address, length, &n_bytes_read); if (data == NULL) { _gum_duk_throw (ctx, "access violation reading 0x%" G_GINT64_MODIFIER "x", address); } switch (type) { case GUM_MEMORY_VALUE_S8: duk_push_number (ctx, *((gint8 *) data)); break; case GUM_MEMORY_VALUE_U8: duk_push_number (ctx, *((guint8 *) data)); break; case GUM_MEMORY_VALUE_S16: duk_push_number (ctx, *((gint16 *) data)); break; case GUM_MEMORY_VALUE_U16: duk_push_number (ctx, *((guint16 *) data)); break; case GUM_MEMORY_VALUE_S32: duk_push_number (ctx, *((gint32 *) data)); break; case GUM_MEMORY_VALUE_U32: duk_push_number (ctx, *((guint32 *) data)); break; case GUM_MEMORY_VALUE_S64: _gum_duk_push_int64 (ctx, *((gint64 *) data), core); break; case GUM_MEMORY_VALUE_U64: _gum_duk_push_uint64 (ctx, *((guint64 *) data), core); break; case GUM_MEMORY_VALUE_LONG: _gum_duk_push_int64 (ctx, *((glong *) data), core); break; case GUM_MEMORY_VALUE_ULONG: _gum_duk_push_uint64 (ctx, *((gulong *) data), core); break; case GUM_MEMORY_VALUE_FLOAT: duk_push_number (ctx, *((gfloat *) data)); break; case GUM_MEMORY_VALUE_DOUBLE: duk_push_number (ctx, *((gdouble *) data)); break; case GUM_MEMORY_VALUE_BYTE_ARRAY: { buffer_data = duk_push_fixed_buffer (ctx, n_bytes_read); memcpy (buffer_data, data, n_bytes_read); duk_push_buffer_object (ctx, -1, 0, n_bytes_read, DUK_BUFOBJ_ARRAYBUFFER); duk_swap (ctx, -2, -1); duk_pop (ctx); break; } case GUM_MEMORY_VALUE_C_STRING: { gchar * str; str = g_utf8_make_valid ((gchar *) data, length); duk_push_string (ctx, str); g_free (str); break; } case GUM_MEMORY_VALUE_UTF8_STRING: { const gchar * end; gchar * slice; if (!g_utf8_validate ((gchar *) data, length, &end)) { _gum_duk_throw (ctx, "can't decode byte 0x%02x in position %u", (guint8) *end, (guint) (end - (gchar *) data)); } slice = g_strndup ((gchar *) data, length); duk_push_string (ctx, slice); g_free (slice); break; } case GUM_MEMORY_VALUE_UTF16_STRING: { gunichar2 * str_utf16; gchar * str_utf8; glong size; str_utf16 = (gunichar2 *) data; str_utf8 = g_utf16_to_utf8 (str_utf16, length, NULL, &size, NULL); if (str_utf8 == NULL) _gum_duk_throw (ctx, "invalid string"); duk_push_string (ctx, str_utf8); g_free (str_utf8); break; } default: g_assert_not_reached (); } g_free (data); } else if (type == GUM_MEMORY_VALUE_BYTE_ARRAY) { duk_push_fixed_buffer (ctx, 0); } else { _gum_duk_throw (ctx, "please provide a length > 0"); } return 1; }