当前位置: 首页 > news >正文

Windows 平台 HOOK DWM 桌面管理程序,实现输出变形的桌面图像到显示器

 by fanxiushu 2025-10-10 转载或引用请注明原始作者。

输出变形桌面到显示器,这功能有啥用?
其实就是把电脑屏幕显示到显示器之前,把图像做变形,
然后再传输到显示器,这样显示器看到的就是乱七八糟变形之后的电脑桌面。
这有啥用?难道就只是图个好玩?
如果图个好玩,可以去使用我的xdisp_virt程序,里边的图像特效就是专门做得桌面变形,只不过需要远程使用。
当然也在计划把DWM HOOK功能集成到xdisp_virt程序中作为一项试验性质的功能,
到时不单可以远程玩,在本地电脑上也能玩了。
下面是演示视频:
 

WIN10,WIN11系统下DWM HOOK演示视频


视频中采用暴力手段终结dwm.exe程序来结束dwm hook,这是因为目前还在开发测试阶段,
等以后集成到xdisp_virt作为一个试验功能之后,就不会这么暴力了。

其实吧,也有实际用处,
比如桌面融合:多个投影仪投影到一个大屏幕上,多个投影仪一起组成扩展显示器功能,
但是投影仪本身的原因,不可能方方正正的规规矩矩的投影形成一个统一的电脑大屏幕。
要么有重叠,要么角度问题等等,这个时候,就需要对投影的图像做调整,使得最终看上去像一个整体,这就是桌面融合技术。
本质其实就是对每个桌面图像进行变形,调整,从而把多个投影更好的融合在一起形成一个整体。

本文不讲桌面融合技术,那样还会牵涉到图形融合算法。
本文只阐述要如何做到电脑桌面变形这种功能。
这听起来似乎没啥难度,但其实是最烦人的,因为它没有统一标准,
windows平台的其中一个解决办法几乎得靠破解DWM桌面管理器来解决这个问题。

很早前我的文章在阐述IDD驱动的时候,就提到过:
https://blog.csdn.net/fanxiushu/article/details/106238853?spm=1001.2014.3001.5501
(WIN10系统 Indirect Display 虚拟显示器之特殊应用)
当时是基于IDD(Indirect Display Driver)讲的,
但是主要还是需要通过IDD引申出一个扩展的硬件接口,比如USB,HDMI显示器接口等。
正常来说,我们的显示器都会插到显卡留下的接口上,比如DisplayPort,HDMI,VDI,VGI等物理接口。
但经过IDD之后的扩展接口则是独立的,要么是无线(也就是无线显示器),
要么是我们自己开发的其他物理接口,反正不会是显卡留下的物理接口。
而我们恰好可以在IDD里边做手脚,做图像变形,然后再输出。通过IDD输出到投影仪上,那可以在IDD中做桌面融合。
这是其中一种桌面融合方案。

而现在的某些显卡,其实本身就具备了融合功能,那是显卡硬件实现的,
这是利用显卡自身能力实现桌面融合的方案,
这可能是性能最好的一种方案,毕竟是直接硬件处理,但取决于特定的显卡,这里不阐述。

如果显卡硬件自身没有变形图像的能力,但是又想让直接插到显卡接口上的显示器看到桌面图像变形。
(注意这里是把显示器直接插到显卡提供的物理接口,如果不是这样,则完全可以直接使用通用的IDD方案了)
这种情况下,就只有破解 Hook 桌面管理程序DWM,然后挂钩某些关键函数,
从这些函数中获取到准备传输给显卡对应物理接口的图像数据,然后做图形变换,最后再发给显示器。
这很像显卡过滤一类的功能,但很早前就说过了,显卡没有这种类型的过滤驱动。
我们在应用层通过一些非常规手段,可直接获取到显卡的图像数据,而且比在内核驱动中获取这个图像数据更容易。
这是windows平台的显卡驱动的运作模式决定的
(图像在应用层渲染,内核层只处理基本的核心逻辑,DWM在应用层合成桌面图像)。

DWM桌面管理器是从WIN7以后的系统才开始出现的,win7只有aero特性才需要dwm,
到了win8,win10之后,DWM必须运行,它的功能就是把各个程序窗口合并合成,生成一个总的桌面图像,
然后发给对应的显示器去显示。
很早前的文章,其实也简单阐述过dwm的作用和功能。
如下的文章链接:
(都是阐述如何做DirectX HOOK从而截取全屏游戏内容的,
之所以在这里提及这两篇文章,是因为DWM HOOK 和DirectX HOOK的原理雷同。)
https://blog.csdn.net/fanxiushu/article/details/89363222?spm=1001.2014.3001.5501
(Windows桌面实现之七(DirectX HOOK 方式截取特殊的全屏程序之一))
https://blog.csdn.net/fanxiushu/article/details/89426367?spm=1001.2014.3001.5501
(Windows桌面实现之八(DirectX HOOK 方式截取特殊的全屏程序之二))

