随着 DirectX 12 成为 3D 游戏与 UE5 生态的标准渲染 API,传统基于系统 DLL 的 Hook 方案逐渐失效 —— 尤其是 UE5 引擎会自研编译 D3D12 核心运行时,脱离系统 DX 库的版本与符号约束,给逆向分析与 Hook 落地带来了更高的适配成本。传统人工逆向的方式不仅效率低下,还难以应对游戏版本迭代、超分辨率技术介入导致的渲染链路变化。

本文提出一套 AI 辅助的 D3D12 Hook 工程化落地方案,将 AI 能力融入模块识别、特征提取、反 Hook 绕过、兼容性适配全流程,同时给出从环境判定到 ImGui 渲染集成的完整实现代码,覆盖 UE5 自研运行时下的各类典型场景。

一、D3D12 渲染环境的 AI 智能感知体系

在启动 Hook 流程前,首先需要精准判定目标程序的 D3D12 运行时类型,这是后续所有方案适配的基础。

1.1 渲染 API 类型的判定逻辑

常规 Release 模式编译的 D3D12 程序会加载D3D12Core.dll作为核心运行时;若为 Debug 调试版本,则会加载d3d12SDKLayers.dll调试层 DLL。很多开发者容易忽略 Debug 版本的场景,直接按 Release 模式定位会导致特征匹配完全失效。

而 UE5 引擎的特殊之处在于:它并不依赖系统目录的 D3D12 运行时,而是随程序本体一同发布自研编译的D3D12Core.dll,其内部函数布局、虚表结构与系统官方 DLL 存在显著差异,直接复用系统 DLL 的偏移与特征码会完全匹配失败。

AI 落地实现方式

搭建轻量的模块识别模型,通过枚举目标进程的模块列表,结合 DLL 导出表、节区特征、版本资源三类维度进行自动分类:

  • 自动区分系统原生 DLL 与引擎自研 DLL,判定是否需要针对程序目录内的 DLL 做单独分析;
  • 自动识别 Release/Debug 运行时版本,匹配对应的分析策略;
  • 自动标记 UE5、Unity 等不同引擎的 D3D12 实现特征,输出预设的适配模板。

1.2 核心分析对象的选取

针对 UE5 自研的D3D12Core.dll,核心分析目标是ID3D12CommandQueueExecuteCommandLists函数 —— 这是 DX12 渲染管线中提交命令列表、执行绘制的核心入口,是实现渲染劫持、UI 叠加的关键 Hook 点。

传统方案中普遍选择 Hook 该函数本身,而更稳妥的方式是通过获取 CommandQueue 对象指针,替换其虚函数表完成 Hook,这种方式对函数体无修改,更难被检测。

二、AI 辅助的目标函数定位与特征工程

函数定位是逆向分析中最耗时的环节,通过 AI 辅助可以将人工分析的工作量降低 80% 以上。

2.1 ExecuteCommandLists 的虚表索引与特征提取

在标准 D3D12 实现中,ExecuteCommandListsID3D12CommandQueue虚函数表中索引为 10 的虚函数。但在 UE 自研的 DLL 中,需要通过反汇编分析确认索引的准确性。

传统人工分析流程

  1. 将目标目录下的D3D12Core.dll导入反汇编工具;
  2. 查找 CommandQueue 类的虚函数表,逐个核对函数功能;
  3. 定位ExecuteCommandLists函数体,手动提取入口附近的稳定字节作为特征码;
  4. 标记指令中的偏移位为通配符,生成可用于内存搜索的特征。

AI 自动化实现方案

通过大模型驱动的逆向分析工具自动完成全流程:

  1. 自动加载目标 DLL,解析 COM 接口的虚表结构,自动识别ExecuteCommandLists函数体;
  2. 自动分析函数入口前后的指令稳定性,过滤掉重定位、相对偏移等易变字节,生成带通配符的二进制特征码;
  3. 自动校验虚表索引的准确性,输出索引值与特征码的对应关系;
  4. 针对不同版本的 DLL,自动差分对比特征码的稳定性,保留跨版本不变的特征段。

