-
背景
- 前段時間國家加強對 app 獲取用戶隱私信息的限制。手 Q 需要排查在用戶未同意隱私條款之前有哪些業(yè)務(wù)進行了獲取 IMEI 和應(yīng)用列表的調(diào)用
- 怎么排查才能不漏掉某些調(diào)用踪危?最后我們是通過 Android Studio 的 Method Breakpoint 功能實現(xiàn)對系統(tǒng) java 函數(shù)斷點調(diào)試解決
- 解決完之后贞远,我們想把這個 Method Breakpoint 技術(shù)做到代碼中蓝仲,由我們控制程序的執(zhí)行袱结。經(jīng)過一段時間的預(yù)研垢夹,目前初步可以做到 Hook Java 方法來解決一些問題果元,本文是對預(yù)研做總結(jié)
-
簡介
- jvmti Java Virtual Machine Tool Interface 是 JVM 提供給外部的用于調(diào)試和分析 JVM 虛擬機的接口
- Android Studio 3.5 版本應(yīng)用 jvmti 技術(shù)優(yōu)化了 Instant Run 功能噪漾,解決了開發(fā)者編譯耗時問題
- jvmti 技術(shù)在 Art 虛擬機上的實現(xiàn)是 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<void **>(&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; }
- 設(shè)置事件回調(diào)函數(shù)
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) { ... } }
- 打印調(diào)用棧
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) { ... }
- 讀寫函數(shù)參數(shù)和局部變量
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); }
-
查看運行結(jié)果日志
-
原理
- 相關(guān)文檔
- 相關(guān)源碼
-
Art 上的 jvmti 技術(shù)框架
- Art TI 對外提供的接口和數(shù)據(jù)結(jié)構(gòu)
- 接口和數(shù)據(jù)結(jié)構(gòu)全部包含在 jvmti.h 頭文件中
- 核心結(jié)構(gòu)是 struct jvmtiEnv 封裝了調(diào)用 jvmti 的上下文信息
- Art TI 接口的實現(xiàn)
- 對應(yīng)源碼文件 http://androidxref.com/9.0.0_r3/xref/art/openjdkjvmti/OpenjdkJvmTi.cc
- 對應(yīng)源碼類 http://androidxref.com/9.0.0_r3/xref/art/openjdkjvmti/OpenjdkJvmTi.cc#JvmtiFunctions
- 接口與實現(xiàn)綁定
// 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 構(gòu)造函數(shù) 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<ObjectTagTable>(new ObjectTagTable(event_handler, this)); // 綁定接口的函數(shù)實現(xiàn) functions = &gJvmtiInterface; }
-
核心結(jié)構(gòu)類圖
-
JavaVM
-
jvmtiEnv
-
JNIEnv
-
數(shù)據(jù)處理流程
-
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()); }
-
-
-
總結(jié)
本文對預(yù)研結(jié)果進行了歸納總結(jié):- 整理了如何利用 jvmti 接口實現(xiàn) Java Hook 的流程
- 整理了 jvmti 接口在 Art 虛擬機中相關(guān)的核心結(jié)構(gòu)如 JavaVM、jvmtiEnv彰触、jniEnv 的關(guān)系
- 整理了 Agent 啟動流程和 jvmtiEnv 結(jié)構(gòu)的實例化流程
手 Q 安卓端 - Art 虛擬機工具接口 Art TI 技術(shù)探索
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
- 文/潘曉璐 我一進店門茉贡,熙熙樓的掌柜王于貴愁眉苦臉地迎上來塞栅,“玉大人,你說我怎么就攤上這事腔丧》乓” “怎么了?”我有些...
- 文/不壞的土叔 我叫張陵愉粤,是天一觀的道長砾医。 經(jīng)常有香客問我,道長衣厘,這世上最難降的妖魔是什么如蚜? 我笑而不...
- 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上错邦,老公的妹妹穿的比我還像新娘。我一直安慰自己讨勤,他們只是感情好借尿,可當(dāng)我...
- 文/花漫 我一把揭開白布茂契。 她就那樣靜靜地躺著,像睡著了一般恢共。 火紅的嫁衣襯著肌膚如雪涨岁。 梳的紋絲不亂的頭發(fā)上尝哆,一...
- 文/蒼蘭香墨 我猛地睜開眼筒愚,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
- 正文 年R本政府宣布稚虎,位于F島的核電站茴她,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜钾怔,卻給世界環(huán)境...
- 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望欣鳖。 院中可真熱鬧察皇,春花似錦、人聲如沸泽台。這莊子的主人今日做“春日...
- 文/蒼蘭香墨 我抬頭看了看天上的太陽稻爬。三九已至嗜闻,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間因篇,已是汗流浹背笔横。 一陣腳步聲響...
推薦閱讀更多精彩內(nèi)容
- 傅盛在微博上給人的印象是隔三差五的跑個馬拉松,閑的不像個上市公司的CEO殃恒≈簿桑看到他的《認(rèn)知三部曲》三篇文章,終于明白...