本文阐述的核心部分: DWM HOOK 的原理跟 DirectX HOOK 的原理非常像。
都是通过挂钩手段,把自己的dll注入到目标进程,然后挂钩某个关键函数,然后从中获取到图像数据。
不同的是,本文只挂钩 dwm.exe 程序,且不再挂钩 DirectX图形库的 Present 函数,而是其他关键函数。
但是不管哪类函数, 根据windows的显卡驱动特点,图像数据最终都需要展现(Present)。
也就是虽然不是DirectX图像库的 Present 函数,但是会是别的类似 Present 的函数。
当我们挂钩到这个类似的Present关键函数之后,
我们就可以想法获取到即将发给显示器的图像数据,然后就可以随心所欲的乱改图像数据,然后再发给显示器了。

虽然说原理看起来挺简单的,但麻烦就在dwm程序中如何定位类似 Present 这样的函数。

当然,这也应该是那些破解爱好者最爱干的事情,有了个大的方向,
接下来就是从dwm.exe程序,其实主要是dwm程序提供的动态库入手。
然后我们可以看到system32目录下除了dwm.exe,更关键的还有个 dwmcore.dll 动态库,这个dll才是破解的关键。
除了dwmcore.dll之外,还有一个动态库也关键,那就是 dxgi.dll,下面会陆续提到。

我们使用IDA打开dwmcore.dll, 同时记得从微软官网下载dwmcore.dll对应的pdb符号文件,
如果IDA配置正确,它会帮你自动下载和加载pdb符号文件。
有了pdb符号文件也是一个关键,因为光是dll动态库,看到的都是一堆分不清东南西北的函数,
只有pdb符号文件才能让你看明白这些函数具体的名字,从而更容易推导出它的实际用途。

windows各个版本系统组件(系统的sys和dll等)都能在官网下载到对应的pdb符号文件,
这方面微软做得也算“良心”吧,让破解的人少浪费点精力。
(当然提供pdb符号文件的本意肯定不是让人去做破解的,
而是用于开发者调试程序的目的,以及帮忙开发者更快定位系统的bug等)

如下图所示:
这是我的 WIN10系统 22H2最新版本的dwmcore.dll 通过IDA 解析之后的图示。



上图光是看看左边,那里显示出来的函数名都是非常非常的多。更别说右边对应的汇编代码了。
所以做破解,你得有超强的耐心和猜测心,大量的时间都会花在猜测--验证--猜测--验证的循环中。
上面的这种情况还算是好的,因为提供了完整的pdb符号文件,各个函数的命名都算规范,比较容易找到规律。
对于那种没有pdb符号文件,甚至编译程序的时候,压根不把调试符号嵌入程序的。
用IDA打开的话,就只能看到一堆看不到任何意义的函数名字,那种情况下去猜测某个函数是干什么的,那就更难了。
当然也可以通过在线调试,下断点等,一点一点的找到程序运行规律,但是太麻烦了,起码我是没这个耐心的。

现在我们来仔细研究图中的A,B,C,D四个着重标记的地方。

先看标记A。
前面说了,我们需要寻找类似Present的函数,所以可以先在IDA左边的函数集合中搜索Present字符串。
当然搜出来一大堆包含Present的函数,为何现在偏偏定位到 COverlayContext::Present 这个函数上,
自然是边猜测边验证(
验证办法之一就是直接断点调试,看看调用堆栈以及运行情况等,
方法二直接写个dll挂钩进去,对每个怀疑函数挂钩尝试。),
当然,我并没如此做,是根据github上的一个dwm_lut 项目上的源代码中看出来的。
https://github.com/lauralex/dwm_lut
https://github.com/ledoge/dwm_lut

因为他的源代码里定义了 COverlayContext_Present_bytes 这样的变量,
与IDA 里边的函数集合一对照,立即就能知道是 COverlayContext::Present  ,
其实还有个 COverlayContext::PresentMPO,
这个才是目前绝大部分真实电脑运行的函数,因为现在显卡基本都支持MPO。
但是我们可以找到 COverlayContext::OverlaysEnabled 这个函数,然后挂钩它,并且返回 false,
这样就能强迫 dwm 桌面管理器程序只调用 COverlayContext::Present  函数了。

