首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> VC >> VC 教學

VC++ 實作阻止全域鉤子載入的 API Hook 技術

     編輯:VC 教學

全域鉤子注入機制分析

當程式 A 安裝 WH_GETMESSAGE 全域鉤子,鉤子函數位於 B.dll。其他程式呼叫 GetMessage 時,系統發現全域鉤子,會檢查目標程序是否已載入 B.dll。若未載入,系統呼叫 LoadLibrary 強制載入,使鉤子 DLL 注入所有呼叫 GetMessage 的程序。

阻止策略:攔截 LoadLibraryExW

目標:在系統嘗試載入鉤子 DLL 時讓 LoadLibrary 失敗。關鍵判斷:正常 DLL 載入呼叫來自 kernel32.dll,唯有鉤子載入是從 user32.dll 發起(WinXP 驗證,後續版本類似)。透過檢查呼叫者位址是否落在 user32.dll 記憶體範圍內,即可識別並攔截鉤子載入。

三步驟實作 API Hook

1. 提供替代函數 newLoadLibraryExW

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); // 正常轉發
}

2. 建立彈簧板函數 fakeLoadLibraryExW

保存原始 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 條指令

3. 修改原始函數入口跳轉至新函數

VirtualProtect(fnRaw, 7, PAGE_READWRITE, &dwOldProtect); // 解除保護
*fnRaw = 0xE9; // JMP
*(UINT32*)(fnRaw+1) = (UINT32)fnNew - (UINT32)(fnRaw+5); // 相對位移
VirtualProtect(fnRaw, 7, dwOldProtect, 0); // 還原保護

完整封裝類別 GBlockHookDll

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] 即為返回位址。
  • 通用 API Hook 框架: 實務中需依指令長度動態決定修改位元組數,可參考 Microsoft Detours 實作。
  • 適用性: 僅阻止透過 user32.dll 載入的全域鉤子,不影響正常 DLL 載入。

總結

透過分析全域鉤子注入路徑,精準攔截 LoadLibraryExW 並判斷呼叫者模組,可有效阻擋惡意或不需的全域鉤子注入本程序。此技術適用於反外掛、安全強化等場景,但需謹慎使用以免影響系統穩定。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved