完整调用链(比我之前只知道 NtUserSetWindowDisplayAffinity 细一层):
R3: SetWindowDisplayAffinity(hwnd, WDA_*)
↓ syscall 0x14BB(win32u 里:mov eax,14BBh; syscall)
R0: win32kfull!NtUserSetWindowDisplayAffinity
└─ SetDisplayAffinity 设标志 + 触发更新
└─ ChangeWindowTreeProtection ★ 递归更新窗口树里所有子窗口
└─ ProtectWindowBitmap 管理位图保护属性
└─ GreProtectSpriteContent ★★ 真正执行保护的那一层
★ 最关键的一句:GreProtectSpriteContent 的 r9 参数就是 dwAffinity。
也就是说 ------ "反截屏"在内核里就是一个寄存器参数。整条链的开关就这一个值。
「0环反截图」怎么做(原文 Demo,工具名 NoScreen)
- 找到 GUI 进程(csrss.exe / winlogon.exe)------ 因为 GreProtectSpriteContent 必须在 GUI 进程上下文里执行(win32kfull
在会话空间,驱动不 attach 进去就够不着)
-
附加进去,然后特征码搜索在 .text 段定位函数
-
hook 它,改 r9
调试手法(原文):双机 WinDbg 附加 csrss,在 GreProtectSpriteContent 上下硬件断点打印 r8/r9,再直接 r r9 = 0x1 验证。
能力(双向,这是它的价值)
-
防守:强制所有窗口进入保护 → 谁都截不到
-
进攻:强制清零 → 解除别人设的保护
-
比用户态强在:用户态 SetWindowDisplayAffinity 只能改自己进程的窗口;内核这层能改任意进程的窗口
边界(三条,和前面对得上)
-
仍然挡不住「进程内截」 ------ 别人 hook 你进程的 Present,根本不经过 win32k / DWM 这条链
-
hook 本身会被发现 ------ 内核代码段的 inline hook,正是 PTE 扫描 / 代码段 CRC 的目标
-
特征码随 Windows 版本变(要一直跟版本维护)+ 需要签名驱动
★ 把它和你上一条说的「显卡接口截图」拼起来看(这是最有价值的整合)
从"更深"绕过 ↑ NvFBC / 显卡驱动读 framebuffer
内核读 primary surface │ ← 什么都挡不住(层次更低)
hook dwm scanout │
────────── WDA 保护线 ────────── │ ← GreProtectSpriteContent / r9 在这条线上
DXGI 桌面复制 / WGC / BitBlt │ ← 被 WDA 挡在这
│
平行路径(不经过这条链): │
进程内 hook Present ────────────┘ ← WDA 管不着
结论:GreProtectSpriteContent 控的是"DWM 合成时排除",不是"framebuffer 里没有" ------
屏幕上还得显示,所以显卡接口读 framebuffer 天然在它之下,绕过它。
0环反截屏能管住的:所有走 WDA 那条链的捕获方式。
管不住的:进程内截(平行路径)+ 显卡接口(更下层)。
这也再次印证了那条总结:反截屏没有银弹,因为入口不在同一条链上 ------ 你控住一个开关,别人换条路就行。