这个函数的规律,在win10 20h1以上好像都是成立的,我没具体去查看版本号,
只知道在 WIIN10 1909之前的版本都没有COverlayContext::Present 这个函数,所以应该是 2020年之后的版本才出现的。
并且WIN11也一直是这个函数,但是到了 WIN11 的24H2版本这个函数的参数变了,
同时到目前为止,最新版本的 24H2 也没法破解出 IDXGISwapChain 交换链地址,
因此WIN11 24H2之后的版本目前没辙。但是呢,
如果是老旧接口,比如VGA,VDI,旧的HDMI接上24H2 还是有的玩,下面会简单提及。
或者把你最新的windows11 退回到 23H2之前,或者干脆装个最新的 windows10, 也能使用。

现在看看在我的电脑 最新版本的WIN10 22H2上,这个函数的具体定义(来自IDA的解析):
int64  COverlayContext::Present(IOverlaySwapChain *,uint,std::vector<tagRECT> const &,uint,bool)
这是一个类成员函数,如果我们要挂钩,得把他当成全局函数来看,因此真正的定义应该如下:
int  COverlayContext_Present(void* thisptr, 
                    IOverlaySwapChain *OverlaySwapChain,  
                     uint a3, std::vector<tagRECT> const &a4 , uint a5, bool a6);

得知了这个函数的功能以及具体参数,现在我们就该挂钩这个函数了。
至于如何挂钩函数,我在很早前讲述 DX HOOK两篇文章都已经提到过,具体也不再这里赘述了。
现在的问题是如何得知 COverlayContext::Present 这个函数的地址,这才是关键的问题。
上图中 B和C标记。
图中标记的地址是:00000001800EBBD8,
而dwmcore.dll在IDA中加载地址是 0000000180000000
所以两个一减, COverlayContext::Present相对 dwmcore.dll 的地址就是  EBBD8 。

现在最新版本的WIN10,22H2这个地址是确定了,其他版本咋办呢?
我们有个简单办法:
搜集所有2020年以上的win10,win11版本,虽然多,但我想不会超过20个吧。
然后每个版本的dwmcore.dll都使用IDA查一遍,记录地址和对应系统的版本号,
形成一个数据库,然后程序里根据这个数据找到对应地址。
除了这个办法,我们还可以根据 COverlayContext::Present函数的特征码,
也就是说不同版本虽然地址不同,但是这个函数只要没修改过,特征码就会保持不变。
然后搜索这个函数的一些特征字节,从而也能定位到这个函数地址,
当然如果不同版本这个函数修改过,那就得使用修改过的特征码来查找。

有了这个地址,我们在被挂钩的DLL中 使用
GetModuleHandle("dwmcore.dll") + EBBD8(COverlayContext::Present的偏移地址)。
就能定位这个函数在内存中的地址,接着使用detours或者其他挂钩的库来挂钩这个函数。
于是我们距离成功接近了一大步。

现在我们挂钩了COverlayContext::Present 函数,同时也能测试到此函数被不断的调用了。
现在的问题是:如何获取到屏幕数据?
我们在看这个函数的参数,有个非常重要的参数 IOverlaySwapChain ,一看名字,就是一个交换链。
很可惜,这个不是我们认识的IDXGISwapChain,而且只有名字,没有这个数据结构的具体定义。
所以下一步还得搞清楚这个结构用来干嘛,
起码得从这个结构中找出我们在DirectX图像库中正常使用的 IDXGISwapChain 交换链。
如果找不到 IDXGISwapChain 交换链,就没法跟DirectX联系起来,没有DirectX的帮忙,那处理起图形来也是举步维艰。
但是 IOverlaySwapChain 是个不知道具体结构内容的东西,因此还得通过破解手段从中获取 IDXGISwapChain,
一开始我把他猜测成COM接口,想通过QueryInterface接口查询到IDXGISwapChain结果以程序崩溃告终。
所以还得从破解代码入手。
我们先假设 IDXGISwapChain 是存储在 IOverlaySwapChain 的某个位置中,
因此只要找到这个偏移值,也就能通过 IOverlaySwapChain 地址计算出 IDXGISwapChain 的地址。
在WIN10系统中确实是这样,这个偏移值固定为 -280。
但是到了win11,情况就挺复杂了,在dwm_lut项目中,我不知道他是怎么破解出来的,
绕了几圈才找到 IDXGISwapChain,而且对最新版本的 WIN11 24H2 也不再有效。

