//Author:Alex(Yock.W)
//轉載請署名出處

前言 基礎知識

本系列文章會對常用的幾種API HOOK方法進行全面的分析。

Hook是什么?

鉤子(Hook),是Windows消息處理機制的一個平臺,應用程序可以在上面設置子程以監視指定窗口的某種消息,而且所監視的窗口可以是其他進程所創建的。當消息到達后,在目標窗口處理函數之前處理它。鉤子機制允許應用程序截獲處理window消息或特定事件。

鉤子實際上是一個處理消息的程序段,通過系統調用,把它掛入系統。每當特定的消息發出,在沒有到達目的窗口前,鉤子程序就先捕獲該消息,亦即鉤子函數先得到控制權。這時鉤子函數即可以加工處理(改變)該消息,也可以不作處理而繼續傳遞該消息,還可以強制結束消息的傳遞。

Hook原理

每個Hook都有一個關聯的鏈表,由系統維護,鏈表指針指向被Hook子程調用的回調函數:

LRESULT WINAPI HookCallBack(
int nCode,
WPARAM wParam,
LPARAM lParam);

nCode 事件代碼
wParam 消息的類型
lParam 包含的消息

當被Hook的類型關聯的消息發生時,系統會把這個消息傳遞到鏈表指針指向的Hook子程,由子程內部對消息進行處理,。

鉤子安裝與卸載

使用Windows提供的API SetWindowsHookEx來將Hook子程指針安裝到Hook鏈表,安裝的Hook子程始終在Hook鏈表頭部。

HHOOK SetWindowsHookEx(
int idHook,
HOOKPROC lpfn,
HINSTANCE hMod,
DWORD dwThreadId);

idHook 安裝的鉤子類型
lpfn 子程函數指針
hMod 應用程序實例的句柄
dwThreadId Hook關聯的線程標識符

當被Hook的消息類型發生時,系統會調用與這個Hook關聯的鏈表頭的Hook子程。由子程序決定是否要把這個事件傳遞給下個子程。Hook子程傳遞事件給下一個Hook子程需要調用使用Windows提供的API CallNextHookEx。

LRESULT CallNextHookEx(
HHOOK hhk,
int nCode,
WPARAM wParam,
LPARAM lParam);

hhk 當前鉤子的句柄,由SetWindowsHookEx返回
nCode 事件代碼
wParam 消息的類型
lParam 包含的消息

鉤子在使用完,需調用UnHookWindowsHookEx來卸載。

UnHookWindowsHookEx(
HHOOK hhk);

Api Hook 的幾種方法

一.修改函數過程映射的地址空間

Api Hook:

我們都知道,在調用函數的時候,會先Call 0xFFFFFF,來到函數映射在內存的地址空間里,這時候,傳入的參數已入棧,那么,我們只需要修改函數頭部,跳轉到我們自定義的過程,這樣,我們就達到了對API函數監視的目的。那么,我們需要多大的空間來寫入代碼讓其跳轉到我們自定義的過程里呢?

聰明的你肯定已經想到了。沒錯,只需5字節即可。Why? 看一下代碼:

jmp 0xFFFFFFFF

jmp 指令占1字節,地址是4字節,所以,只需5字節即可。

有的看官可能會問了,為什么不用Call呢?

因為Call指令在實現跳轉以后會通過ret返回到Call下面的指令繼續執行,而Jmp指令實現跳轉以后則全有子程來控制了。

我們知道了通過以上方法來修改函數頭部數據來達到我們的目的,那么,我們先要得到指向函數頭部的地址,通過LoadLibrary和GetProcAddress即可獲取到我們要監視的函數在內存中映射的地址指針。我們以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;
}

在得到了指向內存映射里函數頭部的指針以后,我們下一步,來讀出函數頭部的5字節。為什么要讀出函數頭部的5字節呢?呵呵,當然是留作恢復時寫會數據了。ReadProcessMemory:

BOOL ReadProcessMemory( 
HANDLE hProcess,
LPCVOID lpBaseAddress, 
LPVOID lpBuffer,
DWORD nSize,
LPDWORD lpNumberOfBytesRead);

hProcess 進程句柄
lpBaseAddress 讀內存開始地址
lpBuffer 保存數據的指針
nSize 數據大小
lpNumberOfBytesRead //address of number of bytes read

BYTE *pFuncData = new BYTE[5];
LPDWORD iRead;

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

保存原函數入口點的數據以后,嘿嘿,當然是寫入指向我們自己的函數地址了。

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是我們自己構造的函數:

int WINAPI MyMessageBoxA(HWND hWnd,LPSTR lpText,LPSTR lpCaption,int uType)
{
//我們來彈出一個消息框,改變一下傳入的標題參數值
//那么首要做的,就是先恢復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;
}

恢復API的原入口處數據:

WriteProcessMemory((void *)GetCurrentProcess(),PMessageBoxA,pFuncData,5,iWrite);

防止修改函數入口數據來進行API HOOK的方法:

在程序啟動的時候保存關鍵API入口的5字節,定期檢查一次,發現被修改了,改回即可。

----------------------------------------------------------------

代碼回頭附上,碼字太累了,前言浪費了點時間,#- - 感覺和掃盲差不多……

下一篇中,會針對SEH技術實現API HOOK和如何防止SEH APIHOOK進行詳解。

掃盲到此結束。