天天看點

一種簡單的java函數hook思路

一種簡單的java函數hook思路

總結

本文取自《深入了解android java虛拟機art》第10章,在讀書過程中總結的一些能讓自己豁然開朗的點,通過這些點我發現了hook java函數的方法,有了此文。

LinkCode

LinkCode函數當中有個重要的過程,就是GetEntryPointFromQuickCompiledCode函數,他的傳回值也就是quick_code為空的話代表該方法沒有進行編譯,在oat檔案當中沒有對應的彙編代碼,需要進入解釋執行模式而不是jit,這裡有一個分支,如果ArtMethod是jni方法的話會将類似于elf檔案的ENTRYPOINT 設定為art_quick_generic_jni_trampoline,普通方法會設定為art_quick_to_interpreter_bridge,如下表(取自該書565頁),這裡是關鍵代表着我們可以通過修改這裡來完成java或jni函數的跳轉,也就是每次art虛拟機解釋執行某個java函數的時候,都會跳轉到該java函數的EntryPoint,那麼隻要把這裡替換成我們自己的c函數的位址,就可以實作攔截java函數了,當然也要保留原函數的 執行過程。

一種簡單的java函數hook思路

方案思考

了解了java函數大緻的執行流程,下一補就是實操了,那麼怎麼更改一個java函數的EntryPoint呢?這是一個困難的問題,首先提出一點

1:是否可以通過hook關鍵art函數的方式來獲得ArtMethod?

當然可以,但是我們要hook的是自身的dex,它的加載時機要早于我們的so加載進記憶體中,是以像LinkCode或者LoadMethod這種在dex加載流程上的函數是不行的,那就需要找一個系統函數,既有我們的函數的ArtMethod,又是在dex加載完成之後才進行的,這個就很難辦了,首先通過函數名過濾ArtMethod就很難,其次dex加載完成之後也很難找到覆寫所有ArtMethod的函數,是以這種方法不可行

2:是否可以像frida一樣Java choose記憶體搜尋ArtMethod?

倒是可以通過java.lang.reflect.Method這個類來進行記憶體漫遊,直接把Method類的執行個體全找出來,高版本可以再通過它父類的方法getArtMethod将它轉化為ArtMethod的指針,但是記憶體漫遊太難實作,可能一會會參考frida實作一下,目前不考慮,是以這種也不行

3:最終方案

最終的方案其實和frida差不多,回顧一下frida如何hook的java函數,先Java.use拿到類,然後用implementation直接覆寫原函數,那麼我們也可以參考jni反射執行java函數的思路,通過FindClass拿到類,再通過GetMethodID的方式拿到一個jmethodID

var MainActivity = Java.use("xxxxx");
        MainActivity.xxxxx.implementation=function(){
            return xxxx;        
        }
           

失敗的案例

那麼已經拿到jmethodID了,要如何轉化為ArtMethod呢,這一點可以通過安卓源碼來檢視在

http://androidxref.com/8.1.0_r33/xref/art/runtime/jni_internal.h

目錄,可以看到,它隻是做了一個強制類型轉換,那麼就相當于我們拿到了我們想hook函數對應的ArtMethod

static inline ArtMethod* DecodeArtMethod(jmethodID method_id) {
  return reinterpret_cast<artmethod*>(method_id);
}
           

現在把他的EntryPoint改成我們想要的函數就好了,那麼就又産生了2個問題

1.EntryPoint在哪我如何能找到它

慶幸的是安卓源碼中有提供函數,也就是上面提到的GetEntryPointFromQuickCompiledCode那麼隻要通過ida檢視它的實作就好了,遺憾的是這是一個inline函數,沒有辦法使用ida檢視,隻能通過自己修改源碼,來檢視偏移了,然後直接脫出libart.so,檢視導出函數getmynative就好了

//ArtMethod.cc
extern "C"  const void * getmynative(ArtMethod* m)REQUIRES_SHARED(Locks::mutator_lock_){

m->SetEntryPointFromQuickCompiledCode(GetQuickGenericJniStub());
m->UnregisterNative();
const void* entry_point = m->GetEntryPointFromQuickCompiledCode();
if(m->IsFastNative())
return GetQuickGenericJniStub();
return entry_point;

}
           

這樣就知道了,偏移是40,那麼直接更改就好了

一種簡單的java函數hook思路

2.直接改會少很多系統函數的調用能否成功

這個從理論上來說,是一定不成功的,看書上後面還有許多的過程,包括參數、棧的儲存,都沒有做,一定不可能成功,但是也不妨試一下,沒準歪打正着了呢,第一版代碼如下

void* func(void* a,void* b,int c,int d){

    __android_log_print(6,"r0ysue","i am success");
    return reinterpret_cast<void *="">(5);

}


jclass a=env->FindClass(classname);
jmethodID b=env->GetMethodID(a,methodname, shoty);
*((long *)a1 + 5)= reinterpret_cast<long>(func);
           

居然沒拉稀,和之前猜的不太一樣,雖然成功執行了我們自己的函數,但是要思考一個問題就是如何執行原函數,這裡如果通過反射直接執行的話,我們拿不到參數是以這種方案有點垃圾,我們還需要繼續改進

