-
背景
- 前段时间国家加强对 app 获取用户隐私信息的限制。手 Q 需要排查在用户未同意隐私条款之前有哪些业务进行了获取 IMEI 和应用列表的调用
- 怎么排查才能不漏掉某些调用?最后我们是通过 Android Studio 的 Method Breakpoint 功能实现对系统 java 函数断点调试解决
- 解决完之后,我们想把这个 Method Breakpoint 技术做到代码中,由我们控制程序的执行。经过一段时间的预研,目前初步可以做到 Hook Java 方法来解决一些问题,本文是对预研做总结
-
简介
- jvmti Java Virtual Machine Tool Interface 是 JVM 提供给外部的用于调试和分析 JVM 虚拟机的接口
- Android Studio 3.5 版本应用 jvmti 技术优化了 Instant Run 功能,解决了开发者编译耗时问题
- jvmti 技术在 Art 虚拟机上的实现是 Art TI
-
怎么做
- 加载 agent 程序
Debug.attachJvmtiAgent("libnative-lib.so", "", getClassLoader());
- agent 初始化
JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM *vm, char *options, void *reserved) { return JVMTI_ERROR_NONE; } JNIEXPORT jint JNICALL Agent_OnAttach(JavaVM *vm, char *options, void *reserved) { return JVMTI_ERROR_NONE; }
- 读取 jvmtiEnv 对象
jvmtiEnv *jvmti_env; if (gVm->GetEnv(reinterpret_cast
(&jvmti_env), JVMTI_VERSION_1_0) != JNI_OK) { ALOG("jvmtiEnv error"); return; } - 查找待 hook java 方法 jmethodID 对象
const char *className = "com/android/internal/telephony/ITelephony$Stub$Proxy"; jclass clazz = env->FindClass(className); if (env->ExceptionCheck()) { env->ExceptionDescribe(); env->ExceptionClear(); } ALOG("jclass = %p", clazz); const char *methodName = "getDeviceId"; const char *sig = "(Ljava/lang/String;)Ljava/lang/String;"; jmethodID method = env->GetMethodID(clazz, methodName, sig); if (env->ExceptionCheck()) { env->ExceptionDescribe(); env->ExceptionClear(); } ALOG(" %s %d gHookMethod = %p", __FILE__, __LINE__, method); gHookMethod = method;
- 向 jvmti 声明开放 can_generate_method_entry_events 和 can_access_local_variables 能力
const jvmtiCapabilities capabilities_ptr = { .can_access_local_variables = 1, .can_generate_method_entry_events = 1, }; if (jvmti_env->AddCapabilities(&capabilities_ptr) != JVMTI_ERROR_NONE) { ALOG("AddCapabilities ERROR"); return; }
- 设置事件回调函数
extern void JNICALL jvmtiEventMethodEntry1 (jvmtiEnv *jvmti_env, JNIEnv *jni_env, jthread thread, jmethodID method) { ... } const jvmtiEventCallbacks callbacks = { .MethodEntry = jvmtiEventMethodEntry1 }; if (jvmti_env->SetEventCallbacks(&callbacks, sizeof(callbacks)) != JVMTI_ERROR_NONE) { ALOG("SetEventCallbacks ERROR"); return; }
- 开启进入 java 方法事件通知
if (jvmti_env->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_METHOD_ENTRY, NULL) != JVMTI_ERROR_NONE) { ALOG("SetEventNotificationMode ERROR"); return; }
- 接收进入 java 方法事件,过滤指定 hook java 方法
extern void JNICALL jvmtiEventMethodEntry1 (jvmtiEnv *jvmti_env, JNIEnv *jni_env, jthread thread, jmethodID method) { if (method == gHookMethod) { ... } }
- 打印调用栈
jint start_depth = 0; jvmtiFrameInfo frame_buffer[64]; jint max_frame_count = 64; jint count_ptr; ALOG("GetStackTrace max_frame_count = %d", max_frame_count); if (jvmti_env->GetStackTrace(thread, start_depth, max_frame_count, frame_buffer, &count_ptr) != JVMTI_ERROR_NONE) { return; } ALOG("GetStackTrace count_ptr = %d", count_ptr); for (int i = 0; i < count_ptr; ++i) { char *name_ptr = NULL; if (jvmti_env->GetMethodName(frame_buffer[i].method, &name_ptr, NULL, NULL) == JVMTI_ERROR_NONE) { jclass declaring_class_ptr; if(jvmti_env->GetMethodDeclaringClass(frame_buffer[i].method, &declaring_class_ptr) == JVMTI_ERROR_NONE){ jclass klass = declaring_class_ptr; char* signature_ptr; char* generic_ptr; if (jvmti_env->GetClassSignature(klass, &signature_ptr, &generic_ptr) == JVMTI_ERROR_NONE){ ALOG("# %d, %s.%s location=%d", i, signature_ptr, name_ptr, frame_buffer[i].location); } } } }
- 读取本地变量表
jint entry_count_ptr; jvmtiLocalVariableEntry *table_ptr; if (jvmti_env->GetLocalVariableTable(method, &entry_count_ptr, &table_ptr) == JVMTI_ERROR_NONE) { ... }
- 读写函数参数和局部变量
jint depth = 0; jint slot = table_ptr->slot; jint value_pt; if (jvmti_env->GetLocalInt(thread, depth, slot, &value_pt) == JVMTI_ERROR_NONE) { ALOG("GetLocalInt depth = %d, slot = %d, value = %d", depth, slot, value_pt); } value_pt++; if (jvmti_env->SetLocalInt(thread, depth, slot, value_pt) == JVMTI_ERROR_NONE) { ALOG("SetLocalInt depth = %d, slot = %d, value = %d", depth, slot, value_pt); }
-
查看运行结果日志
-
原理
- 相关文档
- https://docs.oracle.com/javase/8/docs/platform/jvmti/jvmti.html
- https://source.android.com/devices/tech/dalvik/art-ti
- 相关源码
- http://androidxref.com/9.0.0_r3/xref/art/openjdkjvmti/
-
Art 上的 jvmti 技术框架
- Art TI 对外提供的接口和数据结构
- 接口和数据结构全部包含在 jvmti.h 头文件中
- 核心结构是 struct jvmtiEnv 封装了调用 jvmti 的上下文信息
- Art TI 接口的实现
- 对应源码文件 http://androidxref.com/9.0.0_r3/xref/art/openjdkjvmti/OpenjdkJvmTi.cc
- 对应源码类 http://androidxref.com/9.0.0_r3/xref/art/openjdkjvmti/OpenjdkJvmTi.cc#JvmtiFunctions
- 接口与实现绑定
// The actual struct holding all of the entrypoints into the jvmti interface. const jvmtiInterface_1 gJvmtiInterface = { nullptr, // reserved1 JvmtiFunctions::SetEventNotificationMode, nullptr, // reserved3 JvmtiFunctions::GetAllThreads, JvmtiFunctions::SuspendThread, JvmtiFunctions::ResumeThread, JvmtiFunctions::StopThread, JvmtiFunctions::InterruptThread, JvmtiFunctions::GetThreadInfo, ... } // ArtJvmTiEnv 构造函数 ArtJvmTiEnv::ArtJvmTiEnv(art::JavaVMExt* runtime, EventHandler* event_handler, jint version) : art_vm(runtime), local_data(nullptr), ti_version(version), capabilities(), event_info_mutex_("jvmtiEnv_EventInfoMutex") { object_tag_table = std::unique_ptr
(new ObjectTagTable(event_handler, this)); // 绑定接口的函数实现 functions = &gJvmtiInterface; }
- 相关文档
-
核心结构类图
-
JavaVM
-
jvmtiEnv
-
JNIEnv
-
数据处理流程
-
Agent 启动流程
-
jvmtiEnv 类的实例化流程
// Creates a jvmtiEnv and returns it with the art::ti::Env that is associated with it. new_art_ti // is a pointer to the uninitialized memory for an art::ti::Env. static void CreateArtJvmTiEnv(art::JavaVMExt* vm, jint version, /*out*/void** new_jvmtiEnv) { struct ArtJvmTiEnv* env = new ArtJvmTiEnv(vm, gEventHandler, version); *new_jvmtiEnv = env; gEventHandler->RegisterArtJvmTiEnv(env); art::Runtime::Current()->AddSystemWeakHolder( ArtJvmTiEnv::AsArtJvmTiEnv(env)->object_tag_table.get()); }
-
-
-
总结
本文对预研结果进行了归纳总结:- 整理了如何利用 jvmti 接口实现 Java Hook 的流程
- 整理了 jvmti 接口在 Art 虚拟机中相关的核心结构如 JavaVM、jvmtiEnv、jniEnv 的关系
- 整理了 Agent 启动流程和 jvmtiEnv 结构的实例化流程