//Author:Alex(Yock.W)
//轉(zhuǎn)載請署名出處
前言 基礎(chǔ)知識
本系列文章會對常用的幾種API HOOK方法進(jìn)行全面的分析。
Hook是什么?
鉤子(Hook),是Windows消息處理機(jī)制的一個平臺,應(yīng)用程序可以在上面設(shè)置子程以監(jiān)視指定窗口的某種消息,而且所監(jiān)視的窗口可以是其他進(jìn)程所創(chuàng)建的。當(dāng)消息到達(dá)后,在目標(biāo)窗口處理函數(shù)之前處理它。鉤子機(jī)制允許應(yīng)用程序截獲處理window消息或特定事件。
鉤子實際上是一個處理消息的程序段,通過系統(tǒng)調(diào)用,把它掛入系統(tǒng)。每當(dāng)特定的消息發(fā)出,在沒有到達(dá)目的窗口前,鉤子程序就先捕獲該消息,亦即鉤子函數(shù)先得到控制權(quán)。這時鉤子函數(shù)即可以加工處理(改變)該消息,也可以不作處理而繼續(xù)傳遞該消息,還可以強(qiáng)制結(jié)束消息的傳遞。
Hook原理
每個Hook都有一個關(guān)聯(lián)的鏈表,由系統(tǒng)維護(hù),鏈表指針指向被Hook子程調(diào)用的回調(diào)函數(shù):
LRESULT WINAPI HookCallBack(
int nCode,
WPARAM wParam,
LPARAM lParam);
nCode 事件代碼
wParam 消息的類型
lParam 包含的消息
當(dāng)被Hook的類型關(guān)聯(lián)的消息發(fā)生時,系統(tǒng)會把這個消息傳遞到鏈表指針指向的Hook子程,由子程內(nèi)部對消息進(jìn)行處理,。
鉤子安裝與卸載
使用Windows提供的API SetWindowsHookEx來將Hook子程指針安裝到Hook鏈表,安裝的Hook子程始終在Hook鏈表頭部。
HHOOK SetWindowsHookEx(
int idHook,
HOOKPROC lpfn,
HINSTANCE hMod,
DWORD dwThreadId);
idHook 安裝的鉤子類型
lpfn 子程函數(shù)指針
hMod 應(yīng)用程序?qū)嵗木浔?br>dwThreadId Hook關(guān)聯(lián)的線程標(biāo)識符
當(dāng)被Hook的消息類型發(fā)生時,系統(tǒng)會調(diào)用與這個Hook關(guān)聯(lián)的鏈表頭的Hook子程。由子程序決定是否要把這個事件傳遞給下個子程。Hook子程傳遞事件給下一個Hook子程需要調(diào)用使用Windows提供的API CallNextHookEx。
LRESULT CallNextHookEx(
HHOOK hhk,
int nCode,
WPARAM wParam,
LPARAM lParam);
hhk 當(dāng)前鉤子的句柄,由SetWindowsHookEx返回
nCode 事件代碼
wParam 消息的類型
lParam 包含的消息
鉤子在使用完,需調(diào)用UnHookWindowsHookEx來卸載。
UnHookWindowsHookEx(
HHOOK hhk);
Api Hook 的幾種方法
一.修改函數(shù)過程映射的地址空間
Api Hook:
我們都知道,在調(diào)用函數(shù)的時候,會先Call 0xFFFFFF,來到函數(shù)映射在內(nèi)存的地址空間里,這時候,傳入的參數(shù)已入棧,那么,我們只需要修改函數(shù)頭部,跳轉(zhuǎn)到我們自定義的過程,這樣,我們就達(dá)到了對API函數(shù)監(jiān)視的目的。那么,我們需要多大的空間來寫入代碼讓其跳轉(zhuǎn)到我們自定義的過程里呢?
聰明的你肯定已經(jīng)想到了。沒錯,只需5字節(jié)即可。Why? 看一下代碼:
jmp 0xFFFFFFFF
jmp 指令占1字節(jié),地址是4字節(jié),所以,只需5字節(jié)即可。
有的看官可能會問了,為什么不用Call呢?
因為Call指令在實現(xiàn)跳轉(zhuǎn)以后會通過ret返回到Call下面的指令繼續(xù)執(zhí)行,而Jmp指令實現(xiàn)跳轉(zhuǎn)以后則全有子程來控制了。
我們知道了通過以上方法來修改函數(shù)頭部數(shù)據(jù)來達(dá)到我們的目的,那么,我們先要得到指向函數(shù)頭部的地址,通過LoadLibrary和GetProcAddress即可獲取到我們要監(jiān)視的函數(shù)在內(nèi)存中映射的地址指針。我們以MessageBoxA為例:
typedef int (__stdcall *TMessageBoxA)(HWND hWnd,LPSTR lpText,LPSTR lpCaption,int uType);



extern "C" TMessageBoxA __fastcall GetPFuncAddr(LPSTR DllName,const char* FullName);



TMessageBoxA __fastcall GetPFuncAddr(LPSTR DllName,const char* FullName)


{
void *DllModule;
DllModule = LoadLibrary(DllName);
TMessageBoxA PAddr = (TMessageBoxA)GetProcAddress(DllModule,FullName);
return PAddr;
}
在得到了指向內(nèi)存映射里函數(shù)頭部的指針以后,我們下一步,來讀出函數(shù)頭部的5字節(jié)。為什么要讀出函數(shù)頭部的5字節(jié)呢?呵呵,當(dāng)然是留作恢復(fù)時寫會數(shù)據(jù)了。ReadProcessMemory:
BOOL ReadProcessMemory(
HANDLE hProcess,
LPCVOID lpBaseAddress,
LPVOID lpBuffer,
DWORD nSize,
LPDWORD lpNumberOfBytesRead);
hProcess 進(jìn)程句柄
lpBaseAddress 讀內(nèi)存開始地址
lpBuffer 保存數(shù)據(jù)的指針
nSize 數(shù)據(jù)大小
lpNumberOfBytesRead //address of number of bytes read
BYTE *pFuncData = new BYTE[5];
LPDWORD iRead;

ReadProcessMemory((void *)GetCurrentProcess(),PMessageBoxA,pFuncData,5,iRead);

保存原函數(shù)入口點的數(shù)據(jù)以后,嘿嘿,當(dāng)然是寫入指向我們自己的函數(shù)地址了。
BYTE AsmCode[4];

AsmCode[0] = 0xE9;
__asm


{
lea eax, MyMessageBoxA
mov ebx,PMessageBoxA
sub eax,ebx
sub eax,5
mov dword ptr[AsmCode+1],eax
}
WriteProcessMemory((void *)GetCurrentProcess(),PMessageBoxA,AsmCode,5,iWrite);

MyMessageBoxA是我們自己構(gòu)造的函數(shù):
int WINAPI MyMessageBoxA(HWND hWnd,LPSTR lpText,LPSTR lpCaption,int uType)


{
//我們來彈出一個消息框,改變一下傳入的標(biāo)題參數(shù)值
//那么首要做的,就是先恢復(fù)MessageBoxA
WriteProcessMemory((void *)GetCurrentProcess(),PMessageBoxA,pFuncData,5,iWrite);
int Result = MessageBoxA(hWnd,lpText,"ApiHook Test",uType);
WriteProcessMemory((void *)GetCurrentProcess(),PMessageBoxA,AsmCode,5,iWrite);
return Result;
}
恢復(fù)API的原入口處數(shù)據(jù):
WriteProcessMemory((void *)GetCurrentProcess(),PMessageBoxA,pFuncData,5,iWrite);
防止修改函數(shù)入口數(shù)據(jù)來進(jìn)行API HOOK的方法:
在程序啟動的時候保存關(guān)鍵API入口的5字節(jié),定期檢查一次,發(fā)現(xiàn)被修改了,改回即可。
----------------------------------------------------------------
代碼回頭附上,碼字太累了,前言浪費了點時間,#- - 感覺和掃盲差不多……
下一篇中,會針對SEH技術(shù)實現(xiàn)API HOOK和如何防止SEH APIHOOK進(jìn)行詳解。
掃盲到此結(jié)束。