本環(huán)境是蛇矛實驗室基于"火天網(wǎng)演攻防演訓靶場"進行搭建,通過火天網(wǎng)演中的環(huán)境構建模塊,可以靈活的對目標網(wǎng)絡進行設計和配置,并且可以快速進行場景搭建和復現(xiàn)驗證工作。
前言
Hook中文譯為“鉤子”或“掛鉤”,這很容易聯(lián)想到釣東西,好比釣魚,但將其比作“網(wǎng)”更合適,在安全開發(fā)的過程中,Hook技術主要用于對程序的運行流程進行控制和攔截,對特定的消息或動作進行過濾。
Hook原理
在真正執(zhí)行原始API之前,對程序流程進行攔截,使其先執(zhí)行自定義的代碼后,再執(zhí)行原始API調用流程。
Hook分類
Hook根據(jù)其作用的權限,可分為應用層(R3)鉤子和內核(R0)鉤子,本文主要講解應用層鉤子。
從代碼實現(xiàn)角度,可將R3 Hook分為以下幾類:
基于地址修改,比如IAT Hook。
基于代碼修改,比如Inline Hook。
基于異?;蛘{試,比如VEH Hook。
由于篇幅有限,本文只包含部分Hook技術。
IAT Hook
IAT(import address table,導入地址表)是指PE文件格式中的一個表結構,說到IAT就離不開導入表,在實際的開發(fā)過程中,難免會使用到Windows API,這些API的代碼保存在Windows提供的不同的DLL(動態(tài)鏈接庫)文件中,DLL將這些API導出,在可執(zhí)行程序中使用到其他DLL的代碼或數(shù)據(jù)時,編譯器會將這些導入的信息填充到可執(zhí)行程序的導入表中。當可執(zhí)行程序運行時,系統(tǒng)會將可執(zhí)行程序和其依賴的DLL加載到內存中,其中windows加載器會定位所有導入函數(shù)的地址并將定位到的地址填充到IAT中供其使用,Windows加載器定位這些函數(shù)的地址需要依賴PE文件中的導入表,其中導入表存放了所使用到的DLL文件和導入的函數(shù)名稱和序號信息。
實現(xiàn)原理
通過替換IAT表中函數(shù)的原始地址從而實現(xiàn)Hook。
實現(xiàn)步驟
以Hook User32!MessageBoxA為例:
1. 定義基于User32!MessageBoxA的函數(shù)原型的函數(shù)指針;
2. 獲取User32!MessageBoxA的函數(shù)地址并保存;
3. 創(chuàng)建HookedMessageBoxA函數(shù)(函數(shù)原型同User32!MessageBoxA一樣),以攔截程序對User32!MessageBoxA的調用:
先執(zhí)行自定義的代碼; 再執(zhí)行原始User32!MessageBoxA函數(shù)。
4. 解析導入表,并在IAT中定位User32!MessageBoxA的位置;
5. 使用HookedMessageBoxA函數(shù)的地址替換IAT中User32!MessageBoxA的地址。
實現(xiàn)代碼
#include#include #include #include #pragmacomment (lib, "dbghelp.lib") // 1. 定義基于User32!MessageBoxA的函數(shù)原型的函數(shù)指針 usingMessageBoxT = int(WINAPI*)(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType); // 2. 獲取User32!MessageBoxA的函數(shù)地址并保存; MessageBoxT OriginalMessageBox = MessageBoxA; // 3. 創(chuàng)建HookedMessageBoxA函數(shù)(函數(shù)原型同User32!MessageBoxA一樣),以攔截程序對User32!MessageBoxA的調用: intWINAPI HookedMessageBox(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType) { // 3.1 先執(zhí)行自定義的代碼 MessageBoxW(0, L"HookedMessageBox() called", L"IAT Hook", 0); // 3.2 再執(zhí)行原始User32!MessageBoxA函數(shù) returnOriginalMessageBox(hWnd, lpText, lpCaption, uType); } /* 4. 解析導入表,并在IAT中定位User32!MessageBoxA的位置; 5. 使用HookedMessageBoxA函數(shù)的地址替換IAT中User32!MessageBoxA的地址。 */ boolSetHook(std::stringdllName, std::stringorigFunc, PROC hookingFunc) { ULONG size; DWORD i; LPCSTR importDllName = NULL; HMODULE importDllImageBase = NULL; // 獲取主模塊句柄 HMODULE imageBase = GetModuleHandle(NULL); // 定位主模塊的導入表 PIMAGE_IMPORT_DESCRIPTOR importDescTab = (PIMAGE_IMPORT_DESCRIPTOR)ImageDirectoryEntryToDataEx(imageBase, TRUE, IMAGE_DIRECTORY_ENTRY_IMPORT, &size, NULL); // 尋找目標DLL boolfound = false; for(i = 0; i < size; i++) ??{ ????importDllName = (LPCSTR)importDescTab[i].Name + (DWORD_PTR)imageBase; // 獲取導入DLL名稱 ????if?(_stricmp(dllName.c_str(), importDllName) == 0) ????{ ??????found = true; ??????break; ????} ??} ??// 沒找到目標DLL,返回 ??if?(!found) ????return?false; ??// 找到目標dll ??importDllImageBase = GetModuleHandleA(importDllName); ??if?(!importDllImageBase) return?false; ??PIMAGE_THUNK_DATA originalFirstThunk = (PIMAGE_THUNK_DATA)((ULONG_PTR)imageBase + importDescTab[i].OriginalFirstThunk); // 定位導入名稱表INT ??PIMAGE_THUNK_DATA firstThunk = (PIMAGE_THUNK_DATA)((ULONG_PTR)imageBase + importDescTab[i].FirstThunk); // 定位導入地址表IAT ??// 尋找Hook的API ??while?(originalFirstThunk->u1.AddressOfData) { PIMAGE_IMPORT_BY_NAME functionName = (PIMAGE_IMPORT_BY_NAME)((ULONG_PTR)imageBase + originalFirstThunk->u1.AddressOfData); if(_stricmp(origFunc.c_str(), functionName->Name) == 0) { // 確保內存可寫 DWORD oldProtect = 0; VirtualProtect((LPVOID)(&firstThunk->u1.Function), 4096, PAGE_READWRITE, &oldProtect); // 替換IAT中的函數(shù)地址 firstThunk->u1.Function = (ULONG_PTR)hookingFunc; // 恢復內存屬性 VirtualProtect((LPVOID)(&firstThunk->u1.Function), 4096, oldProtect, &oldProtect); } ++originalFirstThunk; ++firstThunk; } returntrue; } intmain() { // Hook前 MessageBoxA(0, "Before Hooking", "IAT HOOKS", 0); // 進行IAT Hook SetHook("user32.dll", "MessageBoxA", (PROC)HookedMessageBox); // Hook后 MessageBoxA(0, "After Hooking", "IAT Hook", 0); return0; }
上述代碼第一次調用MessageBoxA時,正常彈出,為了之后在調用MessageBoxA時,先執(zhí)行自定義的函數(shù)代碼(HookedMessageBox),首先在進程的IAT中定位到MessageBoxA的地址,這個過程是先通過進程的導入表找到MessageBoxA所在的DLL模塊(user32.dll),找到之后,通過INT(導入名稱表)得到MessageBoxA函數(shù)地址在IAT中的下標,此時使用自定義函數(shù)的地址替換掉IAT中MessageBoxA函數(shù)的地址,即可達到IAT Hook的效果,Hook之后在調用MessageBoxA時,程序會先執(zhí)行HookedMessageBox函數(shù),在執(zhí)行原始的MessageBoxA函數(shù)(需要提前獲取MessageBoxA的地址)。
效果
Inline Hook
Inline Hook實際上是一種通過修改機器碼的方式來實現(xiàn)Hook的技術。
實現(xiàn)原理
通過直接修改API函數(shù)在內存中對應的二進制代碼,通過跳轉指令將其代碼的執(zhí)行執(zhí)行流程改變從而執(zhí)行用戶編寫的代碼進而進行Inline Hook。
實現(xiàn)步驟
以Hook User32!MessageBoxA為例:
1. 在指定進程中內存中找到MessageBoxA函數(shù)地址,并保存函數(shù)頭部若干字節(jié)(用于后續(xù)unpatch);
2. 創(chuàng)建HookedMessageBoxA函數(shù)(函數(shù)原型同User32!MessageBoxA一樣),以攔截程序對User32!MessageBoxA的調用:
先執(zhí)行自定義的代碼; 恢復先前保存的MessageBoxA原始字節(jié); 執(zhí)行原函數(shù) 再次對MessageBoxA進行Hook;
3. 構造跳轉指令,用于后續(xù)替換MessageBoxA函數(shù)代碼頭部字節(jié);
4. 修改MessageBoxA函數(shù)首地址代碼為跳轉指令。
實現(xiàn)代碼
#include#include #ifdefined(_WIN64) #defineORIG_BYTES_SIZE 14 #else #defineORIG_BYTES_SIZE 7 #endif BYTE OriginalBytes[ORIG_BYTES_SIZE]{}; // 用于保存MessageBoxA的部分原始代碼字節(jié) BYTE PatchBytes[ORIG_BYTES_SIZE]{}; // 構造的跳轉指令 usingMessageBoxAT = int(WINAPI*)(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType); MessageBoxAT OriginalMessageBox = nullptr; intWINAPI HookedMessageBox(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType) { // 執(zhí)行自定義的代碼 SIZE_T bytesOut = 0; MessageBoxW(0, L"HookedMessageBox() called", L"Inline Hook", 0); // unpatch MessageBoxA WriteProcessMemory(GetCurrentProcess(), (LPVOID)OriginalMessageBox, OriginalBytes, sizeof(OriginalBytes), &bytesOut); // 調用原來的MessageBoxA intresult = MessageBoxA(NULL, lpText, lpCaption, uType); // 再次patch MessageBoxA WriteProcessMemory(GetCurrentProcess(), OriginalMessageBox, PatchBytes, sizeof(PatchBytes), &bytesOut); returnresult; } boolSetHook(std::stringdllName, std::stringorigFunc, FARPROC hookingFunc) { SIZE_T bytesIn = 0; SIZE_T bytesOut = 0; // 保存MessageBoxA原始地址 OriginalMessageBox = (MessageBoxAT)GetProcAddress(GetModuleHandleA(dllName.c_str()), origFunc.c_str()); // 保存MessageBoxA的部分原始代碼字節(jié) ReadProcessMemory(GetCurrentProcess(), OriginalMessageBox, OriginalBytes, ORIG_BYTES_SIZE, &bytesIn); memset(PatchBytes, 0, sizeof(PatchBytes)); #ifdefined(_WIN64) /* JMP [RIP+0]; xFFx25x00x00x00x00 x00x11x22x33x44x55x66x77 */ memcpy(PatchBytes, "xFFx25", 2); memcpy(PatchBytes + 6, &hookingFunc, 8); #else /* mov eax, &hookingFunc jmp eax */ memcpy(PatchBytes, "xB8", 1); memcpy(PatchBytes + 1, &hookingFunc, sizeof(ULONG_PTR)); memcpy(PatchBytes + 5, "xFFxE0", 2); #endif // patch the MessageBoxA WriteProcessMemory(GetCurrentProcess(), OriginalMessageBox, PatchBytes, sizeof(PatchBytes), &bytesOut); returntrue; } intmain() { // Hook前 MessageBoxA(0, "Before Hooking", "Inline Hook", 0); // 進行Inline Hook SetHook("user32.dll", "MessageBoxA", (FARPROC)HookedMessageBox); // Hook后 MessageBoxA(0, "After Hooking", "Inline Hook", 0); return0; }
程序中調用了2次MessageBoxA,第一次調用時未被掛鉤,之后Inline Hook方式使用對MessageBoxA進行掛鉤,當之后再次調用MessageBoxA時,程序會首先進入自寫函數(shù)(HookedMessageBox)中,在該函數(shù)中,自定義的代碼部分使用MessageBoxW彈出內容,隨后修復MessageBoxA被修改的字節(jié)代碼后,開始執(zhí)行原始MessageBoxA代碼。
效果
VEH Hook
VEH Hook是一種基于異常處理的Hook手段,通過主動觸發(fā)異常從在獲取程序控制權來達到Hook的手段。其中VEH(Vectored Exception Handler,向量化異常處理)是Windows中處理異常的一種方式。
實現(xiàn)原理
由于VEH的異常處理發(fā)生在之前,所以通過`主動拋出異常,使程序觸發(fā)異常,進而使控制權交給異常處理例程的這一系列操作來實現(xiàn)Hook。
實現(xiàn)步驟
以Hook User32!MessageBoxA為例:
1. 獲取MessageBoxA地址,并保存;
2. 安裝VEH異常處理程序,編寫VEHHandler(VEH的異常處理函數(shù));
3. 設置鉤子:人為在Hook點構造異常(比如修改目標函數(shù)第一個字節(jié)位0xCC等),并保存觸發(fā)異常的地址等信息;
4. 在VEHHandler函數(shù)內部修改目標函數(shù)原始流程,并在執(zhí)行完畢后主動修復異常。
實現(xiàn)代碼
#include#include // 獲取目標函數(shù)的地址 ULONG_PTR OriginalMessageBox = NULL; structEXCEPTION_HOOK { ULONG_PTR address; // 用來記錄異常產生的地址,后面將用來確保是我們人為構造的異常 BYTE originalBytes; // 用來記錄原始目標函數(shù)的第一個字節(jié) }; EXCEPTION_HOOK HookInfo; // 異常處理函數(shù) // 用來修改目標函數(shù)原始流程,并在執(zhí)行完我們功能后修復異常 LONG NTAPI VEHHandler(EXCEPTION_POINTERS* ExceptionInfo) { if(ExceptionInfo->ExceptionRecord->ExceptionCode == EXCEPTION_BREAKPOINT && // 異常類型為斷點異常 (ULONG_PTR)ExceptionInfo->ExceptionRecord->ExceptionAddress == HookInfo.address) // 發(fā)生異常的地址為我們主動構造的異常地址 { // 在這里編寫自定義的代碼,或者修改Hook API的相關參數(shù) MessageBoxW(0, L"VEHHandler() called", L"VEH Hook", 0); // 解除鉤子 DWORD oldProtect = 0; VirtualProtect((LPVOID)HookInfo.address, 1, PAGE_EXECUTE_READWRITE, &oldProtect); *(BYTE*)HookInfo.address = HookInfo.originalBytes; VirtualProtect((LPVOID)HookInfo.address, 1, oldProtect, &oldProtect); returnEXCEPTION_CONTINUE_EXECUTION; // 回到異常發(fā)生的地方,由于已經(jīng)修復了異常問題,所以之后能夠正確執(zhí)行 } returnEXCEPTION_CONTINUE_SEARCH; // 向上繼續(xù)尋找異常處理程序 } voidSetHook(ULONG_PTR address) { AddVectoredExceptionHandler(1, VEHHandler); // 添加VEH的異常處理函數(shù) HookInfo.address = address; // 保存目標函數(shù)發(fā)生異常的地址 HookInfo.originalBytes = *(BYTE*)address; // 保存目標函數(shù)原始的第一個字節(jié) DWORD oldProtect = 0; VirtualProtect((LPVOID)address, 1, PAGE_EXECUTE_READWRITE, &oldProtect); *(UCHAR*)address = 0xCC; // 人為構造異常,將目標函數(shù)代碼處的第一個字節(jié)改為0xCC VirtualProtect((LPVOID)address, 1, oldProtect, &oldProtect); } intmain() { // 保存MessageBoxA原始地址 OriginalMessageBox = (ULONG_PTR)GetProcAddress(GetModuleHandleA("user32.dll"), "MessageBoxA"); MessageBoxA(0, "Before Hooking", "VEH Hook", 0); SetHook(OriginalMessageBox);// 安裝鉤子用以觸發(fā)異常 MessageBoxA(0, "After Hooking", "VEH Hook", 0); return0; }
在上述代碼中,先保存了MessageBoxA函數(shù)在當前進程中的地址,之后第一次調用MessageBoxA,此時該函數(shù)還沒有被Hook,隨后為了人為構造異常,將MessageBoxA函數(shù)代碼的第一個字節(jié)修改為0xCC,當之后再次調用MessageBoxA后,程序將會觸發(fā)0xCC異常,由于程序中添加了VEH異常處理,那么程序將跳轉到VEHHandler(自寫的異常處理函數(shù)中),在該函數(shù)代碼中,首先過濾得到主動觸發(fā)的異常,滿足的條件下,開始執(zhí)行自定義的代碼,這里為了說明,使用MessageBoxW彈出對話框,由于異常被程序接管,所以在異常處理函數(shù)中,執(zhí)行完自定義的代碼后,需要修復異常,進而返回原始觸發(fā)異常的位置繼續(xù)執(zhí)行。
效果
PS:通常來說,我們將Hook的功能代碼編寫進一個DLL文件中,在將該DLL文件通過進程注入的方式注入到需要改變程序流程的進程中來達到目的。
丈八網(wǎng)安蛇矛實驗室成立于2020年,致力于安全研究、攻防解決方案、靶場對標場景仿真復現(xiàn)及技戰(zhàn)法設計與輸出等相關方向。團隊核心成員均由從事安全行業(yè)10余年經(jīng)驗的安全專家組成,團隊目前成員涉及紅藍對抗、滲透測試、逆向破解、病毒分析、工控安全以及免殺等相關領域。
審核編輯:湯梓紅
-
API
+關注
關注
2文章
1473瀏覽量
61752 -
應用層
+關注
關注
0文章
46瀏覽量
11487 -
HOOK
+關注
關注
0文章
15瀏覽量
8364
原文標題:安全開發(fā)之應用層Hook技術
文章出處:【微信號:蛇矛實驗室,微信公眾號:蛇矛實驗室】歡迎添加關注!文章轉載請注明出處。
發(fā)布評論請先 登錄
相關推薦
評論