2.2 Present 函数的定位与超分场景适配

IDXGISwapChain::Present是帧呈现的入口,也是实现 UI 叠加最常用的 Hook 点。该函数由dxgi.dll提供,通常使用系统版本,但在开启 GPU 加速、DLSS/FSR 等超分辨率技术后,Present 的调用链会插入中间层模块,直接查看堆栈上一层无法定位到主程序的调用点。

AI 落地解决思路

  1. 自动从系统dxgi.dll中提取 Present 函数的调用特征,记录偏移;
  2. Hook 断下后,AI 自动遍历堆栈返回地址,识别地址所属的模块;
  3. 自动过滤掉超分辨率中间模块、驱动模块的地址,精准定位到程序主模块的返回点;
  4. 针对不同超分技术加载的不同 DLL,自动适配搜索逻辑,无需人工调整。

三、反 Hook 机制的智能绕过与虚表替换实现

很多商业程序会对关键图形函数做 Inline Hook 保护,直接修改函数头会被检测,此时需要采用虚表替换的方案,并配合 AI 分析绕过检测逻辑。

3.1 函数头 Inline Hook 的自动识别

部分程序会在ExecuteCommandLists等关键函数的头部写入 5 字节跳转,实现自身的 Hook 与调用校验:当检测到调用来源不属于自身模块、堆栈返回异常时,会直接让调用失效,起到反调试、反 Hook 的作用。

这类保护的典型特征是函数入口不是标准的函数序言,而是一条跳转指令。其内部逻辑必然会在执行前还原函数头部的原始 5 字节。

AI 辅助分析流程

  • 自动扫描目标函数入口的前 16 字节,识别非标准序言的 Inline Hook;
  • 自动追踪跳转目标,分析 Hook 处理函数的逻辑,定位原始函数头的还原位置;
  • 自动在堆栈中向上回溯,定位到程序代码中调用该函数的真实位置,避开中间 Hook 层。

3.2 虚表替换的两种核心路径

虚表替换的核心原理是:在对象被调用时,通过寄存器获取对象首地址(即虚表指针的地址),将原虚表替换为构造的自定义虚表,从而劫持指定索引的虚函数。整个过程不修改函数体代码,隐蔽性远高于 Inline Hook。

路径 1:SwapChain 虚表替换(Present Hook)

在 Present 调用前的取虚表指令处下断,获取 rcx 寄存器中的 CDXGISwapChain 对象指针,替换其虚表。

典型调用点特征码:

48 8B 01 FF 50 40 45 85 FF 4C 8B 7C 24

路径 2:CommandQueue 虚表替换(ExecuteCommandLists Hook)

在 ExecuteCommandLists 调用前的指令处下断,获取 r12 寄存器中的 CommandQueue 对象指针,替换其虚表。

典型调用点特征码:

4D 8B 0C 24 49 8B CC 4C 8B 44 24 48 8B 94

工程实现建议:优先使用硬件断点实现 Hook 触发,不修改内存中的代码,规避内存校验类的反 Hook 检测。

四、D3D12 Hook 与 ImGui 渲染的完整工程实现

以下给出基于 Present Hook 的 D3D12 ImGui 渲染完整实现,包含设备初始化、描述符堆管理、命令列表执行、帧资源同步的全流程代码,适配 UE5 自研 D3D12 运行时环境。

cpp

运行

// 帧上下文结构体,每个后缓冲对应一个
struct FrameContext
{
    ID3D12CommandAllocator* commandAllocator;
    D3D12_CPU_DESCRIPTOR_HANDLE main_render_target_descriptor;
    ID3D12Resource* main_render_target_resource;
};

