API-HOOK and ANTI-API-HOOK For Ring3

/*

看雪精華文章,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在導入表的偏移)
-$000467cd下的代碼就是:
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
一般掛鉤法就是修改前5個字節
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);
}
2.反內存補丁(着重介紹這裏)

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;
判斷第一個字節是否是0xFF,你也可以根據一些特別的掛鉤修改
例如有的是直接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
第一種長度2字節,第二種長度1字節.一般說來程序第一個字節都不太可能是
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;
}
當指令長度爲1~機器碼爲C3才認爲結束...許多stdcall都是自己恢復堆棧平衡的
...這裏友情提示一下吧...自己編程的時候要小心.至於如何修改?嘿嘿..
很容易我就不多說了.省得別人說我雞婆...

使用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]
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章