UNPKG

frida-java-bridge

Version:
1,391 lines (1,138 loc) 36.4 kB
import { withRunnableArtThread, getArtClassSpec, getArtMethodSpec, getArtFieldSpec, getApi } from './android.js'; const code = `#include <json-glib/json-glib.h> #include <string.h> #define kAccStatic 0x0008 #define kAccConstructor 0x00010000 typedef struct _Model Model; typedef struct _EnumerateMethodsContext EnumerateMethodsContext; typedef struct _JavaApi JavaApi; typedef struct _JavaClassApi JavaClassApi; typedef struct _JavaMethodApi JavaMethodApi; typedef struct _JavaFieldApi JavaFieldApi; typedef struct _JNIEnv JNIEnv; typedef guint8 jboolean; typedef gint32 jint; typedef jint jsize; typedef gpointer jobject; typedef jobject jclass; typedef jobject jstring; typedef jobject jarray; typedef jarray jobjectArray; typedef gpointer jfieldID; typedef gpointer jmethodID; typedef struct _jvmtiEnv jvmtiEnv; typedef enum { JVMTI_ERROR_NONE = 0 } jvmtiError; typedef struct _ArtApi ArtApi; typedef guint32 ArtHeapReference; typedef struct _ArtObject ArtObject; typedef struct _ArtClass ArtClass; typedef struct _ArtClassLinker ArtClassLinker; typedef struct _ArtClassVisitor ArtClassVisitor; typedef struct _ArtClassVisitorVTable ArtClassVisitorVTable; typedef struct _ArtMethod ArtMethod; typedef struct _ArtString ArtString; typedef union _StdString StdString; typedef struct _StdStringShort StdStringShort; typedef struct _StdStringLong StdStringLong; typedef void (* ArtVisitClassesFunc) (ArtClassLinker * linker, ArtClassVisitor * visitor); typedef const char * (* ArtGetClassDescriptorFunc) (ArtClass * klass, StdString * storage); typedef void (* ArtPrettyMethodFunc) (StdString * result, ArtMethod * method, jboolean with_signature); struct _Model { GHashTable * members; }; struct _EnumerateMethodsContext { GPatternSpec * class_query; GPatternSpec * method_query; jboolean include_signature; jboolean ignore_case; jboolean skip_system_classes; GHashTable * groups; }; struct _JavaClassApi { jmethodID get_declared_methods; jmethodID get_declared_fields; }; struct _JavaMethodApi { jmethodID get_name; jmethodID get_modifiers; }; struct _JavaFieldApi { jmethodID get_name; jmethodID get_modifiers; }; struct _JavaApi { JavaClassApi clazz; JavaMethodApi method; JavaFieldApi field; }; struct _JNIEnv { gpointer * functions; }; struct _jvmtiEnv { gpointer * functions; }; struct _ArtApi { gboolean available; guint class_offset_ifields; guint class_offset_methods; guint class_offset_sfields; guint class_offset_copied_methods_offset; guint method_size; guint method_offset_access_flags; guint field_size; guint field_offset_access_flags; guint alignment_padding; ArtClassLinker * linker; ArtVisitClassesFunc visit_classes; ArtGetClassDescriptorFunc get_class_descriptor; ArtPrettyMethodFunc pretty_method; void (* free) (gpointer mem); }; struct _ArtObject { ArtHeapReference klass; ArtHeapReference monitor; }; struct _ArtClass { ArtObject parent; ArtHeapReference class_loader; }; struct _ArtClassVisitor { ArtClassVisitorVTable * vtable; gpointer user_data; }; struct _ArtClassVisitorVTable { void (* reserved1) (ArtClassVisitor * self); void (* reserved2) (ArtClassVisitor * self); jboolean (* visit) (ArtClassVisitor * self, ArtClass * klass); }; struct _ArtString { ArtObject parent; gint32 count; guint32 hash_code; union { guint16 value[0]; guint8 value_compressed[0]; }; }; struct _StdStringShort { guint8 size; gchar data[(3 * sizeof (gpointer)) - sizeof (guint8)]; }; struct _StdStringLong { gsize capacity; gsize size; gchar * data; }; union _StdString { StdStringShort s; StdStringLong l; }; static void model_add_method (Model * self, const gchar * name, jmethodID id, jint modifiers); static void model_add_field (Model * self, const gchar * name, jfieldID id, jint modifiers); static void model_free (Model * model); static jboolean collect_matching_class_methods (ArtClassVisitor * self, ArtClass * klass); static gchar * finalize_method_groups_to_json (GHashTable * groups); static GPatternSpec * make_pattern_spec (const gchar * pattern, jboolean ignore_case); static gchar * class_name_from_signature (const gchar * signature); static gchar * format_method_signature (const gchar * name, const gchar * signature); static void append_type (GString * output, const gchar ** type); static gpointer read_art_array (gpointer object_base, guint field_offset, guint length_size, guint * length); static void std_string_destroy (StdString * str); static gchar * std_string_c_str (StdString * self); extern GMutex lock; extern GArray * models; extern JavaApi java_api; extern ArtApi art_api; void init (void) { g_mutex_init (&lock); models = g_array_new (FALSE, FALSE, sizeof (Model *)); } void finalize (void) { guint n, i; n = models->len; for (i = 0; i != n; i++) { Model * model = g_array_index (models, Model *, i); model_free (model); } g_array_unref (models); g_mutex_clear (&lock); } Model * model_new (jclass class_handle, gpointer class_object, JNIEnv * env) { Model * model; GHashTable * members; gpointer * funcs = env->functions; jmethodID (* from_reflected_method) (JNIEnv *, jobject) = funcs[7]; jfieldID (* from_reflected_field) (JNIEnv *, jobject) = funcs[8]; jobject (* to_reflected_method) (JNIEnv *, jclass, jmethodID, jboolean) = funcs[9]; jobject (* to_reflected_field) (JNIEnv *, jclass, jfieldID, jboolean) = funcs[12]; void (* delete_local_ref) (JNIEnv *, jobject) = funcs[23]; jobject (* call_object_method) (JNIEnv *, jobject, jmethodID, ...) = funcs[34]; jint (* call_int_method) (JNIEnv *, jobject, jmethodID, ...) = funcs[49]; const char * (* get_string_utf_chars) (JNIEnv *, jstring, jboolean *) = funcs[169]; void (* release_string_utf_chars) (JNIEnv *, jstring, const char *) = funcs[170]; jsize (* get_array_length) (JNIEnv *, jarray) = funcs[171]; jobject (* get_object_array_element) (JNIEnv *, jobjectArray, jsize) = funcs[173]; jsize n, i; model = g_new (Model, 1); members = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); model->members = members; if (art_api.available) { gpointer elements; guint n, i; const guint field_arrays[] = { art_api.class_offset_ifields, art_api.class_offset_sfields }; guint field_array_cursor; gboolean merged_fields = art_api.class_offset_sfields == 0; elements = read_art_array (class_object, art_api.class_offset_methods, sizeof (gsize), NULL); n = *(guint16 *) (class_object + art_api.class_offset_copied_methods_offset); for (i = 0; i != n; i++) { jmethodID id; guint32 access_flags; jboolean is_static; jobject method, name; const char * name_str; jint modifiers; id = elements + (i * art_api.method_size); access_flags = *(guint32 *) (id + art_api.method_offset_access_flags); if ((access_flags & kAccConstructor) != 0) continue; is_static = (access_flags & kAccStatic) != 0; method = to_reflected_method (env, class_handle, id, is_static); name = call_object_method (env, method, java_api.method.get_name); name_str = get_string_utf_chars (env, name, NULL); modifiers = access_flags & 0xffff; model_add_method (model, name_str, id, modifiers); release_string_utf_chars (env, name, name_str); delete_local_ref (env, name); delete_local_ref (env, method); } for (field_array_cursor = 0; field_array_cursor != G_N_ELEMENTS (field_arrays); field_array_cursor++) { jboolean is_static; if (field_arrays[field_array_cursor] == 0) continue; if (!merged_fields) is_static = field_array_cursor == 1; elements = read_art_array (class_object, field_arrays[field_array_cursor], sizeof (guint32), &n); for (i = 0; i != n; i++) { jfieldID id; guint32 access_flags; jobject field, name; const char * name_str; jint modifiers; id = elements + (i * art_api.field_size); access_flags = *(guint32 *) (id + art_api.field_offset_access_flags); if (merged_fields) is_static = (access_flags & kAccStatic) != 0; field = to_reflected_field (env, class_handle, id, is_static); name = call_object_method (env, field, java_api.field.get_name); name_str = get_string_utf_chars (env, name, NULL); modifiers = access_flags & 0xffff; model_add_field (model, name_str, id, modifiers); release_string_utf_chars (env, name, name_str); delete_local_ref (env, name); delete_local_ref (env, field); } } } else { jobject elements; elements = call_object_method (env, class_handle, java_api.clazz.get_declared_methods); n = get_array_length (env, elements); for (i = 0; i != n; i++) { jobject method, name; const char * name_str; jmethodID id; jint modifiers; method = get_object_array_element (env, elements, i); name = call_object_method (env, method, java_api.method.get_name); name_str = get_string_utf_chars (env, name, NULL); id = from_reflected_method (env, method); modifiers = call_int_method (env, method, java_api.method.get_modifiers); model_add_method (model, name_str, id, modifiers); release_string_utf_chars (env, name, name_str); delete_local_ref (env, name); delete_local_ref (env, method); } delete_local_ref (env, elements); elements = call_object_method (env, class_handle, java_api.clazz.get_declared_fields); n = get_array_length (env, elements); for (i = 0; i != n; i++) { jobject field, name; const char * name_str; jfieldID id; jint modifiers; field = get_object_array_element (env, elements, i); name = call_object_method (env, field, java_api.field.get_name); name_str = get_string_utf_chars (env, name, NULL); id = from_reflected_field (env, field); modifiers = call_int_method (env, field, java_api.field.get_modifiers); model_add_field (model, name_str, id, modifiers); release_string_utf_chars (env, name, name_str); delete_local_ref (env, name); delete_local_ref (env, field); } delete_local_ref (env, elements); } g_mutex_lock (&lock); g_array_append_val (models, model); g_mutex_unlock (&lock); return model; } static void model_add_method (Model * self, const gchar * name, jmethodID id, jint modifiers) { GHashTable * members = self->members; gchar * key, type; const gchar * value; if (name[0] == '$') key = g_strdup_printf ("_%s", name); else key = g_strdup (name); type = (modifiers & kAccStatic) != 0 ? 's' : 'i'; value = g_hash_table_lookup (members, key); if (value == NULL) g_hash_table_insert (members, key, g_strdup_printf ("m:%c0x%zx", type, id)); else g_hash_table_insert (members, key, g_strdup_printf ("%s:%c0x%zx", value, type, id)); } static void model_add_field (Model * self, const gchar * name, jfieldID id, jint modifiers) { GHashTable * members = self->members; gchar * key, type; if (name[0] == '$') key = g_strdup_printf ("_%s", name); else key = g_strdup (name); while (g_hash_table_contains (members, key)) { gchar * new_key = g_strdup_printf ("_%s", key); g_free (key); key = new_key; } type = (modifiers & kAccStatic) != 0 ? 's' : 'i'; g_hash_table_insert (members, key, g_strdup_printf ("f:%c0x%zx", type, id)); } static void model_free (Model * model) { g_hash_table_unref (model->members); g_free (model); } gboolean model_has (Model * self, const gchar * member) { return g_hash_table_contains (self->members, member); } const gchar * model_find (Model * self, const gchar * member) { return g_hash_table_lookup (self->members, member); } gchar * model_list (Model * self) { GString * result; GHashTableIter iter; guint i; const gchar * name; result = g_string_sized_new (128); g_string_append_c (result, '['); g_hash_table_iter_init (&iter, self->members); for (i = 0; g_hash_table_iter_next (&iter, (gpointer *) &name, NULL); i++) { if (i > 0) g_string_append_c (result, ','); g_string_append_c (result, '"'); g_string_append (result, name); g_string_append_c (result, '"'); } g_string_append_c (result, ']'); return g_string_free (result, FALSE); } gchar * enumerate_methods_art (const gchar * class_query, const gchar * method_query, jboolean include_signature, jboolean ignore_case, jboolean skip_system_classes) { gchar * result; EnumerateMethodsContext ctx; ArtClassVisitor visitor; ArtClassVisitorVTable visitor_vtable = { NULL, }; ctx.class_query = make_pattern_spec (class_query, ignore_case); ctx.method_query = make_pattern_spec (method_query, ignore_case); ctx.include_signature = include_signature; ctx.ignore_case = ignore_case; ctx.skip_system_classes = skip_system_classes; ctx.groups = g_hash_table_new_full (NULL, NULL, NULL, NULL); visitor.vtable = &visitor_vtable; visitor.user_data = &ctx; visitor_vtable.visit = collect_matching_class_methods; art_api.visit_classes (art_api.linker, &visitor); result = finalize_method_groups_to_json (ctx.groups); g_hash_table_unref (ctx.groups); g_pattern_spec_free (ctx.method_query); g_pattern_spec_free (ctx.class_query); return result; } static jboolean collect_matching_class_methods (ArtClassVisitor * self, ArtClass * klass) { EnumerateMethodsContext * ctx = self->user_data; const char * descriptor; StdString descriptor_storage = { 0, }; gchar * class_name = NULL; gchar * class_name_copy = NULL; const gchar * normalized_class_name; JsonBuilder * group; size_t class_name_length; GHashTable * seen_method_names; gpointer elements; guint n, i; if (ctx->skip_system_classes && klass->class_loader == 0) goto skip_class; descriptor = art_api.get_class_descriptor (klass, &descriptor_storage); if (descriptor[0] != 'L') goto skip_class; class_name = class_name_from_signature (descriptor); if (ctx->ignore_case) { class_name_copy = g_utf8_strdown (class_name, -1); normalized_class_name = class_name_copy; } else { normalized_class_name = class_name; } if (!g_pattern_match_string (ctx->class_query, normalized_class_name)) goto skip_class; group = NULL; class_name_length = strlen (class_name); seen_method_names = ctx->include_signature ? NULL : g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); elements = read_art_array (klass, art_api.class_offset_methods, sizeof (gsize), NULL); n = *(guint16 *) ((gpointer) klass + art_api.class_offset_copied_methods_offset); for (i = 0; i != n; i++) { ArtMethod * method; guint32 access_flags; jboolean is_constructor; StdString method_name = { 0, }; const gchar * bare_method_name; gchar * bare_method_name_copy = NULL; const gchar * normalized_method_name; gchar * normalized_method_name_copy = NULL; method = elements + (i * art_api.method_size); access_flags = *(guint32 *) ((gpointer) method + art_api.method_offset_access_flags); is_constructor = (access_flags & kAccConstructor) != 0; art_api.pretty_method (&method_name, method, ctx->include_signature); bare_method_name = std_string_c_str (&method_name); if (ctx->include_signature) { const gchar * return_type_end, * name_begin; GString * name; return_type_end = strchr (bare_method_name, ' '); name_begin = return_type_end + 1 + class_name_length + 1; if (is_constructor && g_str_has_prefix (name_begin, "<clinit>")) goto skip_method; name = g_string_sized_new (64); if (is_constructor) { g_string_append (name, "$init"); g_string_append (name, strchr (name_begin, '>') + 1); } else { g_string_append (name, name_begin); } g_string_append (name, ": "); g_string_append_len (name, bare_method_name, return_type_end - bare_method_name); bare_method_name_copy = g_string_free (name, FALSE); bare_method_name = bare_method_name_copy; } else { const gchar * name_begin; name_begin = bare_method_name + class_name_length + 1; if (is_constructor && strcmp (name_begin, "<clinit>") == 0) goto skip_method; if (is_constructor) bare_method_name = "$init"; else bare_method_name += class_name_length + 1; } if (seen_method_names != NULL && g_hash_table_contains (seen_method_names, bare_method_name)) goto skip_method; if (ctx->ignore_case) { normalized_method_name_copy = g_utf8_strdown (bare_method_name, -1); normalized_method_name = normalized_method_name_copy; } else { normalized_method_name = bare_method_name; } if (!g_pattern_match_string (ctx->method_query, normalized_method_name)) goto skip_method; if (group == NULL) { group = g_hash_table_lookup (ctx->groups, GUINT_TO_POINTER (klass->class_loader)); if (group == NULL) { group = json_builder_new_immutable (); g_hash_table_insert (ctx->groups, GUINT_TO_POINTER (klass->class_loader), group); json_builder_begin_object (group); json_builder_set_member_name (group, "loader"); json_builder_add_int_value (group, klass->class_loader); json_builder_set_member_name (group, "classes"); json_builder_begin_array (group); } json_builder_begin_object (group); json_builder_set_member_name (group, "name"); json_builder_add_string_value (group, class_name); json_builder_set_member_name (group, "methods"); json_builder_begin_array (group); } json_builder_add_string_value (group, bare_method_name); if (seen_method_names != NULL) g_hash_table_add (seen_method_names, g_strdup (bare_method_name)); skip_method: g_free (normalized_method_name_copy); g_free (bare_method_name_copy); std_string_destroy (&method_name); } if (seen_method_names != NULL) g_hash_table_unref (seen_method_names); if (group == NULL) goto skip_class; json_builder_end_array (group); json_builder_end_object (group); skip_class: g_free (class_name_copy); g_free (class_name); std_string_destroy (&descriptor_storage); return TRUE; } gchar * enumerate_methods_jvm (const gchar * class_query, const gchar * method_query, jboolean include_signature, jboolean ignore_case, jboolean skip_system_classes, JNIEnv * env, jvmtiEnv * jvmti) { gchar * result; GPatternSpec * class_pattern, * method_pattern; GHashTable * groups; gpointer * ef = env->functions; jobject (* new_global_ref) (JNIEnv *, jobject) = ef[21]; void (* delete_local_ref) (JNIEnv *, jobject) = ef[23]; jboolean (* is_same_object) (JNIEnv *, jobject, jobject) = ef[24]; gpointer * jf = jvmti->functions - 1; jvmtiError (* deallocate) (jvmtiEnv *, void * mem) = jf[47]; jvmtiError (* get_class_signature) (jvmtiEnv *, jclass, char **, char **) = jf[48]; jvmtiError (* get_class_methods) (jvmtiEnv *, jclass, jint *, jmethodID **) = jf[52]; jvmtiError (* get_class_loader) (jvmtiEnv *, jclass, jobject *) = jf[57]; jvmtiError (* get_method_name) (jvmtiEnv *, jmethodID, char **, char **, char **) = jf[64]; jvmtiError (* get_loaded_classes) (jvmtiEnv *, jint *, jclass **) = jf[78]; jint class_count, class_index; jclass * classes; class_pattern = make_pattern_spec (class_query, ignore_case); method_pattern = make_pattern_spec (method_query, ignore_case); groups = g_hash_table_new_full (NULL, NULL, NULL, NULL); if (get_loaded_classes (jvmti, &class_count, &classes) != JVMTI_ERROR_NONE) goto emit_results; for (class_index = 0; class_index != class_count; class_index++) { jclass klass = classes[class_index]; jobject loader = NULL; gboolean have_loader = FALSE; char * signature = NULL; gchar * class_name = NULL; gchar * class_name_copy = NULL; const gchar * normalized_class_name; jint method_count, method_index; jmethodID * methods = NULL; JsonBuilder * group = NULL; GHashTable * seen_method_names = NULL; if (skip_system_classes) { if (get_class_loader (jvmti, klass, &loader) != JVMTI_ERROR_NONE) goto skip_class; have_loader = TRUE; if (loader == NULL) goto skip_class; } if (get_class_signature (jvmti, klass, &signature, NULL) != JVMTI_ERROR_NONE) goto skip_class; class_name = class_name_from_signature (signature); if (ignore_case) { class_name_copy = g_utf8_strdown (class_name, -1); normalized_class_name = class_name_copy; } else { normalized_class_name = class_name; } if (!g_pattern_match_string (class_pattern, normalized_class_name)) goto skip_class; if (get_class_methods (jvmti, klass, &method_count, &methods) != JVMTI_ERROR_NONE) goto skip_class; if (!include_signature) seen_method_names = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); for (method_index = 0; method_index != method_count; method_index++) { jmethodID method = methods[method_index]; const gchar * method_name; char * method_name_value = NULL; char * method_signature_value = NULL; gchar * method_name_copy = NULL; const gchar * normalized_method_name; gchar * normalized_method_name_copy = NULL; if (get_method_name (jvmti, method, &method_name_value, include_signature ? &method_signature_value : NULL, NULL) != JVMTI_ERROR_NONE) goto skip_method; method_name = method_name_value; if (method_name[0] == '<') { if (strcmp (method_name, "<init>") == 0) method_name = "$init"; else if (strcmp (method_name, "<clinit>") == 0) goto skip_method; } if (include_signature) { method_name_copy = format_method_signature (method_name, method_signature_value); method_name = method_name_copy; } if (seen_method_names != NULL && g_hash_table_contains (seen_method_names, method_name)) goto skip_method; if (ignore_case) { normalized_method_name_copy = g_utf8_strdown (method_name, -1); normalized_method_name = normalized_method_name_copy; } else { normalized_method_name = method_name; } if (!g_pattern_match_string (method_pattern, normalized_method_name)) goto skip_method; if (group == NULL) { if (!have_loader && get_class_loader (jvmti, klass, &loader) != JVMTI_ERROR_NONE) goto skip_method; if (loader == NULL) { group = g_hash_table_lookup (groups, NULL); } else { GHashTableIter iter; jobject cur_loader; JsonBuilder * cur_group; g_hash_table_iter_init (&iter, groups); while (g_hash_table_iter_next (&iter, (gpointer *) &cur_loader, (gpointer *) &cur_group)) { if (cur_loader != NULL && is_same_object (env, cur_loader, loader)) { group = cur_group; break; } } } if (group == NULL) { jobject l; gchar * str; l = (loader != NULL) ? new_global_ref (env, loader) : NULL; group = json_builder_new_immutable (); g_hash_table_insert (groups, l, group); json_builder_begin_object (group); json_builder_set_member_name (group, "loader"); str = g_strdup_printf ("0x%" G_GSIZE_MODIFIER "x", GPOINTER_TO_SIZE (l)); json_builder_add_string_value (group, str); g_free (str); json_builder_set_member_name (group, "classes"); json_builder_begin_array (group); } json_builder_begin_object (group); json_builder_set_member_name (group, "name"); json_builder_add_string_value (group, class_name); json_builder_set_member_name (group, "methods"); json_builder_begin_array (group); } json_builder_add_string_value (group, method_name); if (seen_method_names != NULL) g_hash_table_add (seen_method_names, g_strdup (method_name)); skip_method: g_free (normalized_method_name_copy); g_free (method_name_copy); deallocate (jvmti, method_signature_value); deallocate (jvmti, method_name_value); } skip_class: if (group != NULL) { json_builder_end_array (group); json_builder_end_object (group); } if (seen_method_names != NULL) g_hash_table_unref (seen_method_names); deallocate (jvmti, methods); g_free (class_name_copy); g_free (class_name); deallocate (jvmti, signature); if (loader != NULL) delete_local_ref (env, loader); delete_local_ref (env, klass); } deallocate (jvmti, classes); emit_results: result = finalize_method_groups_to_json (groups); g_hash_table_unref (groups); g_pattern_spec_free (method_pattern); g_pattern_spec_free (class_pattern); return result; } static gchar * finalize_method_groups_to_json (GHashTable * groups) { GString * result; GHashTableIter iter; guint i; JsonBuilder * group; result = g_string_sized_new (1024); g_string_append_c (result, '['); g_hash_table_iter_init (&iter, groups); for (i = 0; g_hash_table_iter_next (&iter, NULL, (gpointer *) &group); i++) { JsonNode * root; gchar * json; if (i > 0) g_string_append_c (result, ','); json_builder_end_array (group); json_builder_end_object (group); root = json_builder_get_root (group); json = json_to_string (root, FALSE); g_string_append (result, json); g_free (json); json_node_unref (root); g_object_unref (group); } g_string_append_c (result, ']'); return g_string_free (result, FALSE); } static GPatternSpec * make_pattern_spec (const gchar * pattern, jboolean ignore_case) { GPatternSpec * spec; if (ignore_case) { gchar * str = g_utf8_strdown (pattern, -1); spec = g_pattern_spec_new (str); g_free (str); } else { spec = g_pattern_spec_new (pattern); } return spec; } static gchar * class_name_from_signature (const gchar * descriptor) { gchar * result, * c; result = g_strdup (descriptor + 1); for (c = result; *c != '\\0'; c++) { if (*c == '/') *c = '.'; } c[-1] = '\\0'; return result; } static gchar * format_method_signature (const gchar * name, const gchar * signature) { GString * sig; const gchar * cursor; gint arg_index; sig = g_string_sized_new (128); g_string_append (sig, name); cursor = signature; arg_index = -1; while (TRUE) { const gchar c = *cursor; if (c == '(') { g_string_append_c (sig, c); cursor++; arg_index = 0; } else if (c == ')') { g_string_append_c (sig, c); cursor++; break; } else { if (arg_index >= 1) g_string_append (sig, ", "); append_type (sig, &cursor); if (arg_index != -1) arg_index++; } } g_string_append (sig, ": "); append_type (sig, &cursor); return g_string_free (sig, FALSE); } static void append_type (GString * output, const gchar ** type) { const gchar * cursor = *type; switch (*cursor) { case 'Z': g_string_append (output, "boolean"); cursor++; break; case 'B': g_string_append (output, "byte"); cursor++; break; case 'C': g_string_append (output, "char"); cursor++; break; case 'S': g_string_append (output, "short"); cursor++; break; case 'I': g_string_append (output, "int"); cursor++; break; case 'J': g_string_append (output, "long"); cursor++; break; case 'F': g_string_append (output, "float"); cursor++; break; case 'D': g_string_append (output, "double"); cursor++; break; case 'V': g_string_append (output, "void"); cursor++; break; case 'L': { gchar ch; cursor++; for (; (ch = *cursor) != ';'; cursor++) { g_string_append_c (output, (ch != '/') ? ch : '.'); } cursor++; break; } case '[': *type = cursor + 1; append_type (output, type); g_string_append (output, "[]"); return; default: g_string_append (output, "BUG"); cursor++; } *type = cursor; } void dealloc (gpointer mem) { g_free (mem); } static gpointer read_art_array (gpointer object_base, guint field_offset, guint length_size, guint * length) { gpointer result, header; guint n; header = GSIZE_TO_POINTER (*(guint64 *) (object_base + field_offset)); if (header != NULL) { result = header + length_size; if (length_size == sizeof (guint32)) n = *(guint32 *) header; else n = *(guint64 *) header; } else { result = NULL; n = 0; } if (length != NULL) *length = n; return result; } static void std_string_destroy (StdString * str) { if ((str->l.capacity & 1) != 0) art_api.free (str->l.data); } static gchar * std_string_c_str (StdString * self) { if ((self->l.capacity & 1) != 0) return self->l.data; return self->s.data; } `; const methodQueryPattern = /(.+)!([^/]+)\/?([isu]+)?/; let cm = null; let unwrap = null; export default class Model { static build (handle, env) { ensureInitialized(env); return unwrap(handle, env, object => { return new Model(cm.new(handle, object, env)); }); } static enumerateMethods (query, api, env) { ensureInitialized(env); const params = query.match(methodQueryPattern); if (params === null) { throw new Error('Invalid query; format is: class!method -- see documentation of Java.enumerateMethods(query) for details'); } const classQuery = Memory.allocUtf8String(params[1]); const methodQuery = Memory.allocUtf8String(params[2]); let includeSignature = false; let ignoreCase = false; let skipSystemClasses = false; const modifiers = params[3]; if (modifiers !== undefined) { includeSignature = modifiers.indexOf('s') !== -1; ignoreCase = modifiers.indexOf('i') !== -1; skipSystemClasses = modifiers.indexOf('u') !== -1; } let result; if (api.flavor === 'jvm') { const json = cm.enumerateMethodsJvm(classQuery, methodQuery, boolToNative(includeSignature), boolToNative(ignoreCase), boolToNative(skipSystemClasses), env, api.jvmti); try { result = JSON.parse(json.readUtf8String()) .map(group => { const loaderRef = ptr(group.loader); group.loader = !loaderRef.isNull() ? loaderRef : null; return group; }); } finally { cm.dealloc(json); } } else { withRunnableArtThread(env.vm, env, thread => { const json = cm.enumerateMethodsArt(classQuery, methodQuery, boolToNative(includeSignature), boolToNative(ignoreCase), boolToNative(skipSystemClasses)); try { const addGlobalReference = api['art::JavaVMExt::AddGlobalRef']; const { vm: vmHandle } = api; result = JSON.parse(json.readUtf8String()) .map(group => { const loaderObj = group.loader; group.loader = (loaderObj !== 0) ? addGlobalReference(vmHandle, thread, ptr(loaderObj)) : null; return group; }); } finally { cm.dealloc(json); } }); } return result; } constructor (handle) { this.handle = handle; } has (member) { return cm.has(this.handle, Memory.allocUtf8String(member)) !== 0; } find (member) { return cm.find(this.handle, Memory.allocUtf8String(member)).readUtf8String(); } list () { const str = cm.list(this.handle); try { return JSON.parse(str.readUtf8String()); } finally { cm.dealloc(str); } } } function ensureInitialized (env) { if (cm === null) { cm = compileModule(env); unwrap = makeHandleUnwrapper(cm, env.vm); } } function compileModule (env) { const { pointerSize } = Process; const lockSize = 8; const modelsSize = pointerSize; const javaApiSize = 6 * pointerSize; const artApiSize = (10 * 4) + (5 * pointerSize); const dataSize = lockSize + modelsSize + javaApiSize + artApiSize; const data = Memory.alloc(dataSize); const lock = data; const models = lock.add(lockSize); const javaApi = models.add(modelsSize); const { getDeclaredMethods, getDeclaredFields } = env.javaLangClass(); const method = env.javaLangReflectMethod(); const field = env.javaLangReflectField(); let j = javaApi; [ getDeclaredMethods, getDeclaredFields, method.getName, method.getModifiers, field.getName, field.getModifiers ] .forEach(value => { j = j.writePointer(value).add(pointerSize); }); const artApi = javaApi.add(javaApiSize); const { vm } = env; const artClass = getArtClassSpec(vm); if (artClass !== null) { const c = artClass.offset; const m = getArtMethodSpec(vm); const f = getArtFieldSpec(vm); let s = artApi; [ 1, c.ifields, c.methods, c.sfields, c.copiedMethodsOffset, m.size, m.offset.accessFlags, f.size, f.offset.accessFlags, 0xffffffff ] .forEach(value => { s = s.writeUInt(value).add(4); }); const api = getApi(); [ api.artClassLinker.address, api['art::ClassLinker::VisitClasses'], api['art::mirror::Class::GetDescriptor'], api['art::ArtMethod::PrettyMethod'], Process.getModuleByName('libc.so').getExportByName('free') ] .forEach((value, i) => { if (value === undefined) { value = NULL; } s = s.writePointer(value).add(pointerSize); }); } const cm = new CModule(code, { lock, models, java_api: javaApi, art_api: artApi }); const reentrantOptions = { exceptions: 'propagate' }; const fastOptions = { exceptions: 'propagate', scheduling: 'exclusive' }; return { handle: cm, mode: (artClass !== null) ? 'full' : 'basic', new: new NativeFunction(cm.model_new, 'pointer', ['pointer', 'pointer', 'pointer'], reentrantOptions), has: new NativeFunction(cm.model_has, 'bool', ['pointer', 'pointer'], fastOptions), find: new NativeFunction(cm.model_find, 'pointer', ['pointer', 'pointer'], fastOptions), list: new NativeFunction(cm.model_list, 'pointer', ['pointer'], fastOptions), enumerateMethodsArt: new NativeFunction(cm.enumerate_methods_art, 'pointer', ['pointer', 'pointer', 'bool', 'bool', 'bool'], reentrantOptions), enumerateMethodsJvm: new NativeFunction(cm.enumerate_methods_jvm, 'pointer', ['pointer', 'pointer', 'bool', 'bool', 'bool', 'pointer', 'pointer'], reentrantOptions), dealloc: new NativeFunction(cm.dealloc, 'void', ['pointer'], fastOptions) }; } function makeHandleUnwrapper (cm, vm) { if (cm.mode === 'basic') { return nullUnwrap; } const decodeGlobal = getApi()['art::JavaVMExt::DecodeGlobal']; return function (handle, env, fn) { let result; withRunnableArtThread(vm, env, thread => { const object = decodeGlobal(vm, thread, handle); result = fn(object); }); return result; }; } function nullUnwrap (handle, env, fn) { return fn(NULL); } function boolToNative (val) { return val ? 1 : 0; }