// 全局状态与资源变量
bool init = false;
bool swapchainInit = true;
UINT buffersCounts = 0;
FrameContext* frameContext = nullptr;
ID3D12Device* d3d12Device = nullptr;
ID3D12CommandQueue* d3d12CommandQueue = nullptr;
IDXGISwapChain3* D3dpSwapChain = nullptr;
ID3D12DescriptorHeap* d3d12DescriptorHeapBackBuffers = nullptr;
ID3D12DescriptorHeap* d3d12DescriptorHeapImGuiRender = nullptr;
ID3D12GraphicsCommandList* d3d12CommandList = nullptr;
HANDLE g_fenceEvent = nullptr;
HANDLE g_hSwapChainWaitableObject = nullptr;

// Present劫持函数
long __fastcall hookPresentD3D12(IDXGISwapChain3* pSwapChain, UINT SyncInterval, UINT Flags)
{
    // 首次调用:初始化D3D12资源与ImGui环境
    if (!init)
    {
        if (SUCCEEDED(pSwapChain->GetDevice(__uuidof(ID3D12Device), (void**)&d3d12Device))) 
        {
            ImGui::CreateContext();
            D3dpSwapChain = pSwapChain;
            unsigned char* pixels;
            int width, height;
            ImGuiIO& io = ImGui::GetIO(); 
            (void)io;
            
            io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
            ImGui::StyleColorsDark();
            io.Fonts->AddFontDefault();
            io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height);
            io.IniFilename = NULL;
            
            // 获取交换链描述,适配窗口模式
            DXGI_SWAP_CHAIN_DESC sdesc;
            pSwapChain->GetDesc(&sdesc);
            sdesc.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH;
            sdesc.OutputWindow = Engine::hWnd;
            sdesc.Windowed = ((GetWindowLongPtr(Engine::hWnd, GWL_STYLE) & WS_POPUP) != 0) ? false : true;

            buffersCounts = sdesc.BufferCount;
            frameContext = new FrameContext[buffersCounts];
            memset(frameContext, 0, buffersCounts * sizeof(FrameContext));
            
            // 创建渲染目标描述符堆
            D3D12_DESCRIPTOR_HEAP_DESC descriptorBackBuffers = {};
            descriptorBackBuffers.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV;
            descriptorBackBuffers.NumDescriptors = buffersCounts;
            descriptorBackBuffers.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;
            descriptorBackBuffers.NodeMask = 1;
            if (d3d12Device->CreateDescriptorHeap(&descriptorBackBuffers, IID_PPV_ARGS(&d3d12DescriptorHeapBackBuffers)) != S_OK)
                return Spoofer(Dx12SwapChainHook.GetOriginal<decltype(&hookPresentD3D12)>((8)), pSwapChain, SyncInterval, Flags);
            
            // 为每个后缓冲创建渲染目标视图
            const auto rtvDescriptorSize = d3d12Device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV);
            D3D12_CPU_DESCRIPTOR_HANDLE rtvHandle = d3d12DescriptorHeapBackBuffers->GetCPUDescriptorHandleForHeapStart();
            for (size_t i = 0; i < buffersCounts; i++) 
            {
                ID3D12Resource* pBackBuffer = nullptr;
                frameContext[i].main_render_target_descriptor = rtvHandle;
                pSwapChain->GetBuffer((UINT)i, IID_PPV_ARGS(&pBackBuffer));
                d3d12Device->CreateRenderTargetView(pBackBuffer, nullptr, rtvHandle);
                frameContext[i].main_render_target_resource = pBackBuffer;
                rtvHandle.ptr += rtvDescriptorSize;
            }

            // 创建ImGui渲染用的CBV_SRV_UAV描述符堆
            D3D12_DESCRIPTOR_HEAP_DESC descriptorImGuiRender = {};
            descriptorImGuiRender.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV;
            descriptorImGuiRender.NumDescriptors = buffersCounts;
            descriptorImGuiRender.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE;
            if (d3d12Device->CreateDescriptorHeap(&descriptorImGuiRender, IID_PPV_ARGS(&d3d12DescriptorHeapImGuiRender)) != S_OK)
                return Spoofer(Dx12SwapChainHook.GetOriginal<decltype(&hookPresentD3D12)>((8)), pSwapChain, SyncInterval, Flags);
            g_pd3dSrvDescHeapAlloc.Create(d3d12Device, d3d12DescriptorHeapImGuiRender);

            // 为每帧创建独立的命令分配器
            for (UINT i = 0; i < buffersCounts; i++)
                if (d3d12Device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&frameContext[i].commandAllocator)) != S_OK)
                    return Spoofer(Dx12SwapChainHook.GetOriginal<decltype(&hookPresentD3D12)>((8)), pSwapChain, SyncInterval, Flags);
            
            // 创建全局命令列表
            if (d3d12Device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, frameContext[0].commandAllocator, NULL, IID_PPV_ARGS(&d3d12CommandList)) != S_OK ||
                d3d12CommandList->Close() != S_OK)
                return Spoofer(Dx12SwapChainHook.GetOriginal<decltype(&hookPresentD3D12)>((8)), pSwapChain, SyncInterval, Flags);

            // 创建同步事件与帧延迟等待对象
            g_fenceEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr);
            if (g_fenceEvent == nullptr)
                return false;

            pSwapChain->SetMaximumFrameLatency(buffersCounts);
            g_hSwapChainWaitableObject = pSwapChain->GetFrameLatencyWaitableObject();

            // 初始化ImGui后端与主题
            ImGui_ImplWin32_Init(Engine::hWnd);
            Menu::loadTheme();
            g_pd3dSrvDescHeapAlloc.Alloc(&my_texture_srv_cpu_handle, &my_texture_srv_gpu_handle);
            bRetGetTexture = ImGui_LoadImage::LoadTextureFromMemory((UCHAR*)logo, sizeof(logo), d3d12Device, my_texture_srv_cpu_handle, &my_texture, &my_image_width, &my_image_height);
            ImGui_ImplDX12_Init(d3d12Device, buffersCounts,
                DXGI_FORMAT_R8G8B8A8_UNORM, d3d12DescriptorHeapImGuiRender,
                d3d12DescriptorHeapImGuiRender->GetCPUDescriptorHandleForHeapStart(),
                d3d12DescriptorHeapImGuiRender->GetGPUDescriptorHandleForHeapStart());
            ImGui_ImplDX12_CreateDeviceObjects();
        }
        init = true;
    }

    // 每帧渲染逻辑
    if (init)
    {
        if (d3d12CommandQueue == nullptr)
        {
            return Spoofer(Dx12SwapChainHook.GetOriginal<decltype(&hookPresentD3D12)>((8)), pSwapChain, SyncInterval, Flags);
        }

        // 交换链重建时解除临时Hook,避免重复初始化
        if (swapchainInit)
        {
            HDHook.UnHook();
            swapchainInit = false;
        }

        // ImGui新帧构建
        ImGui_ImplDX12_NewFrame();
        ImGui_ImplWin32_NewFrame();
        ImGui::NewFrame();
        Menu::get().Render();
        ImGui::Render();

        // 获取当前帧上下文并重置命令分配器
        FrameContext& currentFrameContext = frameContext[pSwapChain->GetCurrentBackBufferIndex()];
        currentFrameContext.commandAllocator->Reset();
        
        FrameContext* frameCtx = WaitForNextFrameResources(buffersCounts);
        frameCtx->commandAllocator->Reset();
    
        // 资源状态转换:呈现态 -> 渲染目标态
        D3D12_RESOURCE_BARRIER barrier = {};
        barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
        barrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE;
        barrier.Transition.pResource = currentFrameContext.main_render_target_resource;
        barrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;
        barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_PRESENT;
        barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_RENDER_TARGET;
        
        d3d12CommandList->Reset(frameCtx->commandAllocator, nullptr);
        d3d12CommandList->ResourceBarrier(1, &barrier);

        // 绑定渲染目标与描述符堆,绘制ImGui
        d3d12CommandList->OMSetRenderTargets(1, &currentFrameContext.main_render_target_descriptor, FALSE, nullptr);
        d3d12CommandList->SetDescriptorHeaps(1, &d3d12DescriptorHeapImGuiRender);
        ImGui_ImplDX12_RenderDrawData(ImGui::GetDrawData(), d3d12CommandList);
        
        // 资源状态转换:渲染目标态 -> 呈现态
        barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_RENDER_TARGET;
        barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_PRESENT;
        d3d12CommandList->ResourceBarrier(1, &barrier);
        d3d12CommandList->Close();

        // 提交命令列表到GPU执行
        d3d12CommandQueue->ExecuteCommandLists(1, reinterpret_cast<ID3D12CommandList* const*>(&d3d12CommandList));
    }

    // 调用原始Present完成帧呈现
    return Spoofer(Dx12SwapChainHook.GetOriginal<decltype(&hookPresentD3D12)>((8)), pSwapChain, SyncInterval, Flags);
}

