首页
课程
问答
CTF
社区
招聘
峰会
发现
排行榜
知识库
工具下载
看雪20年
看雪商城
证书查询
登录
注册
首页
社区
课程
招聘
发现
问答
CTF
排行榜
知识库
工具下载
峰会
看雪商城
证书查询
社区
编程技术
发新帖
2
2
[原创] BitBlt「超级截屏」方案(DWM篇)
发表于: 2026-9-11 09:07
402
[原创] BitBlt「超级截屏」方案(DWM篇)
hawkeye_Leo
2026-9-11 09:07
402
**窗口隐藏与反截屏系列 · 第 3 期** 工具:IDA · CE · Hawkeye Lab · Cursor MCP --- ## 目录 1. [背景与系列规划](#1-背景与系列规划) 2. [BitBlt 与 DWM 的联系](#2-bitblt-与-dwm-的联系) 3. [「显示」与「截屏」](#3-显示与截屏) 4. [分析与突破点](#4-分析与突破点) 5. [实现流程梳理](#5-实现流程梳理) 6. [Hawkeye Lab](#6-hawkeye-lab) 7. [开源实现](#7-开源实现) --- ## 1. 背景与系列规划 本系列共三篇,本篇为第三期: - [第 1 期](../01-display-affinity/):`SetWindowDisplayAffinity` 原理与检测 - [第 2 期](../02-visual-hiding/):Visual 层隐藏原理与检测 - **第 3 期(本文)**:基于 BitBlt 的「超级截屏」方案 前两期分别介绍了窗口级与 Visual 层的反截屏原理与检测;本篇离开检测视角,从 BitBlt 截屏路径入手,探讨一种可绕过上述保护的「超级截屏」方案。 --- ## 2. BitBlt 与 DWM 的联系 简单的流程: ``` gdi32!BitBlt └─ win32kfull!NtGdiBitBlt └─ DwmSyncCaptureSurfaceBits └─ LpcSendWaitReceivePort → dwm.exe └─ dwmcore!CaptureBitsResponse::RenderForCapture └─ 原路返回 ``` 和 DisplayAffinity 类似,BitBlt 的调用还是会先进内核,经过一系列的函数调用,不出意外地发送 LPC 消息给 `dwm.exe`。`dwm.exe` 接到消息后,最终落到了 `dwmcore!CaptureBitsResponse::RenderForCapture`。完事后原路返回。 即,BitBlt 的截屏结果不是看内核,而是看 DWM 的心情。更直白点,完全由 `dwmcore.dll` 来支配。 --- ## 3. 「显示」与「截屏」 经过分析,「显示」与「截屏」在 RenderTarget 上各走一路,在 Visual 树与合成代码上却是同一套:各有独立的 RenderTarget,却共享同一棵全局 Visual 树与几乎同一套合成逻辑。 正常显示路线: ``` CDDisplayRenderTarget::RenderAndPresent → BeginFrame(显示器 RT) → DrawVisualTree → Present ``` 截屏路线(`RenderForCapture` 里): ``` BeginFrame(captureRenderTarget) → DrawVisualTree → 从 capturebits scratch 读像素 → section 回内核 ``` --- ## 4. 分析与突破点 IDA 分析 `CaptureBitsResponse::RenderForCapture`,调用顺序大致如下: ``` CaptureBitsResponse::RenderForCapture ├─ CComposition::ShowHideCursors(0) (Hide 鼠标 Visual;CExcludeVisualReference::Hide) ├─ CDrawingContext::Create (堆上 AllocClear 创建;此时尚未绑定 RT) └─ 一帧绘制 ├─ CDrawingContext::BeginFrame (传入 capture IDeviceTarget,绑定 RT) ├─ CDrawingContext::DrawVisualTree ← 截屏 / 显示共用 └─ CDrawingContext::EndFrame (结束后 RestoreCursors,并清 g_pComposition+0x196A) ``` `DrawVisualTree` 最关键:它把全局 Visual 树里可以画的 Visual 画上去;截屏与显示两条路线都会经过它。 ### readback 标志:g_pComposition + 0x196A ``` 进入 capture: [g_pComposition+0x196A] ← 1 BeginFrame → DrawVisualTree → EndFrame 离开 capture: [g_pComposition+0x196A] ← 0 ``` 在 `RenderForCapture` 里,capture 开始前会把 `1` 写入 `g_pComposition + 0x196A`,IDA 里对应汇编如下: 进入 capture:`mov r15d, 1` 后写 `[g_pComposition+196Ah]`。 capture 结束后再写回 `0`(此处 `r13b` 为 0),对应汇编如下: 离开 capture:写 `[g_pComposition+196Ah] ← 0`,随后 `RestoreCursors`。 整段一帧绘制被这对写 / 清操作包起来,这是最值得怀疑的突破点。 [记得上一期还表扬了微软] ### 进一步查证 对 `196Ah` 做 IDA 全局搜索,能追到 readback 相关代码;Win11 的一些版本的 PDB 里还直接带有 `IsScreenReadBack()`函数: `CComposition::IsScreenReadBack` 读取 `[rcx+196Ah]` 并返回,即检测 readback / 截屏语义标志。 名字里带 ReadBack,似乎就是在回答「当前是不是回写/截屏状态」。可惜 Win10 上往往没有这个符号;本文分析的机器是 Win11,算是运气比较好。 ### Lab 验证 静态分析到这里还缺实锤:在运行中的 `dwm.exe` 里,把下图红框处写 `1` 改成写 `0`,看普通 BitBlt 是否也能截到受保护窗口。 用 Hawkeye Lab 直接修改 `dwm.exe`,把 `mov r15d, 1` 的 immediate `01` 改成 `00`: Hawkeye Lab 将 `mov r15d, 1` 改为 `mov r15d, 0`(immediate `01` → `00`)。 改完后再触发普通 BitBlt 截屏,所有被反截屏的窗口和 Visual 都可以截到。 ### 有趣的 bug 正常情况下,这个标志只在截屏的一帧里存活:`RenderForCapture` 进入绘制前把 `g_pComposition + 0x196A` 置为 `1`,帧结束后写回 `0`。 反过来又试了一下:不动截屏代码,直接在内存里把该字节改成 `1`,让 DWM 长期处于 readback 语义。桌面上的反截屏窗口会从屏幕上消失。不是变透明,而是显示路径在 `DrawVisualTree` 里同样会读这一标志,受保护内容被整体跳过。再触发一次截屏、让 capture 正常走完置位和清零,又恢复了正常的逻辑。 --- ## 5. 实现流程梳理 Lab 里改 immediate 的验证说明方向是对的;真正用起来,需要的是一套可重复、可自动化的流程。思路很直接:在 BitBlt 调用之前,远程改掉写 readback 标志的那条指令,让 capture 期间 `g_pComposition + 0x196A` 始终为 `0`;BitBlt 调用完毕后把代码改回去。全程不必注入 `dwm.exe`,也不必 Hook——只是对dwm.exe进程做极短窗口的代码 patch。 对第 1 期的 DisplayAffinity 与第 2 期的 Visual 隐藏都适用:它们在 capture 路径里正是被 readback 语义挡住的。按下面四步操作即可复现: 1. **定位 patch 点。** 在 `dwmcore.dll` 的 `RenderForCapture` 里找写入 `[g_pComposition+196Ah]` 的指令。本文 Win11 样例为 `mov [rax+196Ah], r15b`(图 1,此前由 `mov r15d, 1` 准备写入值)。不同版本指令编码可能略有差异,宜按模式扫描,不宜硬编码。 2. **改写指令。** 把「写 `1`」改成「写 `0`」。可直接改 store 为 `mov [rax+196Ah], 0`,或只改 `mov r15d, 1` 的 immediate(`01` → `00`)。 3. **正常截图。** 调用 BitBlt。 4. **恢复修改点。** 把第 2 步的 patch 原样写回。 --- ## 6. Hawkeye Lab (给自己打广告) 本系列三篇把对抗面交代清楚了:DisplayAffinity、Visual 隐藏,以及 BitBlt readback 绕过。对游戏反作弊工程师来说,真正吃功夫的通常不是某一个小技巧,而是整条响应链——分析行为、定位外挂代码、快速特征上线-—能不能又快又稳、一轮轮跑通。 **Hawkeye Lab** 面向反作弊这一场景:把零散排查收成工业化、流程化的固定动作,少了在互不打通的工具之间摸黑;PDF 分析报告直接输出,检测结果与研判维度一次给齐,方便全方位参考和后续跟进。 - Hawkeye Lab 介绍:<mark class="encrypted">8d4K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Z5j5i4N6C8k6i4W2W2i4K6u0V1L8r3g2G2i4K6u0W2k6$3W2@1K9s2g2T1i4K6u0W2K9h3!0Q4x3V1k6Z5j5i4N6C8k6i4W2W2i4K6u0r3L8r3q4T1i4K6u0r3</mark> --- ## 7. 开源实现 本期代码已经开源。 - 源码仓库:<mark class="encrypted">833K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6Z5j5i4N6C8k6i4W2W2i4K6u0V1e0r3g2G2i4K6u0r3K9r3q4%4K9$3g2&6k6g2)9J5k6r3y4G2L8h3#2#2L8X3W2@1P5b7`.`.</mark> - 预编译包:<mark class="encrypted">af4K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6Z5j5i4N6C8k6i4W2W2i4K6u0V1e0r3g2G2i4K6u0r3K9r3q4%4K9$3g2&6k6g2)9J5k6r3y4G2L8h3#2#2L8X3W2@1P5g2)9J5c8Y4u0W2L8r3g2S2M7$3g2K6i4K6u0r3L8r3q4@1k6i4y4@1</mark>
传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!!
最后于
2026-9-11 09:14 被hawkeye_Leo编辑 ,原因: 语句更加流畅
#系统内核
#开源分享
收藏
・
2
点赞
・
2
打赏
分享
分享到微信
分享到QQ
分享到微博
赞赏记录
参与人
雪币
留言
时间
mb_jrenfgzj
为你点赞!
3天前
我的小拇指啊
感谢你的贡献,论坛因你而更加精彩!
6天前
查看更多
赞赏
×
1 雪花
5 雪花
10 雪花
20 雪花
50 雪花
80 雪花
100 雪花
150 雪花
200 雪花
支付方式:
微信支付
赞赏留言:
快捷留言
感谢分享~
精品文章~
原创内容~
精彩转帖~
助人为乐~
感谢分享~
最新回复
(
0
)
游客
登录
|
注册
方可回帖
回帖
表情
雪币赚取及消费
高级回复
返回
hawkeye_Leo
3
发帖
0
回帖
10
RANK
关注
私信
他的文章
[原创] BitBlt「超级截屏」方案(DWM篇)
402
[原创]Visual 层反截屏原理与 DWM 侧检测
509
[原创]SetWindowDisplayAffinity:原理与检测(DWM篇)
346
关于我们
联系我们
企业服务
看雪公众号
专注于PC、移动、智能设备安全研究及逆向工程的开发者社区
看原图
赞赏
×
雪币:
+
留言:
快捷留言
为你点赞!
返回
顶部