allhookinone

struct HookInfo{
    char * classDesc;//被hook方法聲明所在的類.
    char * methodName;//被hook方法名字
    char * methodSig;//被hook方法的參數和返回值例如,如果被hook方法是`private String getString(int a,String b)`則此時對應的methodSig為`(ILjava/lang/String;)Ljava/lang/String;`
    
    //for dalvik jvm
    bool isStaticMethod;//
    void * originalMethod;
    void * returnType;
    void * paramTypes;

    //for art jvm
    const void * nativecode;
    const void * entrypoint;
}

java hook思路

在hook之前需要準備的信息

  1. 需要知道被hook方法的名字以及被hook方法定義所在的類
  2. 通過classname.class.getDeclaredMethod("methodname",args[i].class)方法獲取被hook方法的Method對象;
  3. 得到被hook方法的Method對象之后,可以通過Method類提供的方法獲取更多的被hook方法的信息。具體是clasdes(被Hook方法的聲明所在的類,通過method.getDeclaringClass().getName()方法獲得),methodname(通過method.getName()方法獲得),methodsig(methodsig中存儲的是被hook方法的參數和返回值).

hook流程

在進行hook時,主要思路是修改被hook方法對應的ArtMethod對象中的EntryPointFromCompiledCode()即被hook方法的本地機器指令入口。修改該入口,使得該入口指向我們編寫的函數,在執行完成之后跳轉到被hook函數,從而完成整個hook過程。
由于java代碼在生成本地機器指令時,是通過dex2oat程序完成的,而native層代碼生成本地機器指令時則是通過gcc生成,這兩套工具生成的機器指令規則不同,所以在相互調用時,需要去調整寄存器中的內容。所以在修改被hook方法時,需要對寄存器中內容進行調整。
兩種工具生成的本地機器指令對應的寄存器的內容如下所示

r0=method pointer                                                  |r0=method pointer
r1=thread pointer                                                  |r1=arg1
r2=args array                                                      |r2=arg2
r3=old sp                                                          |r3=arg3
[sp]=entrypoint                                                    |r9=thread pointer
        1                                                         |[sp]=method pointer
                                                                   |[sp+4]=addr of thiz
                                                                   |[sp+8]=addr of arg1
                                                                   |[sp+12]=addr of arg2
                                                                   |[sp+16]=addr of arg3 
                                                                                2

具體代碼分析

代碼入口點hookMethodNative(String clsdes,String methodname,String methodsig,boolean isstatic)
對應的native層方法是java_com_example_allhookinone_HookUtils_hookMethodNative(JNIEnv *env,jobject thiz,jstring cls,jstring methodname,jstring methodsig,jboolean isstatic)方法
java語言中的String對應到native層是jstring,所以整體的代碼邏輯是

  1. 將jstring類型的變量都轉換成char *類型,存入到HookInfo結構體中。這一步會對HookInfo結構體中的classDesc,methodName,methodSig,isStaticMethod進行賦值
  2. 獲取android虛擬機運行過程中加載的so文件,以此來判斷android虛擬機是art模式還是dalvik模式。這里假定androidVM此時是ART運行時,往下繼續分析代碼。
  3. 從HookInfo結構體中可以獲取到被hook方法的相關信息,包括classDesc(被hook方法定義所在的類),methodName(被hook方法的名字),methodSig(被hook方法的參數和返回值),isStaticMethod(被hook方法是否是靜態方法)。
  4. 獲取當前程序對應的JNIEnv。然后通過JNIEnv中提供的JNI方法得到被hook方法對應的ArtMethod對象。具體的獲取方法是
    4.1 通過env->FindClass(classDesc)方法獲取被hook方法所在的類,存儲在jclass類型的變量claxx中。
    4.2 判斷被hook方法是否是靜態方法(通過第一步中獲取的isStaticMethod判斷),如果是靜態方法則通過env->GetStaticMethodID(claxx,methodName,methodSig)獲取被hook方法的MethodID,存儲在jmethodID類型的變量methid中;如果是非靜態方法,則通過env->GetMethodID(claxx,methodName,methodSig)獲取被hook方法的MethodID,同樣存儲在jmethodID類型的變量methid中。
  5. 獲取到被hook方法的methodID之后,通過reinterpret_cast<ArtMethod *>(methid)獲取被hook方法,存儲在ArtMethod*類型的變量artmeth中
  6. 獲取ArtMethod類型的對象之后,通過GetEntryPointFromCompiledCode()獲取被hook方法原本的本地機器指令入口,然后將這個入口與我們hook之后指向的入口做比較,如果此時被hook方法指定的本地機器指令入口不是我們hook之后指向的入口,則繼續走下邊的流程;如果兩個入口相同,則說明該方法已經被hook,程序運行結束。
  7. 被hook方法對應的artmethod對象中指定的本地機器指令入口不是hook之后指向的入口
    7.1 獲取此時被hook方法對應的artmethod對象中指定的本地機器指令入口,存儲在新定義的變量entrypoint中
    7.2 將entrypoint賦值給HookInfo結構體中的entrypoint變量
    7.3 將當前被hook方法對應的artmethod對象中指定的NativeMethod賦值給HookInfo結構體中的nativecode
    7.4重新設置被hook方法對應的artmethod對象中的EntryPointFromCompiledCode,將這個值設置為art_quick_dispatcher。
    7.5重新設置artmethod對象中的nativemethod。

