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

Windows 2000 下 API Hook 實作:VirtualProtectEx + WriteProcessMemory 修改程式碼區

     編輯:關於C++

背景與挑戰

Win9x 時代可透過 VxD、LDT、IDT 進入 Ring 0 修改記憶體,但 Win2K (NT 核心) 禁止此類操作。幸好 Win2K 提供強大的使用者模式記憶體操作 API:

  • VirtualProtectEx:修改目標進程記憶體頁面保護屬性
  • WriteProcessMemory:寫入目標進程記憶體
  • ReadProcessMemory:讀取目標進程記憶體

配合全域 Hook 將 DLL 注入目標進程,即可在使用者模式實現 API 攔截。

核心 API 原型

BOOL VirtualProtectEx(HANDLE hProcess, LPVOID lpAddress, DWORD dwSize,
                      DWORD flNewProtect, PDWORD lpflOldProtect);

BOOL WriteProcessMemory(HANDLE hProcess, LPVOID lpBaseAddress,
                        LPCVOID lpBuffer, DWORD nSize, LPDWORD lpNumberOfBytesWritten);

BOOL ReadProcessMemory(HANDLE hProcess, LPCVOID lpBaseAddress,
                       LPVOID lpBuffer, DWORD nSize, LPDWORD lpNumberOfBytesRead);

完整 DLL 實作 (攔截 MessageBoxA)

全域變數與結構


HHOOK g_hHook;
HINSTANCE g_hInstDll;
FARPROC fpMessageBoxA;
HMODULE hUser32;
BYTE OldCode[5], NewCode[5];
DWORD dwOldProtect, dwNewProtect;
BOOL bHooked = FALSE;

struct HOOK_API {
    LPCSTR szFunc;   // 目標函數名
    PROC pNewProc;   // 新函數指標
    PROC pOldProc;   // 原函數指標
};

空 Hook 回呼 (僅為注入 DLL)


LRESULT WINAPI EmptyHook(int nCode, WPARAM wParam, LPARAM lParam) {
    return CallNextHookEx(g_hHook, nCode, wParam, lParam);
}

安裝/解除 Hook (EXE 呼叫)


extern "C" __declspec(dllexport) BOOL __stdcall InstallHook() {
    g_hInstDll = LoadLibrary("HookDll.dll");  // 自身 DLL 檔名
    g_hHook = SetWindowsHookEx(WH_GETMESSAGE, EmptyHook, g_hInstDll, 0);
    return g_hHook != NULL;
}

extern "C" __declspec(dllexport) BOOL __stdcall UninstallHook() {
    return UnhookWindowsHookEx(g_hHook);
}

初始化:定位目標 API 並構造 JMP 指令


BOOL Init() {
    hUser32 = LoadLibrary("user32.dll");
    fpMessageBoxA = GetProcAddress(hUser32, "MessageBoxA");
    if (!fpMessageBoxA) return FALSE;

    // 1. 備份原始 5 字節 (JMP 指令長度)
    memcpy(OldCode, fpMessageBoxA, 5);

    // 2. 構造 JMP MyMessageBoxA (0xE9 + 相對位移 4 字節)
    NewCode[0] = 0xE9;
    DWORD_PTR target = (DWORD_PTR)MyMessageBoxA;
    DWORD_PTR src = (DWORD_PTR)fpMessageBoxA;
    *(DWORD*)&NewCode[1] = target - src - 5;  // 相對位移

    HookOn();
    return TRUE;
}

自訂攔截函數


int WINAPI MyMessageBoxA(HWND hWnd, LPCTSTR lpText, LPCTSTR lpCaption, UINT uType) {
    HookOff();  // 必須先關閉 Hook,否則遞迴呼叫導致死循環
    int ret = MessageBoxA(hWnd, "來自 Hook 的內容", lpCaption, MB_OK | MB_ICONINFORMATION);
    HookOn();
    return ret;
}

啟用/停用 Hook (修改目標進程記憶體)


void HookOn() {
    HANDLE hProc = OpenProcess(PROCESS_ALL_ACCESS, FALSE, GetCurrentProcessId());
    VirtualProtectEx(hProc, fpMessageBoxA, 5, PAGE_READWRITE, &dwOldProtect);
    WriteProcessMemory(hProc, fpMessageBoxA, NewCode, 5, NULL);
    VirtualProtectEx(hProc, fpMessageBoxA, 5, dwOldProtect, &dwOldProtect);
    bHooked = TRUE;
    CloseHandle(hProc);
}

void HookOff() {
    HANDLE hProc = OpenProcess(PROCESS_ALL_ACCESS, FALSE, GetCurrentProcessId());
    VirtualProtectEx(hProc, fpMessageBoxA, 5, PAGE_READWRITE, &dwOldProtect);
    WriteProcessMemory(hProc, fpMessageBoxA, OldCode, 5, NULL);
    VirtualProtectEx(hProc, fpMessageBoxA, 5, dwOldProtect, &dwOldProtect);
    bHooked = FALSE;
    CloseHandle(hProc);
}

DLL 進入點


BOOL WINAPI DllEntryPoint(HINSTANCE hinst, DWORD reason, LPVOID) {
    switch (reason) {
        case DLL_PROCESS_ATTACH:
            if (!Init()) MessageBoxA(NULL, "Init Failed", "Error", MB_OK);
            break;
        case DLL_PROCESS_DETACH:
            if (bHooked) UninstallHook();
            break;
    }
    return TRUE;
}

EXE 測試程式


extern "C" __declspec(dllimport) BOOL __stdcall InstallHook();
extern "C" __declspec(dllimport) BOOL __stdcall UninstallHook();

void __fastcall TForm1::Button1Click(TObject *Sender) {
    if (InstallHook()) {
        MessageBoxA(NULL, "原始內容", "標題", MB_OK);  // 實際顯示 "來自 Hook 的內容"
        UninstallHook();
    }
}

關鍵技術點總結

技術點說明
為何用 WH_GETMESSAGE郵件 Hook 會將 DLL 注入所有擁有訊息佇列的 GUI 進程
5 字節 JMPx86 近程相對跳轉指令固定 5 字節 (0xE9 + 32bit 位移)
VirtualProtectEx修改目標進程記憶體保護,需 PROCESS_VM_OPERATION 權限
OpenProcess 權限PROCESS_ALL_ACCESS = VM_OPERATION | VM_READ | VM_WRITE
HookOn/HookOff 配對自訂函數內呼叫原 API 時必須先 HookOff,避免無限遞迴
64 位元限制此技術僅適用 32 位元進程;x64 需不同指令編碼與更複雜的注入方式

參考資源

  • Jeffrey Richter, "Advanced Windows" - 記憶體管理與 Hook 章節
  • MSDN: VirtualProtectEx, WriteProcessMemory, SetWindowsHookEx
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved