PE 文件載入時,載入器解析導入表,將每個導入函數的實際地址寫入 IAT (FirstThunk)。**修改 IAT 中的函數指標,即可將調用重導向到自定義函數**。
// 待 Hook 函數描述
typedef struct _HOOK_API {
LPCSTR szFunc; // 目標函數名 (如 "MessageBoxW")
PROC pNewProc; // 新函數指標
PROC pOldProc; // 原函數指標 (保存用於呼叫原函數)
} HOOK_API, *LPHOOK_API;
PIMAGE_IMPORT_DESCRIPTOR GetNamedImportDescriptor(HMODULE hModule, LPCSTR szImportMod)
{
// 1. DOS Header
PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)hModule;
if (pDos->e_magic != IMAGE_DOS_SIGNATURE) return NULL;
// 2. NT Headers
PIMAGE_NT_HEADERS pNt = (PIMAGE_NT_HEADERS)((BYTE*)hModule + pDos->e_lfanew);
if (pNt->Signature != IMAGE_NT_SIGNATURE) return NULL;
// 3. 導入表 RVA
DWORD importRVA = pNt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress;
if (!importRVA) return NULL;
// 4. 遍歷導入描述符
PIMAGE_IMPORT_DESCRIPTOR pDesc = (PIMAGE_IMPORT_DESCRIPTOR)((BYTE*)hModule + importRVA);
while (pDesc->Name) {
PSTR szName = (PSTR)((BYTE*)hModule + pDesc->Name);
if (_stricmp(szName, szImportMod) == 0) // 找到目標 DLL (如 "USER32.dll")
return pDesc;
pDesc++;
}
return NULL;
}
BOOL HookAPIByName(HMODULE hModule, LPCSTR szImportMod, LPHOOK_API pHookApi)
{
// 1. 取得導入描述符
PIMAGE_IMPORT_DESCRIPTOR pDesc = GetNamedImportDescriptor(hModule, szImportMod);
if (!pDesc) return FALSE;
// 2. 取得 OriginalFirstThunk (INT) 和 FirstThunk (IAT)
PIMAGE_THUNK_DATA pOrigThunk = (PIMAGE_THUNK_DATA)((BYTE*)hModule + pDesc->OriginalFirstThunk);
PIMAGE_THUNK_DATA pRealThunk = (PIMAGE_THUNK_DATA)((BYTE*)hModule + pDesc->FirstThunk);
// 3. 遍歷 Thunk 陣列
while (pOrigThunk->u1.Function) {
// 忽略序數導入
if (!(pOrigThunk->u1.Ordinal & IMAGE_ORDINAL_FLAG)) {
PIMAGE_IMPORT_BY_NAME pByName = (PIMAGE_IMPORT_BY_NAME)
((BYTE*)hModule + pOrigThunk->u1.AddressOfData);
// 比對函數名
if (_stricmp(pHookApi->szFunc, (char*)pByName->Name) == 0) {
// 4. 修改 IAT 頁面保護
MEMORY_BASIC_INFORMATION mbi;
VirtualQuery(pRealThunk, &mbi, sizeof(mbi));
DWORD oldProtect;
VirtualProtect(mbi.BaseAddress, mbi.RegionSize, PAGE_READWRITE, &oldProtect);
// 5. 保存原指標、寫入新指標
if (!pHookApi->pOldProc)
pHookApi->pOldProc = (PROC)pRealThunk->u1.Function;
pRealThunk->u1.Function = (DWORD)pHookApi->pNewProc;
// 6. 恢復保護
VirtualProtect(mbi.BaseAddress, mbi.RegionSize, oldProtect, &oldProtect);
return TRUE;
}
}
pOrigThunk++;
pRealThunk++;
}
return FALSE; // 未找到目標函數
}
// 新函數實現
int WINAPI MyMessageBoxW(HWND hWnd, LPCWSTR lpText, LPCWSTR lpCaption, UINT uType)
{
// 修改內容、記錄日誌、條件攔截...
return MessageBoxW(hWnd, L"Hooked!", L"API Hook Demo", uType);
}
// 全域 Hook 描述
HOOK_API g_HookMsgBox = {
"MessageBoxW", // 目標函數名
(PROC)MyMessageBoxW, // 新函數
NULL // 原函數 (Hook 成功後填入)
};
// DLL 入口:PROCESS_ATTACH 時安裝
BOOL APIENTRY DllMain(HINSTANCE hInst, DWORD reason, LPVOID)
{
if (reason == DLL_PROCESS_ATTACH) {
// 結合全域鉤子 (WH_MOUSE) 確保 DLL 注入目標進程
// 此處簡化:假設已注入
HookAPIByName(GetModuleHandle(NULL), "USER32.dll", &g_HookMsgBox);
}
return TRUE;
}
// 主程式
HMODULE hHookDll = LoadLibrary("ApiHook.dll"); // 載入 DLL
// 安裝滑鼠鉤子觸發 DLL 注入
HHOOK hMouseHook = SetWindowsHookEx(WH_MOUSE,
(HOOKPROC)GetProcAddress(hHookDll, "DummyMouseProc"),
hHookDll, 0); // 0 = 全域
// 此時 ApiHook.dll 已注入所有 GUI 進程,DLL_PROCESS_ATTACH 執行 HookAPIByName
// 彈出 MessageBoxW 的程式將顯示 "Hooked!"
| 議題 | 說明 |
|---|---|
| EAT (導出表) Hook | 修改 DLL 自己的導出表,影響所有調用者,更徹底但風險高 |
| Inline Hook (代碼填充) | 在目標函數開頭寫入 JMP 指令,無視 IAT,攔截動態 GetProcAddress |
| 延遲載入 DLL | IAT 初始為存根,首次調用時解析,需在解析後 Hook |
| CFG (Control Flow Guard) | Win8.1+ 編譯器插入間接呼叫檢查,修改 IAT 可能觸發 CFG 失敗 |
| 64 位相容 | 指標 64 位,IMAGE_THUNK_DATA 為 ULONGLONG,編譯 x64 DLL |
GetProcAddress 取得真實地址與 IAT 比對。
IAT Hook 是用戶態 API 攔截最經典、最穩定的技術,掌握 PE 結構與 VirtualProtect 操作是核心關鍵。