什麼叫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表,填入自己的函數地址。 |