目前仅在 Windows 11 25H2 系统 使用NVIDIA显卡驱动 物理机环境下测试有效
源码在文章最后
挂钩函数
位于 dwmcore.dll 的 COverlayContext::Present
函数原型 (win11 24h2 以前的旧版本)
__int64 __fastcall COverlayContext::Present(
COverlayContext *this,
__int64 a2,
unsigned int a3,
__int64 a4,
int a5,
char a6
);
在win11 24h2更新后变为 (win11 24h2 及以上版本)
__int64 __fastcall COverlayContext::Present(
CDirectFlipInfo **this,
struct IOverlaySwapChain *a2,
unsigned int a3,
__int64 a4,
int a5,
unsigned __int8 *a6,
char a7
);
其中的 a2 参数指向一个 IOverlaySwapChain 类对象
不同系统需要使用不同的Hook回调
// COverlayContext::Present 函数原型
typedef __int64 (COverlayContext_Present_old)(void* _this, void* a2, unsigned int a3, __int64 a4, int a5, bool a6);
static COverlayContext_Present_old* COverlayContext_Present_old_orig = nullptr; // Windows 11 24H2 以前
typedef __int64 (COverlayContext_Present)(void* _this, void* a2, unsigned int a3, __int64 a4, int a5, void* a6, bool a7);
static COverlayContext_Present* COverlayContext_Present_orig = nullptr;
// Hook回调
static __int64 COverlayContext_Present_old_hook(void* _this, void* IOverlaySwapChain, unsigned int a3, __int64 a4, int a5, bool a6)
{
return COverlayContext_Present_old_orig(_this, IOverlaySwapChain, a3, a4, a5, a6);
}
static __int64 COverlayContext_Present_hook(void* _this, void* IOverlaySwapChain, unsigned int a3, __int64 a4, int a5, void* a6, bool a7)
{
return COverlayContext_Present_orig(_this, IOverlaySwapChain, a3, a4, a5, a6, a7);
}通过特征码获取函数位置
不同系统的函数头部特征均有变化,可以使用winbindex获取不同系统的 dwmcore.dll 文件
Winbindex - The Windows Binaries Index
const unsigned char COverlayContext_Present_bytes_win10[] = {
0x48, 0x89, 0x5c, 0x24, 0x08, 0x48, 0x89, 0x74, 0x24, 0x10, 0x57, 0x48, 0x83, 0xec, 0x40, 0x48, 0x8b, 0xb1, 0x20,
0x2c, 0x00, 0x00, 0x45, 0x8b, 0xd0, 0x48, 0x8b, 0xfa, 0x48, 0x8b, 0xd9, 0x48, 0x85, 0xf6, 0x0f, 0x85
};
const unsigned char COverlayContext_Present_bytes_w11_21h2[] = {
0x48, 0x33, 0xC4, 0x48, 0x89, 0x44, 0x24, 0x50, 0x48, 0x8B, 0xB1, 0xA0, 0x2B, 0x00, 0x00, 0x48, 0x8B, 0xFA,
0x48, 0x8B, 0xD9, 0x48, 0x85, 0xF6
};
const unsigned char COverlayContext_Present_bytes_w11_22h2[] = {
0x40, 0x53, 0x55, 0x56, 0x57, 0x41, 0x56, 0x41, 0x57, 0x48, 0x81, 0xEC, 0x88, 0x00, 0x00, 0x00,
0x48, 0x8B, 0x05, '?', '?', '?', '?', 0x48, 0x33, 0xC4, 0x48, 0x89, 0x44, 0x24, 0x78, 0x48
};
const unsigned char COverlayContext_Present_bytes_w11_24h2[] = {
0x4C, 0x8B, 0xDC, 0x56, 0x41, 0x56
};
const unsigned char COverlayContext_Present_bytes_w11_25h2[] = {
0x40, 0x55, 0x53, 0x56, 0x57, 0x41, 0x54, 0x41, 0x55, 0x41, 0x56, 0x41, 0x57, 0x48, 0x8D, 0x6C,
0x24, 0xF9, 0x48, 0x81, 0xEC, 0xF8, 0x00, 0x00, 0x00, 0x48, 0x8B, 0x05, '?', '?', '?', '?',
0x48, 0x33, 0xC4, 0x48, 0x89, 0x45, 0xEF, 0x4C, 0x8B, 0x65, '?',
0x48, 0x8B, 0xD9
};其中 win11 22h2 和 win11 23h2 函数特征相同
通过IOverlaySwapChain获取后备缓冲区 (ID3D11Texture2D)
在 win11 24h2 及以前的系统,可通过偏移值直接获取到 IDXGISwapChain 类对象,然后通过 IDXGISwapChain->GetBuffer 获取后备缓冲区
| 系统版本 | 偏移值
| 备注 |
| Windows 10 | -0x118 |
|
| Windows 11 21H2 | -0x148 |
|
| Windows 11 22H2 及 23H3 | +0xE0
| 需要先通过负偏移读取修正值 |
| Windows 11 24H2 | +0x108
|
|
if (g_WinVersion == WIN10)
swapChain = *(IDXGISwapChain**)((unsigned char*)overlaySwapChain - 0x118);
else if (g_WinVersion == WIN11_21H2)
swapChain = *(IDXGISwapChain**)((unsigned char*)overlaySwapChain - 0x148);
else if (g_WinVersion == WIN11_22H2_23H2)
{
int sub = *(int*)((unsigned char*)overlaySwapChain - 4);
void* real = (unsigned char*)overlaySwapChain - sub - 0x1B0;
swapChain = *(IDXGISwapChain**)((unsigned char*)real + 0xE0);
}
else if (g_WinVersion == WIN11_24H2)
swapChain = *(IDXGISwapChain**)((unsigned char*)overlaySwapChain + 0x108);ID3D11Texture2D* backBuffer = nullptr;
HRESULT hr = swapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), (void**)&backBuffer);
if (FAILED(hr) || !backBuffer) return nullptr;
return backBuffer;
在win11 25h2 及以后的系统重构了 COverlayContext 的内存布局
调用 IOverlaySwapChain 虚函数表索引24的 CLegacySwapChain::GetPhysicalBackBuffer 获取 CLegacySwapChainBuffer
再调用 CLegacySwapChainBuffer 虚函数表索引19的 CLegacySwapChainBuffer::GetD3D11Resource 获取D3D11Resource
typedef void* (__fastcall* VirtFunc)(void*);
// 从 overlaySwapChain 获取虚函数表,调用索引 24 的函数 CLegacySwapChain::GetPhysicalBackBuffer
void** vt = *(void***)overlaySwapChain;
VirtFunc func1 = (VirtFunc)vt[24]; // 索引 24
void* r1 = func1(overlaySwapChain); // 返回 CLegacySwapChainBuffer 对象
// 从 CLegacySwapChain 获取虚函数表,调用索引 19 的函数 CLegacySwapChainBuffer::GetD3D11Resource
void** vt2 = *(void***)r1;
VirtFunc func2 = (VirtFunc)vt2[19]; // 索引 19
void* r2 = func2(r1); // 返回纹理对象(IUnknown*)
将指针类型从 IUnknown* 转换为强类型的 ID3D11Texture2D*,并内部调用 AddRef 增加一次引用计数。
ID3D11Texture2D* tex = nullptr;
HRESULT hr = ((IUnknown*)r2)->QueryInterface(IID_ID3D11Texture2D, (void**)&tex);
if (FAILED(hr) || !tex) return nullptr;
最后得到后备缓冲区
使用ID3D11Texture2D初始化ImGui并渲染自定义绘制
注意:需要手动设置ImGui 当前渲染目标(后备缓冲区)的尺寸
DWM 没有窗口句柄,ImGui 无法自动获取客户区尺寸。如果不手动设置,DisplaySize 会保持为 0,导致 ImGui 无法计算布局,所有绘制命令都不会生成
// 初始化ImGui
D3D11Texture2D->GetDevice(&g_pd3dDevice);
g_pd3dDevice->GetImmediateContext(&g_pd3dContext);
ImGui::CreateContext();
ImGui_ImplDX11_Init(g_pd3dDevice, g_pd3dContext);
// 获取后备缓冲区尺寸
D3D11_TEXTURE2D_DESC bbDesc;
3D11Texture2D->GetDesc(&bbDesc);
// 设置 ImGui 显示尺寸
ImGui::GetIO().DisplaySize = ImVec2((float)bbDesc.Width, (float)bbDesc.Height);
// ##################只调用一次,避免性能浪费
// 创建 RTV
if (g_prtView) { g_prtView->Release(); g_prtView = nullptr; }
g_pd3dDevice->CreateRenderTargetView(D3D11Texture2D, nullptr, &g_prtView);
// 释放后备缓冲区
D3D11Texture2D->Release();
// 保存原始渲染目标
ID3D11RenderTargetView* prevRTV = nullptr;
ID3D11DepthStencilView* prevDSV = nullptr;
g_pd3dContext->OMGetRenderTargets(1, &prevRTV, &prevDSV);
// 设置我们的渲染目标
g_pd3dContext->OMSetRenderTargets(1, &g_prtView, nullptr);
// 开始 ImGui 帧
ImGui_ImplDX11_NewFrame();
ImGui::NewFrame();
// ########################################################
// ############## 这里开始我们的自定义绘制 ################
// ########################################################
// 绘制矩形
ImDrawList* draw_list = ImGui::GetForegroundDrawList();
draw_list->AddRectFilled(ImVec2(100, 100), ImVec2(300, 200), IM_COL32(255, 0, 0, 255));
// ########################################################
// ######################## 结束 ##########################
// ########################################################
ImGui::Render();
ImGui_ImplDX11_RenderDrawData(ImGui::GetDrawData());测试效果
win11 25h2 物理机