由于对 IOverlaySwapChain的贫乏的了解,我也无法描述得清楚。
但愿有这个志向的破解者早日在最新的WIN11 24H2版本中的根据 IOverlaySwapChain 找到 IDXGISwapChain 交换链地址。
但是可能有个更加糟糕的情况:
Win11 24H2最新版本中使用直接调用底层驱动(渲染驱动接口)来展现图形数据(具体有待验证),
意思就是说不再调用任何 跟 DirectX 相关的接口。
本来DirectX接口就是在底层驱动接口上建立起来的,所以直接调用底层接口可能更高效些。
这种情况下,是无法从 IOverlaySwapChain 获取 IDXGISwapChain 的,因为压根就么有。

上面扯了这么多,其实集中就在如何解决从  IOverlaySwapChain 获取 IDXGISwapChain的问题。
虽然最新win11 24h2是个麻烦,但是老版本的WIN11,以及2020年之后的WIN10都是有效的。
所以还是有用处。
先看下面的代码:
static INT64 my_COverlayContext_Present(
    void* thisptr, struct IOverlaySwapChain* overlay_sc,
    unsigned int a3, vector<tagRECT> const & rc_arr, unsigned int a5, bool a6)
{
//    log_printf("-- Present. rc_Arr=%d; a3=%d,a5=%d,a6=%d\n", rc_arr.size(),a3,a5,a6);

    IDXGISwapChain* sc = NULL; //严格的说应该是 IDXGISwapChainDWMLegacy,
    if (os_ver.dwBuildNumber >= 26100) {/// win11 24h2
        //////偏移不对,还是说不再有 IDXGISwapChain 了?
        sc = NULL;
    }
    else if (os_ver.dwBuildNumber >= 22000) {// win11
        int sub_from_legacy_swapchain = *(int*)((unsigned char*)overlay_sc - 4);
        void* real_overlay_swap_chain = (unsigned char*)overlay_sc - sub_from_legacy_swapchain -
            0x1b0;
        sc = *(IDXGISwapChain**)((unsigned char*)real_overlay_swap_chain +
            224);
    }
    else {// win10
        sc = *(IDXGISwapChain**)( (unsigned char*)overlay_sc - 280);
            
    }

    dwm_swap_data_t* sd = NULL;
    if (sc) {
        sd = RunningSwapChain(sc, 3);
    }
    if (sd && sd->is_drawed) {

    }
    
    INT64 ret = org_COverlayContext_Present(thisptr, overlay_sc, a3, rc_arr, a5, a6);
    
    ///恢复back buffer缓存
    RestoreSwapChain(sd);

    return ret;
}

my_COverlayContext_Present 是替换 COverlayContext::Present 的函数。
在我们的替换函数中,首先就应该获取到 IDXGISwapChain,严格来说应该是IDXGISwapChainDWMLegacy接口,
因为dwmcore.dll代码就是使用的这个接口,但是两个接口中的GetDevice,GetBuffer这两个函数,他们的位置是一致的,
而我们恰恰只需要用到 IDXGISwapChain 里边的GetDevice和GetBuffer两个函数,
所以选择我们熟悉的IDXGISwapChain。
同时里边寻找IDXGISwapChain的都是一些固定编码值,而且也只支持64位系统。

接下来就是RunningSwapChain 是我自己定义的函数了,
在里边主要是 调用GetDevice接口获取 D3D11设备,调用GetBuffer获取back buffer,也就是即将展现到显示器的图形数据。
然后就是做各种图形变换运算,当然因为变形图形算法比较杂,
采用的是 “图形数据COPY到CPU -》运算变形 -》再 COPY回GPU”  的办法处理的。效率嘛,肯定比较差了。

然后就是调用原始函数org_COverlayContext_Present,把被我们改过的图像数据展现给显示器。
最后就是复原back buffer 。

最新版本真的就没办法了吗? 其实还有有办法的,但是还是得有限制条件:
那就是是电脑显卡要有老旧的接口,比如VGA,VDI以及旧版本的HDMI接口。
同时我们不再替换 COverlayContext::Present函数了,
而是在dxgi.dll 动态库中,替换
CDXGISwapChainDWMLegacy::PresentDWM ,
CDXGISwapChainDWMLegacy::PresentMultiplaneOverlay
等函数,
看名字带Legacy,就是遗留的意思,也就是过去的老旧的接口模式。
为何这么说呢?
因为我家电脑,一台miniPC集成了HDMI输出接口,一台miniPC只有typec输出的雷电接口(DIsplayPort)。
装的都是最新版本的 WIN11 24H2。
还有我的笔记本,装的是最新版本 WIN10 22H2,
笔记本也支持 typec输出的雷电接口。

我同时实现挂钩 COverlayContext::Present 和 CDXGISwapChainDWMLegacy::PresentDWM 两种方案。
测试结果如下:
在HDMI输出miniPC上,可以通过CDXGISwapChainDWMLegacy::PresentDWM 拦截处理图形数据,即便系统是最新的是WIN11 24H2。
而在只有typec雷电接口的miniPC上,压根不走 CDXGISwapChainDWMLegacy::PresentDWM  ,只走 COverlayContext::Present。
同样的,我的笔记本WIN10,当我使用笔记本自己的显示器的时候,他能调用 CDXGISwapChainDWMLegacy::PresentDWM,
说明我的笔记本内置的显示器接口比较老。
但是到了 我把笔记本通过typec外接显示器的时候,情况就跟我的只有雷电接口的miniPC上的情况一样了。

说了这么多,其实总结起来就是:不管是win10还是win11,只要显卡是老旧接口(VGA,VDI,旧HDMI)。
都能通过 CDXGISwapChainDWMLegacy::PresentDWM 方案获取,而不再需要 COverlayContext::Present。
(但是现在的新电脑有多少是老旧接口的呢?)

我们可以查看上图的D标记。
图中有个  COverlayContext::LegacyPresentRequired 函数(这个函数到了win11已经没有了,是其他判断函数)。
也就是在COverlayContext::Present 函数中,首先调用 COverlayContext::LegacyPresentRequired 判断是否Legacy,
是的话就转到  CDXGISwapChainDWMLegacy::PresentDWM 里边去执行。
否则就是 COverlayContext::Present  函数自己处理 Present 过程。

至此,我们大致了解了 dwm的hook过程,对于最新WIN11 24H2目前是没辙,除非使用的是老旧接口的显卡。
至于WIN7,WIN8的DWM HOOK办法目前有待研究,但是这两系统用的越来越少,不处理关系也不大。

但是总体来说,比如你想实现投影仪融合技术,能使用通用的IDD办法,还是尽量使用IDD。
否则像遇到目前WIN11 24H2破解不了的问题,以及以后有没有人破解得出,那也是一个未知的答案。
这种情况下,那是真的没有办法。
同时随着每次微软发布新的windows版本,都得及时重新破解调整,甚是麻烦。

因此本文也只是一个研究试验的用处,并不推荐用在成熟的解决方案中。
同时本牵涉到的一些破解技术,也并不推荐大家学习研究,因为破解本身就不属于正当行为。

http://www.dtcms.com/a/495448.html

相关文章:

  • Java 大视界 -- Java 大数据在智能电网电力市场交易数据分析与策略制定中的关键作用
  • 安徽和县住房城乡建设局网站wordpress移动端顶部导航栏
  • oracle:判断字段不以开头
  • 学习笔记3-深度学习之logistic回归向量化
  • 哈尔滨网站建设网站优秀个人网站设计欣赏
  • 高辐射环境下AS32S601ZIT2型MCU的抗辐照性能与应用潜力分析
  • 基于STM32F407与FT245R芯片实现USB转并口通信时序控制
  • Retrofit 与 OkHttp 全面解析与实战使用(含封装示例)
  • qiankun知识点
  • 面向接口编程与自上而下的系统设计艺术
  • 数据结构基石:单链表的全面实现、操作详解与顺序表对比
  • 网站 无限下拉做一个小程序需要多少钱
  • 【Kubernetes】常见面试题汇总(二十六)
  • 微网站设计制作wordpress在线文档
  • “自来水”用英语怎么说?
  • 小杰深度学习(fifteen)——视觉-经典神经网络——MobileNetV1
  • 网站建设方任务 职责如何建设网站兴田德润可信赖
  • EWM - TM 集成(ASR)
  • 数据库数据插入与查询
  • 图书馆网站建设报告海报设计的基本要素
  • Sightline Intelligence边缘可操作情报-专为关键任务决策而打造
  • 2016-2023年全国内陆水体营养状态数据
  • MongoDB 固定集合
  • 【Linux】多路转接
  • 可视化开发 + AI:软件开发的黄金组合
  • 哪个网站做质量认证书范本ps教程
  • 河北邢台有几个区县合肥seo网站优化培训
  • 2025年智能装备与机器人国际学术会议(IER 2025)
  • Fixed VLC snap on Ubuntu
  • 豆瓣 wordpress 插件做一个网站加优化排名得多少钱