一種簡單的java函數hook思路

進階

既然上面的方案有瑕疵,就是有很多函數沒有執行,導緻我們拿不到參數,那麼可以采取下一個,就是模仿Native函數的方式,将本函數改成Native函數,然後再使用将Native函數注冊就好了,那麼根據上面第一節的内容隻要将EntryPoint改成art_quick_generic_jni_trampoline,就好了,再次用ida打開libart.so,發現art_quick_generic_jni_trampoline是一個函數,但它不是一個導出函數,使用導出表沒有辦法找到它,那麼就用上篇文章提到的節頭表索引發來搞定,最後再用RegisterNatives注冊函數位址就好了

int so=findsym("/system/lib64/libart.so","art_quick_generic_jni_trampoline");
    *((long *)a1 + 5)= reinterpret_cast<long>((char *) startr +so - 0x25000);//需要-0x25000是因為libart.so的程式頭在偏移為0x25000這裡沒周遊偷懶了
    env->RegisterNatives(a,getMethods,1);
           

這樣就完成了Java函數的Native化,那麼試驗一下好不好使,果然不行提示不是native函數無法完成注冊,那麼就看一下如何的判斷是否是Native函數,之前編譯rom的時候我就加入了,Native相關的判斷,可以看到,公式

(~*(_DWORD *)(a1 + 4) & 0x80100)

,就是判斷是否是native函數的,隻要它不等0,那麼就可以認為我們的函數是Native函數.

一種簡單的java函數hook思路

那麼我們就可以構造了,直接做異或就好了

*(_QWORD *)(a1 + 4)= *(_QWORD *)(a1 + 4)^0x80100 ;
           

這樣準備就完成了,我們就可以像動态注冊的jni函數一樣,去實作我們的c層的add函數

,執行就正常了,這裡不給大家貼圖了,因為還有最後一個問題沒解決,就是如何執行原函數。

實作原函數

c調用java函數隻有一種方式,那就是反射,是以我們隻能用反射,那麼就要收集參數了,比如我下面的這個函數,我需要一個執行個體,一個env,2個參數

public int add2(int a,int b){
        return 7;
    }

           

幸運的是,我們是動态注冊的,動态注冊的執行個體函數會自帶env和this執行個體,是以我們直接調用就好了

void* add(void* a,void* b,int c,int d){

    JNIEnv *st=(JNIEnv *)a;
    jclass a2=st->FindClass("com/r0ysue/myjavahook/MainActivity");
    jmethodID b2=st->GetMethodID(a2,"add2", "(II)I");
   int yy= st->CallIntMethod(static_cast<jobject>(b), b2, c, d);
   __android_log_print(6,"r0ysue","%x",yy);
    return reinterpret_cast<void *="">(5);

}
           

直接死循環爆棧了........,太垃圾了,還是思路沒找好,想想也是,我沒有将函數恢複就直接反射,肯定會再次調用的,是以要想一個辦法将ArtMethod恢複為當初java函數的樣子,這裡我又用了全局變量(沒錯就是inlinehook 時候坑了我那麼久的東西,是以一會還要解決多函數的hook問題)

一種簡單的java函數hook思路

這裡方案是這樣的,将hook函數中儲存的jump和nativ,直接複原,但是這裡我沒想好怎麼搞,是以baocun函數要一個參數,就是ArtMethod指針,儲存完還要回複到執行Native函數的狀态

void hook(){

....
   nativ=*(_QWORD *)(a1 + 4);//isNative()?
    jump=*((long *)a1 + 5);//EntryPoint
....

}

void huifu(__int64 a,int n){

    *((long *)a + 5)=old1;
    *(_QWORD *)(a + 4)=old2;

}

void baocun(__int64 a){
 old1=*((long *)a + 5);
 old2=*(_QWORD *)(a + 4);
    *((long *)a + 5)= jump;
    *(_QWORD *)(a + 4)= nativ|0x80100;
}
void* add(void* a,void* b,int c,int d){

    JNIEnv *st=(JNIEnv *)a;
    jclass a2=st->FindClass("com/r0ysue/myjavahook/MainActivity");
    jmethodID b2=st->GetMethodID(a2,"add2", "(II)I");
  baocun((__int64)b2);
   int yy= st->CallIntMethod(static_cast<jobject>(b), b2, c, d);
   __android_log_print(6,"r0ysue","%x",yy);
   huifu((__int64)b2)
    return reinterpret_cast<void *="">(5);

}


           

hook 多個函數

void baocun(__int64 a,int n){


 old1[n]=*((long *)a + 5);
 old2[n]=*(_QWORD *)(a + 4);

    *((long *)a + 5)= jump[n];
//    *(_QWORD *)(a + 4)=0x1d8ee408000101;
    *(_QWORD *)(a + 4)= nativ[n]|0x80100;
}

void huifu(__int64 a,int n){

    *((long *)a + 5)=old1[n];
    *(_QWORD *)(a + 4)=old2[n];

}
void hook(){
...
    nativ[ns]=*(_QWORD *)(a1 + 4);
    jump[ns]=*((long *)a1 + 5);

...
}