五、AI 驱动的工程化优化与落地保障

5.1 多场景兼容性自动适配

AI 适配引擎可以自动识别程序的渲染配置:自动检测是否开启超分辨率技术并调整堆栈定位逻辑,自动识别窗口 / 全屏模式的交换链差异,自动适配不同厂商 GPU 的驱动特性,规避驱动层兼容性问题。

5.2 运行时稳定性监控

AI 监控模块实时检测渲染状态:自动监控描述符堆溢出、命令分配器泄漏等常见问题;出现渲染崩溃时自动回退 Hook 并保留现场日志;自动统计帧耗时,避免 Hook 逻辑导致的性能下降。

5.3 版本迭代自动化更新

程序版本更新后,AI 自动扫描新的 DLL 文件,重新匹配特征码、更新 Hook 点偏移,对比虚表结构变化并校验索引有效性,无需人工逆向分析即可完成版本适配。

六、分阶段落地执行清单

阶段 1:环境准备与样本采集

  1. 搭建目标程序运行环境,获取对应版本的 D3D12Core.dll 与 dxgi.dll;
  2. 部署 AI 逆向分析工具,导入目标 DLL 进行初步解析;
  3. 确认渲染 API 版本、运行时类型、引擎版本。

阶段 2:AI 辅助分析与特征生成

  1. 运行 AI 分析脚本,自动定位 ExecuteCommandLists 与 Present 的调用点;
  2. 生成稳定的内存搜索特征码,确认虚表索引;
  3. 分析反 Hook 机制,确定虚表替换的触发位置。

