frida-java-bridge
Version:
Java runtime interop from Frida
1,391 lines (1,138 loc) • 36.4 kB
JavaScript
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;
}