首页
社区
课程
招聘
[原创] BitBlt「超级截屏」方案(DWM篇)
发表于: 2026-9-11 09:07 1408

[原创] BitBlt「超级截屏」方案(DWM篇)

2026-9-11 09:07
1408

窗口隐藏与反截屏系列 · 第 3 期

工具:IDA · CE · Hawkeye Lab · Cursor MCP

目录

  1. 背景与系列规划
  2. BitBlt 与 DWM 的联系
  3. 「显示」与「截屏」
  4. 分析与突破点
  5. 实现流程梳理
  6. Hawkeye Lab
  7. 开源实现

1. 背景与系列规划

本系列共三篇,本篇为第三期:

  • 第 1 期:SetWindowDisplayAffinity 原理与检测
  • 第 2 期: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 里对应汇编如下:
pic1进入 capture:mov r15d, 1 后写 [g_pComposition+196Ah]。

capture 结束后再写回 0(此处 r13b 为 0),对应汇编如下:
pic2离开 capture:写 [g_pComposition+196Ah] ← 0,随后 RestoreCursors。

整段一帧绘制被这对写 / 清操作包起来,这是最值得怀疑的突破点。 [记得上一期还表扬了微软]

进一步查证

对 196Ah 做 IDA 全局搜索,能追到 readback 相关代码;Win11 的一些版本的 PDB 里还直接带有 IsScreenReadBack()函数:
pic3CComposition::IsScreenReadBack 读取 [rcx+196Ah] 并返回,即检测 readback / 截屏语义标志。

名字里带 ReadBack,似乎就是在回答「当前是不是回写/截屏状态」。可惜 Win10 上往往没有这个符号;本文分析的机器是 Win11,算是运气比较好。

Lab 验证

静态分析到这里还缺实锤:在运行中的 dwm.exe 里,把下图红框处写 1 改成写 0,看普通 BitBlt 是否也能截到受保护窗口。

用 Hawkeye Lab 直接修改 dwm.exe,把 mov r15d, 1 的 immediate 01 改成 00:
pic4Hawkeye 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 介绍:212K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Z5j5i4N6C8k6i4W2W2i4K6u0V1L8r3g2G2i4K6u0W2k6$3W2@1K9s2g2T1i4K6u0W2K9h3!0Q4x3V1k6Z5j5i4N6C8k6i4W2W2i4K6u0r3L8r3q4T1i4K6u0r3

7. 开源实现

本期代码已经开源。

  • 源码仓库:6e7K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6Z5j5i4N6C8k6i4W2W2i4K6u0V1e0r3g2G2i4K6u0r3K9r3q4%4K9$3g2&6k6g2)9J5k6r3y4G2L8h3#2#2L8X3W2@1P5b7`.`.
  • 预编译包:aacK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6Z5j5i4N6C8k6i4W2W2i4K6u0V1e0r3g2G2i4K6u0r3K9r3q4%4K9$3g2&6k6g2)9J5k6r3y4G2L8h3#2#2L8X3W2@1P5g2)9J5c8Y4u0W2L8r3g2S2M7$3g2K6i4K6u0r3L8r3q4@1k6i4y4@1

[招生]科锐逆向工程师培训(2026年11月13日实地,远程教学同时开班, 第57期)

最后于 2026-9-11 09:14 被hawkeye_Leo编辑 ,原因: 语句更加流畅
收藏
点赞 ・2
打赏
分享
最新回复 (3)
雪    币: 61
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
2 楼
精彩
2026-10-8 14:07
0
雪    币: 3029
活跃值: (5935)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3 楼
好好好
3天前
0
雪    币: 256
活跃值: (944)
能力值: ( LV5,RANK:60 )
在线值:
发帖
回帖
粉丝
4 楼
看看DwmGetDxRgn这个API
3天前
0
游客
登录 | 注册 方可回帖
返回