阶段 3:Hook 框架开发

  1. 实现硬件断点 Hook 框架,支持指定地址下断与寄存器读取;
  2. 实现虚表替换逻辑,构造自定义虚函数表;
  3. 完成 CommandQueue 对象指针的获取与存储。

阶段 4:ImGui 渲染集成

  1. 实现 D3D12 设备、描述符堆、命令分配器的初始化;
  2. 集成 ImGui 的 DX12 后端与 Win32 后端;
  3. 实现帧资源同步与渲染命令提交。

阶段 5:测试与优化

  1. 使用 AI 自动化测试工具,遍历不同渲染配置下的稳定性;
  2. 性能调优,控制 Hook 逻辑的额外开销;
  3. 兼容性验证,覆盖不同 GPU、不同系统版本。

阶段 6:上线与监控

  1. 灰度发布,收集异常反馈;
  2. 接入 AI 监控系统,实时追踪运行状态;
  3. 建立版本更新的自动化适配流程。

这套 AI 辅助的 D3D12 Hook 方案,将传统人工逆向的经验转化为可自动化执行的流程,大幅降低了 UE5 等现代引擎下图形 Hook 的开发与维护成本。相比于传统方案,AI 赋能后的适配效率与稳定性都有显著提升,尤其适合需要快速适配多场景、频繁应对版本更新的工程场景。