當程式 A 安裝 WH_GETMESSAGE 全域鉤子,鉤子函數位於 B.dll。其他程式呼叫 GetMessage 時,系統發現全域鉤子,會檢查目標程序是否已載入 B.dll。若未載入,系統呼叫 LoadLibrary 強制載入,使鉤子 DLL 注入所有呼叫 GetMessage 的程序。
目標:在系統嘗試載入鉤子 DLL 時讓 LoadLibrary 失敗。關鍵判斷:正常 DLL 載入呼叫來自 kernel32.dll,唯有鉤子載入是從 user32.dll 發起(WinXP 驗證,後續版本類似)。透過檢查呼叫者位址是否落在 user32.dll 記憶體範圍內,即可識別並攔截鉤子載入。
HMODULE WINAPI newLoadLibraryExW(LPCWSTR lpLibFileName, HANDLE hFile, DWORD dwFlags) {
DWORD dwCaller;
__asm push dword ptr [ebp+4]
__asm pop dword ptr [dwCaller] // 取得返回位址
// 判斷呼叫者是否在 user32.dll 範圍內
if (dwCaller > m_dwUser32Low && dwCaller < m_dwUser32Hi) {
#ifdef _DEBUG
// 除錯輸出被攔截的 DLL 名稱
#endif
return 0; // 直接失敗,阻止鉤子載入
}
return rawLoadLibraryExW(lpLibFileName, hFile, dwFlags); // 正常轉發
}
保存原始 LoadLibraryExW 前 7 位元組(前兩條指令共 7 字節),接著 jmp 回原函數第 3 條指令:
// 原始函數前兩條指令(共 7 字節)
push 34h // 6A 34
push 7C80E288h // 68 88 E2 80 7C
// fakeLoadLibraryExW 結構:
memcpy(fnFake, rawAddr, 7); // 複製前 7 字節
fnFake[7] = 0xE9; // JMP 指令
*(UINT32*)(fnFake+8) = (rawAddr+7) - (fnFake+12); // 相對位移跳回原函數第 3 條指令
VirtualProtect(fnRaw, 7, PAGE_READWRITE, &dwOldProtect); // 解除保護
*fnRaw = 0xE9; // JMP
*(UINT32*)(fnRaw+1) = (UINT32)fnNew - (UINT32)(fnRaw+5); // 相對位移
VirtualProtect(fnRaw, 7, dwOldProtect, 0); // 還原保護
class GBlockHookDll {
public:
GBlockHookDll() {
MODULEINFO info = {0};
GetModuleInformation(GetCurrentProcess(), GetModuleHandle(_T("user32.dll")), &info, sizeof(info));
m_dwUser32Low = (DWORD)info.lpBaseOfDll;
m_dwUser32Hi = m_dwUser32Low + info.SizeOfImage;
}
void PatchLoadLibrary() {
LPVOID* pfnRaw = (LPVOID*)&rawLoadLibraryExW;
BYTE* fnRaw = (BYTE*)*pfnRaw;
const int nFirstBytes = 7;
// 1. 建立彈簧板
BYTE* fnFake = fakeLoadLibraryExW;
memcpy(fnFake, fnRaw, nFirstBytes);
fnFake[nFirstBytes] = 0xE9;
*(UINT32*)(fnFake + nFirstBytes + 1) = (UINT32)fnRaw + nFirstBytes - (UINT32)(fnFake + nFirstBytes + 5);
// 2. 修改原函數入口
DWORD dwOldProtect;
VirtualProtect(fnRaw, nFirstBytes, PAGE_READWRITE, &dwOldProtect);
*fnRaw = 0xE9;
*(UINT32*)(fnRaw+1) = (UINT32)newLoadLibraryExW - (UINT32)(fnRaw+5);
VirtualProtect(fnRaw, nFirstBytes, dwOldProtect, 0);
// 3. 更新函數指標指向彈簧板
*pfnRaw = fnFake;
}
private:
static HMODULE WINAPI newLoadLibraryExW(LPCWSTR lpLibFileName, HANDLE hFile, DWORD dwFlags) {
DWORD dwCaller;
__asm push dword ptr [ebp+4]
__asm pop dword ptr [dwCaller]
if (dwCaller > m_dwUser32Low && dwCaller < m_dwUser32Hi) {
#ifdef _DEBUG
// TRACE 輸出攔截資訊
#endif
return 0;
}
return rawLoadLibraryExW(lpLibFileName, hFile, dwFlags);
}
static DWORD m_dwUser32Low, m_dwUser32Hi;
static BYTE fakeLoadLibraryExW[12];
static HMODULE (WINAPI *rawLoadLibraryExW)(LPCWSTR, HANDLE, DWORD);
};
// 靜態成員定義
DWORD GBlockHookDll::m_dwUser32Low = 0;
DWORD GBlockHookDll::m_dwUser32Hi = 0;
BYTE GBlockHookDll::fakeLoadLibraryExW[12] = {0};
HMODULE (WINAPI *GBlockHookDll::rawLoadLibraryExW)(LPCWSTR, HANDLE, DWORD) = LoadLibraryExW;
GBlockHookDll g_blocker; // 全域物件
g_blocker.PatchLoadLibrary(); // 在程式初始化時呼叫一次
push ebp; mov ebp, esp 後,[ebp+4] 即為返回位址。user32.dll 載入的全域鉤子,不影響正常 DLL 載入。透過分析全域鉤子注入路徑,精準攔截 LoadLibraryExW 並判斷呼叫者模組,可有效阻擋惡意或不需的全域鉤子注入本程序。此技術適用於反外掛、安全強化等場景,但需謹慎使用以免影響系統穩定。