/*
看雪精華文章,http://bbs.pediy.com/showthread.php?t=37586。
概括性介紹了API-HOOK的知識。
*/
標 題: 【原創】API-HOOK and ANTI-API-HOOK For Ring3
作 者: Anskya時 間: 2007-01-07,20:56:49
鏈 接: http://bbs.pediy.com/showthread.php?t=37586
<<API-HOOK and ANTI-API-HOOK For Ring3>>
轉載請保留版權.謝謝
[email protected]
今天突然看到"墮落天才"仁兄的兩篇文章
感謝他的的文章和共享精神.謝謝...突然手癢..有感而發
API-HOOK和ANTI-API-HOOK已經不算什麼新鮮的技術了
一般大概用的技術都差不多
[1]簡要介紹API-HOOK
1.IAT補丁
介紹:
一般調用函數都是call [MessageBoxA]這樣的格式
很明顯[MessageBoxA]下的地址就是函數的真正的地址
Delphi: push 0 push 0 push 0 push 0 call -$000467cd(這裏是MessageBox在導入表的偏移)
jmp dword ptr [$004514b0]
004514b0下的地址是---77D504EA(剛好就是MessageBoxA的地址)
IAT補丁的意思就是修改jmp dword ptr [$004514b0]這句爲自己的鉤子地址
然後鉤子返回的時候返回77D504EA地址
(這裏用Delphi的Debug是因爲順手.還有一點就是..VC看不到棧值)
優點:簡單...
缺點:如果是動態調用的函數導入表中是不會出現這種函數的
所以就出現了下面的技術
2.內存補丁:
介紹:
由於IAT的缺點於是牛們就想到了動態修改DLL函數的內存
還是以MessageBoxA爲例子:
77D504EA > 8BFF MOV EDI,EDI 77D504EC 55 PUSH EBP 77D504ED 8BEC MOV EBP,ESP 77D504EF 833D BC04D777 0>CMP DWORD PTR DS:[77D704BC],0 77D504F6 74 24 JE SHORT USER32.77D5051C 77D504F8 64:A1 18000000 MOV EAX,DWORD PTR FS:[18] 77D504FE 6A 00 PUSH 0 77D50500 FF70 24 PUSH DWORD PTR DS:[EAX+24] 77D50503 68 240BD777 PUSH USER32.77D70B24 77D50508 FF15 C812D177 CALL DWORD PTR DS:[<&KERNEL32.Interlocke>; kernel32.InterlockedCompareExchange 77D5050E 85C0 TEST EAX,EAX 77D50510 75 0A JNZ SHORT USER32.77D5051C 77D50512 C705 200BD777 0>MOV DWORD PTR DS:[77D70B20],1 77D5051C 6A 00 PUSH 0 77D5051E FF75 14 PUSH DWORD PTR SS:[EBP+14] 77D50521 FF75 10 PUSH DWORD PTR SS:[EBP+10] 77D50524 FF75 0C PUSH DWORD PTR SS:[EBP+C] 77D50527 FF75 08 PUSH DWORD PTR SS:[EBP+8] 77D5052A E8 2D000000 CALL USER32.MessageBoxExA 77D5052F 5D POP EBP 77D50530 C2 1000 RETN 10
mov edi,edi
push ebp
mov ebp,esp
剛好是5個字節.jmp到HookProc的地址
然後再調回到MessageBoxA+5的地方...
優點:比較實用
缺點:Ring3很好用...如果非要弄個缺點就是
有的時候函數代碼頭部未必是
mov edi,edi
push ebp
mov ebp,esp
許多API函數的頭部都是這樣的但是一些cdecl調用格式
或者非stdcall格式的函數無法掛鉤.但是配合脫鉤一起用會發現
效果不錯...可以當bpx用..再配合一點彙編知識就可以獲取寄存器數據等
3.深入上面的
有許許多多的什麼陷井技術,棧填寫返回地址等...RelocationTable掛鉤技術
其實就是內存補丁技術...不過用了不同的方法填寫返回地址而已
4.SEH or VEH掛鉤
填寫Int3,Int1等指令讓程序產生異常然後調轉到鉤子執行過程.
個人很習慣這種方法VEH玩過一下...但是由於兼容性不強.你可以在HookSpy的代碼
找到VEH的代碼和相關應用.配合着調試API用起來也很過癮
至少你不用DebugActiveProcess函數去掛接進程
直接利用CreateRemoteThread函數注入DLL.注入方法很多看個人喜好
具體查看羅聰前輩的<<用 SEH 技術實現 API Hook>>
VEH技術實現最近打算也寫一篇...正在孕釀(關於着方面的知識太少了)
有個地方需要注意~SEH和VEH有點不太好調試~許多異常都會碑調試器捕獲到
所以最好用OD或者專用的調試器...編程工具自帶的調試器會捕獲所有的異常
5.調試寄存器
不多說了看EliCZ叔叔的文章和代碼吧
說了一大對無聊的東西現在來說說反調試的問題
[2]常用的ANTI-APIHOOK技術
1.IAT-API-HOOK
由於修改的導入表地址.
最簡單的方法就是你需要的函數全部使用GetProcAddress函數來獲取
typedef int(*TMessageBoxA)(HWND hWnd, LPCTSTR lpText, LPCTSTR lpCaption, UINT uType); void __fastcall TForm1::Button1Click(TObject *Sender) { TMessageBoxA MsgBox; MsgBox = (TMessageBoxA)GetProcAddress(LoadLibrary("user32.dll"), "MessageBoxA"); MsgBox(0, 0, 0, 0); }
1.相信許多人都用過Madshi的madCollection
通過跟蹤發現他是
一般掛鉤都是修改前5個字節
77D504EA >- FF25 1E00055F JMP DWORD PTR DS:[5F05001E]--被補丁了. 77D504F0 3D BC04D777 CMP EAX,user32.77D704BC 77D504F5 007424 64 ADD BYTE PTR SS:[ESP+64],DH 77D504F9 A1 18000000 MOV EAX,DWORD PTR DS:[18] 77D504FE 6A 00 PUSH 0 77D50500 FF70 24 PUSH DWORD PTR DS:[EAX+24] 77D50503 68 240BD777 PUSH user32.77D70B24 77D50508 FF15 C812D177 CALL DWORD PTR DS:[<&KERNEL32.Interlocke>; kernel32.InterlockedCompareExchange 77D5050E 85C0 TEST EAX,EAX 77D50510 75 0A JNZ SHORT user32.77D5051C 77D50512 C705 200BD777 0>MOV DWORD PTR DS:[77D70B20],1 77D5051C 6A 00 PUSH 0 77D5051E FF75 14 PUSH DWORD PTR SS:[EBP+14] 77D50521 FF75 10 PUSH DWORD PTR SS:[EBP+10] 77D50524 FF75 0C PUSH DWORD PTR SS:[EBP+C] 77D50527 FF75 08 PUSH DWORD PTR SS:[EBP+8] 77D5052A E8 2D000000 CALL user32.MessageBoxExA 77D5052F 5D POP EBP 77D50530 C2 1000 RETN 10
bool IsHook(char *lpChar) { if(*lpChar == 0xFF) return true; } function IsHook(lpFunc: Pointer): Boolean; begin Result := False; if (Char(lpFunc^)=#$FF) then Result := True; end;
例如有的是直接jmp 調轉到直接的地址...E9.或者call-F8(機器碼)
2.棧保存地址..
和上面一樣不過這裏有一點點區別掛鉤方式被修改成
77D504EA > 68 04BF4000 PUSH 40BF04------這裏被寫成鉤子過程地址 77D504EF C3 RETN-------------返回 77D504F0 3D BC04D777 CMP EAX,user32.77D704BC 77D504F5 007424 64 ADD BYTE PTR SS:[ESP+64],DH 77D504F9 A1 18000000 MOV EAX,DWORD PTR DS:[18] 77D504FE 6A 00 PUSH 0 77D50500 FF70 24 PUSH DWORD PTR DS:[EAX+24] 77D50503 68 240BD777 PUSH user32.77D70B24 77D50508 FF15 C812D177 CALL DWORD PTR DS:[<&KERNEL32.Interlocke>; kernel32.InterlockedCompareExchange 77D5050E 85C0 TEST EAX,EAX 77D50510 75 0A JNZ SHORT user32.77D5051C 77D50512 C705 200BD777 0>MOV DWORD PTR DS:[77D70B20],1 77D5051C 6A 00 PUSH 0 77D5051E FF75 14 PUSH DWORD PTR SS:[EBP+14] 77D50521 FF75 10 PUSH DWORD PTR SS:[EBP+10] 77D50524 FF75 0C PUSH DWORD PTR SS:[EBP+C] 77D50527 FF75 08 PUSH DWORD PTR SS:[EBP+8] 77D5052A E8 2D000000 CALL user32.MessageBoxExA 77D5052F 5D POP EBP 77D50530 C2 1000 RETN 10
對付這種掛鉤方式...方法一般2種
分析一下他是如何掛鉤的吧...首先他要先獲取你要掛鉤的函數地址
填寫前六個字節
1.你提前掛鉤...然後讓他掛你的鉤子.
這樣你直接調用你自己的返回地址就好了最好可以多複製一點.
另外說一點.許多API函數都是調用xxxxW或者xxxxEx什麼的
所以一般A系函數都很短你甚至可以直接複製到自身進程裏面去執行
具體代碼和資料看下面的描述
2.脫鉤.
這裏代碼很多.幾乎所有的API-Hook library都有這個函數
自己看一下吧.我就不多說了
3.利用反彙編引擎搜索鉤子返回地址!Cool.直接調用返回函數!!
由於掛鉤的時候會把修改的代碼轉移到別的地方
掛鉤需要使用6個字節的空間.所以掛鉤的時候就需要計算
需要多長的指令...(所以一般CodeHook Library裏面都自帶一個長度反彙編引擎)
看個人喜好...看你用的是什麼庫了(29A等許多病毒代碼裏面都有許多)
常用的:
Opcode Length Disassembler Coded By Ms-Rem(ASM,C,Delphi版本)
Length Disassembler Engine By Zombie
其它的還有.剛在WASM上發現一個新的,沒有用過反正就那兩種原理
也沒有多少測試...好了下面說說如何尋找返回地址
鉤子都是需要返回地址的.不然這個函數就會被屏蔽.無效代碼
可是返回地址如何獲取呢???既然是掛鉤那就肯定有一個返回地址..
不然他自己怎麼調用API函數?
這裏引入一個疑問...
返回地址一般都是最後~由於是跨段代碼調轉,所以搜索ret後最後
一個跨段調轉即可...
1.判斷是否被掛鉤,
一般API函數開頭都是
mov edi, edi push ebp mov ebp, esp or push ebp mov ebp, esp
E9,E8,FF之類的(爲什麼很少看到enter這個指令?據說有BUG?)
一旦超過5個字節就認爲他被掛鉤了!
2.確定他複製走了多少地址
(由於需要六個字節,但是前面三條指令只有5個字節.他就從第4行開始動手,
然後下來就是.開始計算了.)
祭出LDE或者OLD.
可以確定第一行指令是,push 0x00000000,jmp 0x00000000
這樣的東東...如果有哪位仁兄使用了比較另類的掛鉤指令的話.那就需要特殊處理
了
SizeOfCode(void *Code, unsigned char **pOpcode);返回指令長度
SizeOfProc(void *Proc);獲取過程長度.-她會從指針開頭反彙編.直到遇到ret
(友情提示.代碼有個BUG)
unsigned long __fastcall SizeOfProc(void *Proc) { ULONG Length; PUCHAR pOpcode; ULONG Result = 0; do { Length = SizeOfCode(Proc, &pOpcode); Result += Length; if ((Length == 1) && (*pOpcode == 0xC3)) break; Proc = (PVOID)((ULONG)Proc + Length); } while (Length); return Result; }
...這裏友情提示一下吧...自己編程的時候要小心.至於如何修改?嘿嘿..
很容易我就不多說了.省得別人說我雞婆...
使用SizeOfCode函數獲取指令長度直到大於6或者等於6
如果第一行就是5個字節.取代碼指針.
3.既然已經獲取到了地址
稍微跟蹤一下就會發現函數返回的地址是ret往上第一條指令!也就是最後一條指令
由於是跨段調轉所以會使用call[]---機器碼FF的指令
function GetProcAddressEx(Proc: Pointer): Pointer; var lpCallRet: Pointer; iCodeLen: Integer; begin Result := nil; lpCallRet := nil; iCodeLen := SizeOfCode(Proc); // 判斷第一行代碼是否爲push 0xXXXXXXXX if (iCodeLen = 5) and (Byte(Proc^) = $68) then begin // 獲取0xXXXXXXXX Proc := Pointer(PDWORD(longword(Proc) + 1)^); while True do begin iCodeLen := SizeOfCode(Proc); // 判斷是否爲call dword ptf[0xXXXXXXXX] if (iCodeLen = 6) and (Byte(Proc^) = $FF) and (Byte(Pointer (longword(Proc) + 1)^) = $15) then begin // 獲取0xXXXXXXXX lpCallRet := Proc; Break; end; // 函數結尾 if (Byte(Proc^) = $C3) then Break; Proc := pointer(longword(Proc) + iCodeLen); end; end; if lpCallRet <> nil then begin Result := Pointer(PDWORD(PDWORD(longword(lpCallRet) + 2)^)^); end; end;
這篇文章完全是一篇回憶錄...以前寫的代碼和資料全部丟失了..
這裏使用的是Ms-Rem的Opcode Length Disassembler Engine
這裏描述的是如何bypass 堆跳轉掛鉤模式...jmp和call調轉模式
都一樣的原理和代碼可以實現.大家可以自己模擬作一下.關鍵是思路
大家有什麼好的方法希望提供...
OLD引擎您可以從Ms-Rem的process_hunter Src中獲取到
www.wasm.ru
再次感謝許許多多的知名的不知名的大俠們的文章和代碼.感謝
thank:EliCZ,Madshi,Ms-Rem,Aphex...29A Group
轉載請保留版權.謝謝
[email protected]