Inline HOOK API 改進版(hot-patching)

記得在之前寫過一篇hook api的文章(C/C++ HOOK API(原理深入剖析之-LoadLibraryA)),那篇文章主要原理是構造一塊代碼字節,將LoadLibraryA函數的前面16個字節給修改,然後跳轉到自定義的函數中。要調用正常的函數時,又將其unHook,這樣一來再一次調用中,有一次unhook和一次hook,操作顯得過於頻繁。而且hook與unhook當時設計成了thiscall,因此維護傳遞this的寄存器(通常是ecx)就成了必然,再加上參數的傳遞,__Inline_Hook_Func函數的邏輯就顯得有點複雜和臃腫。

 

就以上情況,本文打算較之前的邏輯做個改進,拋棄掉this和參數的維護,利用windows爲多數API預留的前五個字節的nop空間和mov edi, edi佔用的兩個字節來實現inline hook。即所謂的hot-patching技術,運行時修改函數的行爲,同時不破壞函數的主體邏輯,因此免去了hook與unhook不斷切換,在線程安全上也有很好的效果。

 

本文還是以LoadLibraryA函數爲例,先看LoadLibraryA的反彙編:

7602285F  nop             
76022860  nop             
76022861  nop             
76022862  nop             
76022863  nop             
76022864  mov         edi,edi
76022866  push        ebp 
76022867  mov         ebp,esp
76022869  cmp         dword ptr [ebp+8],0
7602286D  push        ebx 
7602286E  push        esi 
7602286F  push        edi 
76022870  je          7602288A
76022872  push        760228A0h
76022877  push        dword ptr [ebp+8]
7602287A  call        dword ptr ds:[75FD12E4h]
76022880  pop         ecx 
76022881  pop         ecx 
76022882  test        eax,eax
76022884  je          7603F39F
7602288A  push        0   
7602288C  push        0   
7602288E  push        dword ptr [ebp+8]
76022891  call        76022859
76022896  pop         edi 
76022897  pop         esi 
76022898  pop         ebx 
76022899  pop         ebp 
7602289A  ret         4  

 

如上,前面紅色的部分即是可以靈活操作的部分,mov edi,edi兩個字節可以替換成一個short jmp,5個nop字節可以替換成一個長跳。

藍色的0x76022864地址是LoadLibraryA的入口,如果將這裏改成short jmp後,要調用正常的LoadLibraryA,則只需要在此基礎上加2個字節的偏移進行call即可。清楚了原理,先動手吧,用C++實現,先寫一個類。

 

這個InlineHookHolder構造時,便將目標函數進行hook操作,這個操作將mov edi,edi替換成jmp XX,這裏是相對偏移,偏移量爲7字節(5個nop字節加上本身2個字節),所以有:
pSrcFunc[ 5 ]       = 0xEB;     // short jmp
pSrcFunc[ 6 ]       = 0xF9;     // short jmp offset: -7

0xEB即是short jmp的機器碼,0xF9即偏移,這裏是負數,向前偏移7個字節。

 

至於5個nop即替換成了jmp 0x???????。因此有:

( int& )pSrcFunc[1] = ( int )dst_jmp_func - ( int )pSrcFunc - 5; // 跳轉的偏移,目標地址與當前地址的差值+jmp本身的5個字節
pSrcFunc[ 0 ]       = 0xE9;   // 長jmp 的機器碼

 

在構造函數修改之後,析構函數負責unhook,在其間的過程中都不需要操作。

寫好了類,然後再寫跳轉到的自定義函數,代碼如下:

 

 

如上面代碼,LOADER_CAST宏用於將API的入口地址加上2,避免又跳轉到自定義hook函數裏面了。

 

由於LoadLibraryA是__stdcall(WINAPI宏),會在內部ret時保持堆棧平衡,因此我們自定義的函數也保持這個規則,不然會導致堆棧不平衡。WINAPI_FUNC宏就是爲了遵循這個規則而定義的,免得粗心忘了加__stdcall了。

 

DECLARE_HOOK_HOLDER宏是聲明定義一個InlineHookHolder對象,將原函數和目標函數傳入構造函數進行hook操作。我們可以將這個對象聲明成全局的,這樣在程序退出時調用析構進行unhook,_ReadWriteVPHolder類也是爲了RAII的機制。

 

最後調用代碼可以簡單如下:

 

當然在MyLoadLibraryA裏就可以做一些我們想做的事情了,比如檢測你加載的DLL是否合法等,這對於比較簡單的遊戲反外掛上有一定的作用。本文就只拋磚引玉介紹下原理吧。

 

 

與之前的版本比較,這種方法只能應用於預留了5個nop和mov edi,edi這7個字節的API函數,不建議強制使用這種方式,視情況而定。再者,在本文中沒有涉及手工編寫內嵌彙編代碼,邏輯簡單且更清晰,在效率上也較高一些,安全性方面也要好一些。不過之前的方法對於追究堆棧調用模型很有好處。

 

本文原理比較簡單,就先介紹到此,歡迎大牛拍磚。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章