修改完成之后,當執行被hook方法時,由于已經將被hook方法的本地機器指令入口設置為art_quick_dispatcher,所以執行到被hook方法時會去運行art_quick_dispatcher。
art_quick_dispatcher是由匯編代碼實現的。如下所示。

 ENTRY art_quick_dispatcher
    push    {r4, r5, lr}           @ sp - 12
    mov      r0, r0                @ pass r0 to method
    str   r1, [sp, #(12 + 4)]
    str      r2, [sp, #(12 + 8)]
    str      r3, [sp, #(12 + 12)]
    mov   r1, r9                   @ pass r1 to thread
    add      r2, sp, #(12 + 4)     @ pass r2 to args array
    add   r3, sp, #12              @ pass r3 to old SP
    blx      artQuickToDispatcher   @ (Method* method, Thread*, u4 **, u4 **)
    pop      {r4, r5, pc}          @ return on success, r0 and r1 hold the result
END art_quick_dispatcher

從這段匯編可以看出,在運行匯編之前,寄存器中的內容分布是第二種

這段匯編代碼的邏輯是

  1. 保存環境,以便運行完成之后返回,執行下一條指令。
  2. 保存r1,r2,r3寄存器中的內容
  3. r0中的值不變,在兩種存儲規則中都是存儲的method pointer
  4. 從r9中獲取thread pointer,存儲到r1中
  5. r2中存儲的是參數指針,
  6. r3中存儲原來的棧定sp
  7. 調用artQuickToDispatcher方法。
  8. 運行結束,返回pc,從pc中可以獲取下一條指令

artQuickToDispatcher方法

artQuickToDispatcher方法主要完成的工作包括兩點

  • 運行我們添加的before hook method,
  • 調整寄存器中的內容,將寄存器中的內容從第一種修改成第二種之后調用被hook方法原本的本地機器指令

artQuickToDispatcher方法的代碼如下所示

extern "C" uint64_t artQuickToDispatcher(ArtMethod* method, Thread *self, u4 **args, u4 **old_sp){
    HookInfo *info = (HookInfo *)method->GetNativeMethod();
    LOGI("[+] entry ArtHandler %s->%s", info->classDesc, info->methodName);

    // If it not is static method, then args[0] was pointing to this
    if(!info->isStaticMethod){
        Object *thiz = reinterpret_cast<Object *>(args[0]);
        if(thiz != NULL){
            char *bytes = get_chars_from_utf16(thiz->GetClass()->GetName());
            LOGI("[+] thiz class is %s", bytes);
            delete bytes;
        }
    }
//從這個函數開始到這里為止的這一段代碼相當于before hook方法

    const void *entrypoint = info->entrypoint;//info中存儲的是被hook方法原本的本地機器指令入口
    method->SetNativeMethod(info->nativecode); //restore nativecode for JNI method
    uint64_t res = art_quick_call_entrypoint(method, self, args, old_sp, entrypoint);//調整寄存器內容,從第一種調整成第二種,調整之后會通過blx跳轉到被hook方法原本的本地機器指令區執行

    JValue* result = (JValue* )&res;
    if(result != NULL){
        Object *obj = result->l;
        char *raw_class_name = get_chars_from_utf16(obj->GetClass()->GetName());

        if(strcmp(raw_class_name, "java.lang.String") == 0){
            char *raw_string_value = get_chars_from_utf16((String *)obj);
            LOGI("result-class %s, result-value \"%s\"", raw_class_name, raw_string_value);
            free(raw_string_value);
        }else{
            LOGI("result-class %s", raw_class_name);
        }

        free(raw_class_name);
    }

    // entrypoid may be replaced by trampoline, only once.
//  if(method->IsStatic() && !method->IsConstructor()){

    entrypoint = method->GetEntryPointFromCompiledCode();
    if(entrypoint != (const void *)art_quick_dispatcher){
        LOGW("[*] entrypoint was replaced. %s->%s", info->classDesc, info->methodName);

        method->SetEntryPointFromCompiledCode((const void *)art_quick_dispatcher);
        info->entrypoint = entrypoint;
        info->nativecode = method->GetNativeMethod();
    }

    method->SetNativeMethod((const void *)info);

//  }

    return res;
}

具體的代碼分析見注釋

art_quick_call_entrypoint是由匯編代碼實現的。如下所示。

 ENTRY art_quick_call_entrypoint
    push    {r4, r5, lr}           @ sp - 12
    sub     sp, #(40 + 20)         @ sp - 40 - 20
    str     r0, [sp, #(40 + 0)]    @ var_40_0 = method_pointer
    str     r1, [sp, #(40 + 4)]    @ var_40_4 = thread_pointer
    str     r2, [sp, #(40 + 8)]    @ var_40_8 = args_array
    str     r3, [sp, #(40 + 12)]   @ var_40_12 = old_sp
    mov     r0, sp
    mov     r1, r3
    ldr     r2, =40
    blx     memcpy                 @ memcpy(dest, src, size_of_byte)
    ldr     r0, [sp, #(40 + 0)]    @ restore method to r0
    ldr     r1, [sp, #(40 + 4)]
    mov     r9, r1                 @ restore thread to r9
    ldr     r5, [sp, #(40 + 8)]    @ pass r5 to args_array
    ldr     r1, [r5]               @ restore arg1
    ldr     r2, [r5, #4]           @ restore arg2
    ldr     r3, [r5, #8]           @ restore arg3
    ldr     r5, [sp, #(40 + 20 + 12)] @ pass ip to entrypoint
    blx     r5
    add     sp, #(40 + 20)
    pop     {r4, r5, pc}           @ return on success, r0 and r1 hold the result
 END art_quick_call_entrypoint

在運行這段匯編之前,寄存器中的內容分布是第一種

這段匯編代碼的邏輯是
1.保存環境,以便運行完成之后返回,執行下一條指令。
2.保存r0,r1,r2,r3寄存器中的內容
3.調用memcpy申請一個空間。memcpy的參數是dest,src,size_of_byte,所以在調用memcpy之前,首先要將函數的參數存儲到r0,r1,r2中。
4.重新給r0,r1,r2,r9等寄存器賦值,使得寄存器按照第二種方式。
5.調用entrypoint。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,646評論 6 533
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,595評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,560評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,035評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,814評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,224評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,301評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,444評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,988評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,804評論 3 355
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,998評論 1 370
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,544評論 5 360
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,237評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,665評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,927評論 1 287
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,706評論 3 393
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,993評論 2 374

推薦閱讀更多精彩內容

  • 8086匯編 本筆記是筆者觀看小甲魚老師(魚C論壇)《零基礎入門學習匯編語言》系列視頻的筆記,在此感謝他和像他一樣...
    Gibbs基閱讀 37,304評論 8 114
  • 王爽匯編全書知識點大綱 第一章 基礎知識 機器語言 匯編語言的產生 匯編語言的組成 存儲器 cpu對存儲器的讀寫 ...
    2c3ba901516f閱讀 2,431評論 0 1
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,665評論 25 708
  • ai安閱讀 169評論 0 0
  • 今天去學校給初中競賽的學生監考,沒開考前我們在一起等待,我無聊的看向周圍,看到一位美女老師和同事說話,我盯著她看,...