adbi的java hook實現代碼ddi不在之前下載的文件中,下載地址:https://github.com/crmulliner/ddi,具體的編譯看readme里面很詳細的介紹了。注意ddi代碼不能單獨使用要跟之前的adbi相結合,因為adbi提供了注入so。本文不對代碼進行詳細的剖析(你可以看參考資料的文章),分析下2個問題:java如何hook;如何執行自己的java函數。
java hook:
其實在ddi的java hook和xposed的hook原理(不清楚的看我之前xposed的分析)是相同的,都是改變被hook函數的accessflag;將原本的java函數變成native函數,從而去執行自己定義的native函數;它實現的代碼在dalvik_hook.c的dalvik_hook函數中。但不同於xposed是,它沒有直接修改Mehtod結構的insns和nativeFunc而是調用了dvmUseJNIBridge函數來修改insns和nativeFunc。但在我看這個函數時發現其跟android源碼中的dvmRegisterJNIMethod很相似:
static bool dvmRegisterJNIMethod(ClassObject* clazz, const char* methodName, const char* signature, void* fnPtr) { // 解釋下參數: // clazz:類名"shy/luo/jni/ClassWithJni"; // methodName:需要注冊的jni方法名 nanosleep; // signature:方法的簽名 實質是方法的參數和返回值,區別不同參數的函數 // fnPtr: jni方法函數地址 即shy_luo_jni_ClassWithJni_nanosleep函數;dalvik執行的就是這個函數,很重要哎 Method* method; ...... method = dvmFindDirectMethodByDescriptor(clazz, methodName, signature); ...... dvmUseJNIBridge(method, fnPtr); ...... }
dvmRegisterJNIMethod(關於此函數的解析,看dalvik淺析二:jni、so)函數中用dvmFindDirectMethodByDescriptor找到函數在dalvik中的Method結構,然后再dvmUseJNIBridge為Method注冊native函數(在正常情況下,該Method的accessflag就是native所以無需修改)。下面來看ddi中的dalvik_hook函數實現
void* dalvik_hook(struct dexstuff_t *dex, struct dalvik_hook_t *h) { //dalvik_hook_t結構體中已包含函數所屬的class、name、signature ...... h->method = dex->dvmFindVirtualMethodHierByDescriptor_fnPtr(target_cls, h->method_name, h->method_sig); if (h->method == 0) { h->method = dex->dvmFindDirectMethodByDescriptor_fnPtr(target_cls, h->method_name, h->method_sig); } ...... //保留method的屬性帶以后還原 h->iss = h->method->insSize; h->rss = h->method->registersSize; h->oss = h->method->outsSize; // 這里要處理 h->method->insSize = h->n_iss; h->method->registersSize = h->n_rss; h->method->outsSize = h->n_oss; h->method->jniArgInfo = 0x80000000; // <--- also important;這里很重要,下面要分析 ...... h->access_flags = h->method->a; //保留原先函數的accessflag h->method->a = h->method->a | h->af; // make method native ...... // bind function dex->dvmUseJNIBridge_fnPtr(h->method, h->native_func); ...... }
顯而易見,dalvik_hook按dvmRegisterJNIMethod的邏輯來編寫的,所以hook其實是自己為java函數注冊了一個jni方法!(熟讀源碼理解實現機制,你會發現很多框架和工具都是基於源碼的)ok,上面的知識點大部分都是熟悉,注意藍色代碼很重要的是函數使用的dalvik寄存器個數,xposed也對其進行修正但不同的是jniArgInfo屬性,xposed沒對它修改,看下對jniArgInfo的描述
jniArgInfo:這個變量記錄了一些預先計算好的信息,從而不需要在調用的時候再通過方法的參數和返回值實時計算了,方便了JNI的調用,提高了調用的速度。如果第一位為1(即0x80000000),則Dalvik虛擬機會忽略后面的所有信息,強制在調用時實時計算;
可以這么理解jniArgInfo相當於jni方法的cache標識符,若jniArgInfo不為0x80000000則dalvik會按之前調用過的jni方法去執行。回到xposed和ddi的區別:
xposed:在loadPackage時hook函數,而在此時絕對沒有執行過函數的jni方法;
ddi:在app運行時hook函數,我們可不敢肯定此時沒有執行過jni方法啊
執行自己的java函數:
在so中執行了上面的java hook后,app在調用被hook函數時就會執行我們的自定義的native函數。那我們又怎樣去執行我們自己的java函數呢(難道不可以直接在native函數中實現邏輯?當然可以啊,但java開發android簡單啊)?很簡單,1.加載自己的dex(包含自定義java函數的文件);2.找到自定義java函數地址3.執java函數。下面對以上三步用到的知識點剖析。
const DalvikNativeMethod dvm_dalvik_system_DexFile[] = { // /dalvik/vm/native/dalvik_system_DexFile.cpp基於源碼4.4 520 { "openDexFileNative", "(Ljava/lang/String;Ljava/lang/String;I)I", 521 Dalvik_dalvik_system_DexFile_openDexFileNative }, 522 { "openDexFile", "([B)I", 523 Dalvik_dalvik_system_DexFile_openDexFile_bytearray }, 524 { "closeDexFile", "(I)V", 525 Dalvik_dalvik_system_DexFile_closeDexFile }, 526 { "defineClassNative", "(Ljava/lang/String;Ljava/lang/ClassLoader;I)Ljava/lang/Class;", 527 Dalvik_dalvik_system_DexFile_defineClassNative }, 528 { "getClassNameList", "(I)[Ljava/lang/String;", 529 Dalvik_dalvik_system_DexFile_getClassNameList }, 530 { "isDexOptNeeded", "(Ljava/lang/String;)Z", 531 Dalvik_dalvik_system_DexFile_isDexOptNeeded }, 532 { NULL, NULL, NULL }, 533};
第一、二步的知識點都在上面的代碼里。加載dex的知識我們在app加殼的文章中有所提及,ddi利用Dalvik_dalvik_system_DexFile_openDexFileNative直接加載目錄里的dex文件。關於第二步找函數是利用Dalvik_dalvik_system_DexFile_defineClassNative來實現,具體可以看dalvik淺析三:類加載。 這里我有一點不明白的是為什么調用了Dalvik_dalvik_system_DexFile_defineClassNative還要再去調用(*env)->FindClass去得到ClassObject,明明2者都是調用dvmFindClassNoInit去實現的啊,有誰知道告訴我!第三步更簡單了,得到函數的ClassObject后直接找到methodID,再調用CallVoidMethodA就可以在native執行java函數啦。
最后添幅圖來理解下ddi的整個執行過程吧
當然你也可以在my_init中hook到自定義native函數2中,省去native函數1的執行。
ok,adbi的分析就到此為止啦。總結下adbi在hijack中利用ptrace在目標進程注入代碼,該代碼會加載so;而在so里面可以進行so和java hook:
so hook:找到native函數的指令利用Inline hook去修改它,就可以在執行native函數時去執行自定義的函數;
java hook:改變函數的accessflag去執行自己的native函數,而在native中去執行自定義的java函數
參考資料: