記得在之前寫過一篇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函數,不建議強制使用這種方式,視情況而定。再者,在本文中沒有涉及手工編寫內嵌彙編代碼,邏輯簡單且更清晰,在效率上也較高一些,安全性方面也要好一些。不過之前的方法對於追究堆棧調用模型很有好處。
本文原理比較簡單,就先介紹到此,歡迎大牛拍磚。