然后我们进入游戏,设置全屏模式或窗口化全屏模式,我们的绘制又又又失效了。原因是启用了Independent Flip(独立翻转)。画面直接交给显示器,不再经过DWM进程了。
禁用Independent Flip(独立翻转)
Independent Flip(独立翻转)
WDDM 1.3+ 引入的 DXGI 翻转模型高级输出模式,游戏 / 应用帧直接交给显示硬件扫描输出,完全绕过 DWM 用户态合成
Composed Flip(合成翻转)
Windows DXGI 翻转呈现模型(Flip Model)下的一种窗口化呈现状态,应用交换链缓冲区与 DWM(桌面窗口管理器)共享、并由 DWM 完成桌面画面合成后再输出
WDDM 3.2
Windows11 24H2以上系统使用,MPO 调度重构,激进的 Independent Flip 策略,需要通过注册表强制禁用
方案一:Windows 11 24H2 及以上版本(24H2 / 25H2)
在显卡驱动层面全局禁用硬件覆盖平面(MPO),彻底关闭 Independent Flip。
1. 按 Win + R 输入 regedit ,打开注册表编辑器
2. 定位到以下路径:
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\GraphicsDrivers
3. 右侧空白处右键 → 新建 → DWORD (32 位) 值,命名为 DisableOverlays
4. 双击该值,将「数值数据」改为 1 ,基数保持「十六进制」
5. 重启电脑生效
方案二:Windows 10 / Windows 11 21H2 / 22H2 / 23H2 旧版本 (未测试)
作用于 DWM 合成器层面,强制所有窗口走合成路径。
1. 按 Win + R 输入 regedit ,打开注册表编辑器
2. 定位到以下路径:
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\Dwm
3. 右侧空白处右键 → 新建 → DWORD (32 位) 值,命名为 OverlayTestMode
4. 双击该值,将「数值数据」改为 5 ,基数保持「十六进制」
5. 重启电脑生效
Windows 11 24H2 及以上系统,绝对禁止同时设置以下两个注册表键值
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\GraphicsDrivers → DisableOverlays
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\Dwm → OverlayTestMode
同时配置驱动级与 DWM 合成器级的 MPO 禁用参数,会导致 DWM 渲染管道死锁,触发 dwm.exe 无限循环崩溃、桌面反复黑屏重启。(游戏进程全屏后)
最后
项目源码:d2cK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6j5K9h3q4G2b7X3g2A6K9h3E0Q4x3V1k6p5N6$3#2Q4x3X3c8u0L8X3A6W2j5%4c8A6L8$3&6Q4x3X3c8u0L8f1N6#2K9g2!0q4c8W2!0n7b7#2)9^5z5q4!0q4z5g2)9^5y4#2)9^5b7#2!0q4z5g2)9&6c8q4!0m8x3W2!0q4y4#2)9&6b7g2)9^5y4p5c8%4L8b7`.`. Injection ImGui 2)
本项目仅用于 Windows 系统底层技术学习与研究用途,禁止用于任何非法场景、违规软件、游戏外挂开发或商业用途。
冰与火的战歌:Windows内核攻防实战高级班!从零到实战,融合AI与Windows内核攻防全技术栈,打造具备自动化能力的内核开发高手。
最后于 2天前
被mb_pelomxiq编辑
,原因: