? 什么叫Hook API?所謂Hook就是鉤子的意思,而API是指
Windows開放給程序員的編程接口,使得在用戶級別下可以對操作系統進行控制,也就是一般的應用程序都需要調用API來完成某些功能, Hook
API的意思就是在這些應用程序調用真正的系統API前可以先被截獲,從而進行一些處理再調用真正的API來完成功能。在講Hook API之
前先來看一下如何Hook消息,例如Hook全局鍵盤消息,從而可以知道用戶按了哪些鍵,這種Hook消息的功能可以由以下函數來完成,該函數將一個新的
Hook加入到原來的Hook鏈中,當某一消息到達后會依次經過它的Hook鏈再交給應用程序。HHOOK SetWindowsHookEx(
????
int idHook,????????????????????? //Hook類型,例如WH_KEYBOARD,WH_MOUSE
????
HOOKPROC lpfn,????????????? //Hook處理過程函數的地址
???? HINSTANCE
hMod,?????????? //包含Hook處理過程函數的dll句柄(若在本進程可以為NULL)
???? DWORD
dwThreadId,????? //要Hook的線程ID,若為0,表示全局Hook所有
);????
這里需要提一下的就是如果是Hook全局的而不是某個特定的進程則需要將Hook過程編寫為一個DLL,以便讓任何程序都可以加載它來獲取Hook過程函
數。???? 而對于Hook API微軟并沒有提供直接的接口函數,也許它并不想讓我們這樣做,
不過有2種方法可以完成該功能。第一種,修改可執行文件的IAT表(即輸入表),因為在該表中記錄了所有調用API的函數地址,則只需將這些地址改為自己
函數的地址即可,但是這樣有一個局限,因為有的程序會加殼,這樣會隱藏真實的IAT表,從而使該方法失效。第二種方法是直接跳轉,改變API函數的頭幾個
字節,使程序跳轉到自己的函數,然后恢復API開頭的幾個字節,在調用AP完成功能后再改回來又能繼續Hook了,但是這種方法也有一個問題就是同步的問
題,當然這是可以克服的,并且該方法不受程序加殼的限制。???? 下面將以一個Hook指定程序send函數的例子來詳細描述如何Hook
API,以達到監視程序發送的每個封包的目的。采用的是第二種方法,編寫為一個dll。首先是一些全局聲明,//本dll的handle
HANDLE
g_hInstance = NULL;
//修改API入口為 mov eax, 00400000;jmp eax是程序能跳轉到自己的函數
BYTE
g_btNewBytes[8] = { 0xB8, 0x0, 0x0, 0x40, 0x0, 0xFF, 0xE0, 0x0 };
//
保存原API入口的8個字節
DWORD g_dwOldBytes[2][2] = { 0x0, 0x0, 0x0, 0x0 };
//
鉤子句柄
HHOOK??? g_hOldHook = NULL;
//API中send函數的地址
DWORD g_pSend =
0;
//事務,解決同步問題
HANDLE g_hSendEvent =
NULL;//自己的send函數地址,參數必須與API的send函數地址相同int _stdcall hook_send( SOCKET s,
const char *buf, int len, int flags );//要Hook的進程和主線程ID號DWORD
g_dwProcessID = 0;
DWORD g_dwThreadID = 0;????
從聲明可以看出,我們會把API函數的首8個字節改為 mov eax, 00400000;jmp eax ,使程序能夠跳轉,只需獲取我們自己的函數
地址填充掉00400000即可實現跳轉。而g_dwOldBytes是用來保存API開頭原始的8個字節,在真正執行API函數是需要寫回。還有一點,
在聲明新的函數時,該例中為hook_send,除了保正參數與API的一致外,還需要聲明為__stdcall類型,表示函數在退出前自己來清理堆棧,
因為這里是直接跳轉到新函數處,所以必須自己清理堆棧。下面看主函數,BOOL APIENTRY DllMain( HANDLE hModule,
??????????????????????????????????? DWORD?? ul_reason_for_call,
???????????????????????????????????
LPVOID lpReserved
????????????????????????????????? )
{
????
if(ul_reason_for_call == DLL_PROCESS_ATTACH)
??? {?????? //獲取本dll句柄
??????
g_hInstance = hModule;???????????? //創建事務
?????? g_hSendEvent =
CreateEvent( NULL, FALSE, TRUE, NULL );
?????
??????
//重寫API開頭的8字節
?????? HMODULE hWsock = LoadLibrary( "wsock32.dll" );
??????
g_pSend = ( DWORD )GetProcAddress( hWsock, "send" );??????
//保存原始字節?????? ReadProcessMemory( INVALID_HANDLE_VALUE, ( void *
)g_pSend,
?????????? ( void * )g_dwOldBytes[0], sizeof( DWORD )*2,
NULL );?????? //將00400000改寫為我們函數的地址
?????? *( DWORD* )( g_btNewBytes +
1 ) = ( DWORD )hook_send;
?????? WriteProcessMemory(
INVALID_HANDLE_VALUE, ( void * )g_pSend,
?????????? ( void *
)g_btNewBytes, sizeof( DWORD )*2, NULL );
???? }
???? return
TRUE;
}????
以上是dll的main函數,在被指定的程序加載的時候會自動運行dll的main函數來完成初始化,這里就是改寫API的首地址來完成跳轉。當然本程序
是對于指定程序進行Hook,如果要進行全局Hook,可以在main函數中用GetModuleFileName函數來獲取exe文件完整路徑,判斷當
前進程是否是想要Hook的進程。寫函數中使用INVALID_HANDLE_VALUE,表示寫本進程。int _stdcall
hook_send( SOCKET s, const char *buf, int len, int flags )
{
???
int nRet;??? WaitForSingleObject( g_hSendEvent, INFINITE );?????
//恢復API頭8個字節
??? WriteProcessMemory( INVALID_HANDLE_VALUE, ( void*
)g_pSend,
?????? ( void* )g_dwOldBytes[0], sizeof( DWORD )*2, NULL
);??? /*??? 這里可以添加想要進行的處理過程??? */??? //真正執行API函數
??? nRet = send( s,
buf, len, flags );??? //寫入跳轉語句,繼續Hook
??? WriteProcessMemory(
INVALID_HANDLE_VALUE, ( void* )g_pSend,
?????? ( void*
)g_btNewBytes, sizeof( DWORD )*2, NULL );??? SetEvent( g_hSendEvent
);??? return nRet;
}HOOK_API BOOL StartHook(HWND hWnd)
{????
//通過傳入的窗口句柄獲取線程句柄
???? g_dwThreadID = GetWindowThreadProcessId( hWnd,
&g_dwProcessID );???? //WH_CALLWNDPROC類型的Hook
???? g_hOldHook =
SetWindowsHookEx( WH_CALLWNDPROC,?? HookProc,?????????? ( HINSTANCE )
g_hInstance, g_dwThreadID );
???? if( g_hOldHook == NULL )
????????
return FALSE;
???? return TRUE;
}static LRESULT WINAPI HookProc(
int nCode, WPARAM wParam, LPARAM lParam )
{
return
CallNextHookEx( g_hOldHook, nCode, wParam, lParam );
}HOOK_API void
StopHook(void)
{
??? if(g_hOldHook != NULL)
??? {
???????
WaitForSingleObject( g_hSendEvent, INFINITE );
??????? HANDLE
hProcess = NULL;
??????? hProcess = OpenProcess(PROCESS_ALL_ACCESS,
FALSE, g_dwProcessID);
??????? DWORD dwOldProc;
??????? DWORD
dwNewProc;??????? //改變頁面屬性為讀寫
??????? VirtualProtectEx( hProcess, (
void* )g_pSend, 8, PAGE_READWRITE, &dwOldProc );???????
//恢復API的首8個字節
??????? WriteProcessMemory( hProcess, ( void* )g_pSend,
???????????? ( void* )g_dwOldBytes[0], sizeof( DWORD )*2, NULL
);??????? //恢復頁面文件的屬性
??????? VirtualProtectEx( hProcess, ( void*
)g_pSend, 8, dwOldProc, &dwNewProc );
?
???????
CloseHandle(g_hSendEvent);
?
??????? UnhookWindowsHookEx(
g_hOldHook );
???? }
}???
可以看出,我們創建的Hook類型是WH_CALLWNDPROC類型,該類型的Hook在進程與系統一通信時就會被加載到進程空間,從而調用dll的
main函數完成真正的Hook,而在SetWindowsHookEx函數中指定的HookProc函數將不作任何處理,只是調用
CallNextHookEx將消息交給Hook鏈中下一個環節處理,因為這里SetWindowsHookEx的唯一作用就是讓進程加載我們的
dll。???? 以上就是一個最簡單的Hook
API的例子,該種技術可以完成許多功能。例如網游外掛制作過程中截取發送的與收到的封包即可使用該方法,或者也可以在
Hook到API后加入木馬功能,反向連接指定的主機或者監聽某一端口,還有許多加殼也是用該原理來隱藏IAT表,填入自己的函數地址。