3环反截图demo 测试环境:windows 10 18362
SetWindowDisplayAffinity SetWindowDisplayAffinity 是 Windows API 中的一个函数,用于设置窗口的显示亲和性(display affinity)。这意味着你可以指定一个窗口的内容是否仅限于在主显示器上显示、还是可以跨多个显示器显示等。这个功能对于开发某些类型的安全应用特别有用,比如防止屏幕截图或视频录制敏感信息。
函数原型 BOOL SetWindowDisplayAffinity(
HWND hWnd,
DWORD dwAffinity
);
#define WDA_NONE 0x00000000
#define WDA_MONITOR 0x00000001
#define WDA_EXCLUDEFROMCAPTURE 0x00000011
hWnd: 指向要设置显示亲和性的窗口句柄。 dwAffinity: 指定窗口的显示亲和性。可以是以下值之一: WDA_NONE (0x00000000): 窗口没有显示亲和性限制,可以在任何显示器上显示。 WDA_MONITOR (0x00000001): 窗口内容只能在物理监视器上显示;如果尝试通过远程桌面会话访问该窗口,则内容将被隐藏。
返回值 - 如果函数成功执行,返回非零值。
- 如果发生错误,返回零。
下面是一个简单的 C++ 示例,演示如何使用 `SetWindowDisplayAffinity` 来设置窗口不可见:
#include <windows.h>
#include <iostream>
// 窗口过程函数
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
switch (uMsg) {
case WM_DESTROY:
PostQuitMessage(0);
return 0;
default:
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
}
// 创建 GUI 窗口
HWND CreateMyWindow() {
WNDCLASSEX wc = { sizeof(WNDCLASSEX) };
wc.lpfnWndProc = WindowProc;
wc.hInstance = GetModuleHandle(NULL);
wc.lpszClassName = L"MyWindowClass";
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
if (!RegisterClassEx(&wc)) {
std::cerr << "注册窗口类失败!错误代码: " << GetLastError() << std::endl;
return NULL;
}
HWND hWnd = CreateWindowEx(
0,
L"MyWindowClass",
L"HideWindow",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT,
400, 300,
NULL, NULL, GetModuleHandle(NULL), NULL
);
if (!hWnd) {
std::cerr << "创建窗口失败!错误代码: " << GetLastError() << std::endl;
}
return hWnd;
}
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
// 1. 创建窗口
HWND hMyWindow = CreateMyWindow();
if (!hMyWindow) {
return 1;
}
// 2. 显示窗口(不隐藏)
ShowWindow(hMyWindow, SW_SHOW);
UpdateWindow(hMyWindow);
// 3. 设置防截图
if (SetWindowDisplayAffinity(hMyWindow, WDA_EXCLUDEFROMCAPTURE)) {
std::cout << "防截图设置成功!" << std::endl;
}
else {
std::cout << "防截图设置失败!错误代码: " << GetLastError() << std::endl;
}
// 4. 消息循环
MSG msg;
while (GetMessage(&msg, NULL, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return (int)msg.wParam;
}
在这个例子中,我们创建了一个简单的静态文本窗口,并设置了它的显示亲和性为 WDA_EXCLUDEFROMCAPTURE,这样就确保了当有人试图通过远程桌面查看这台计算机时,这个窗口的内容不会被显示出来。
0环反截图: SetWindowDisplayAffinity 的完整调用链路分析 1. 用户模式入口 从用户模式调用 SetWindowDisplayAffinity 开始:
BOOL SetWindowDisplayAffinity(HWND hWnd, DWORD dwAffinity); hWnd:目标窗口句柄。dwAffinity:亲和性标志(如 WDA_EXCLUDEFROMCAPTURE)。2. 用户模式到内核的跳转 user32.dll -> win32u.dll 中的实现 :
SetWindowDisplayAffinity 调用 NtUserSetWindowDisplayAffinity(通过 syscall 进入内核)。.text:000000018000A790 ; __int64 NtUserSetWindowDisplayAffinity()
.text:000000018000A790 public NtUserSetWindowDisplayAffinity
.text:000000018000A790 NtUserSetWindowDisplayAffinity proc near
.text:000000018000A790 ; DATA XREF: .rdata:000000018000C8C4↓o
.text:000000018000A790 ; .rdata:off_18000DF28↓o ...
.text:000000018000A790 mov r10, rcx
.text:000000018000A793 mov eax, 14BBh
.text:000000018000A798 test byte ptr ds:7FFE0308h, 1
.text:000000018000A7A0 jnz short loc_18000A7A5
.text:000000018000A7A2 syscall ; Low latency system call
.text:000000018000A7A4 retn
.text:000000018000A7A5 ; ---------------------------------------------------------------------------
.text:000000018000A7A5
.text:000000018000A7A5 loc_18000A7A5: ; CODE XREF: NtUserSetWindowDisplayAffinity+10↑j
.text:000000018000A7A5 int 2Eh ; DOS 2+ internal - EXECUTE COMMAND
.text:000000018000A7A5 ; DS:SI -> counted CR-terminated command string
.text:000000018000A7A7 retn
.text:000000018000A7A7 NtUserSetWindowDisplayAffinity endp mov eax, 14BBh ; 系统调用号 = 0x14BB (5307)
test byte ptr ds:7FFE0308h, 1
jnz short loc_18000A7A5 ; 若为1,走 int 2Eh 路径
syscall ; 否则走 syscall 路径
retn 系统调用分发 :
内核根据调用号 0x14BB 路由到 win32kfull.sys 中的 NtUserSetWindowDisplayAffinity。 3. 内核模式处理(win32kfull.sys) (1) NtUserSetWindowDisplayAffinity检查 hWnd 是否有效(通过 ValidateHwnd)。 验证 dwAffinity 标志合法性(如 WDA_MONITOR 或 WDA_EXCLUDEFROMCAPTURE)。 NtUserSetWindowDisplayAffinity
└─ SetDisplayAffinity
└─ ChangeWindowTreeProtection
└─ ProtectWindowBitmap
└─ GreProtectSpriteContent // 实际执行保护 (2)关键函数解析 函数名
作用
SetDisplayAffinity
设置窗口的显示亲和性标志,并触发更新。
ChangeWindowTreeProtection
递归更新窗口树中所有子窗口的保护状态。
ProtectWindowBitmap
管理窗口位图的保护属性(如禁止截图)。
GreProtectSpriteContent
底层实现 :修改窗口(sprite)的渲染属性(如透明度、可捕获性)。
4. 调试 注意:需要附加GUI进程
内核-用户模式桥梁 : csrss.exe (Client/Server Runtime Subsystem)是 Windows 的核心进程,负责管理 Win32 子系统(如窗口、图形)。 权限要求 : win32kfull.sys 的某些函数(如 GreProtectSpriteContent )必须在 GUI 进程上下文中执行。 !process 0 0 csrss.exe
.process /i /p ffffae8f`3d4d4080 # 替换为实际 EPROCESS 地址
g # 继续执行,等待中断
x win32kfull!NtUserSetWindowDisplayAffinity #获取 在 WinDbg 中设置硬件断点:进行追踪输出+修改
# 断点触发时打印调用栈
ba e1 win32kfull!GreProtectSpriteContent "k; g"
# 跟踪底层实现
ba e1 win32kfull!GreProtectSpriteContent ".printf \"rcx=0x%p rdx=0x%x r8=0x%x r9=0x%x\\n\", @rcx, @rdx, @r8, @r9; g"
#参数修改验证
ba e1 win32kfull!GreProtectSpriteContent "r r8 = 0x1; r r9 = 0x1; .printf \"rcx=0x%p rdx=0x%x r8=0x%x r9=0x%x\\n\", @rcx, @rdx, @r8, @r9; g"
#r9为:dwAffinity
驱动内核代码Demo: /******************************************************************************
* @Function : GetGreProtectSpriteContentAddress
* @Description : 获取GreProtectSpriteContent函数地址
* @Return : NTSTATUS 执行状态
******************************************************************************/
NTSTATUS GetGreProtectSpriteContentAddress()
{
PUCHAR targetFunction = NULL;
LONG relativeOffset = 0;
PULONG64 functionPointerAddress = NULL;
PEPROCESS guiProcess = NULL;
KAPC_STATE apcState = { 0 };
NTSTATUS status = STATUS_UNSUCCESSFUL;
do
{
// 查找GUI进程
guiProcess = FindGuiProcess();
if (guiProcess == NULL)
{
LOG_INFO("[NoScreen] Failed to find GUI process (csrss.exe or winlogon.exe)");
status = STATUS_NOT_FOUND;
break;
}
// 附加到GUI进程
status = AttachToGuiProcess(guiProcess, &apcState);
if (!NT_SUCCESS(status))
{
LOG_INFO("[NoScreen] Failed to attach to GUI process: 0x%08X", status);
break;
}
// 在GUI进程上下文中搜索目标函数地址
targetFunction = SearchCode("win32kfull.sys", ".text", "E8****8BF885C075*85F674", 0);
if (targetFunction == NULL)
{
LOG_INFO("[NoScreen] Failed to find target function in win32kfull.sys");
status = STATUS_NOT_FOUND;
break;
}
/*
反汇编分析:
.text:00000001C02486B4 E8 BF 8D 02 00 call GreProtectSpriteContent
.text:00000001C02486B9 8B F8 mov edi, eax
.text:00000001C02486BB 85 C0 test eax, eax
.text:00000001C02486BD 75 0E jnz short loc_1C02486CD
.text:00000001C02486BF 85 F6 test esi, esi
.text:00000001C02486C1 74 0A jz short loc_1C02486CD
*/
// 计算函数指针地址
relativeOffset = *(PLONG)(targetFunction + 1); // 读取相对偏移
functionPointerAddress = (PULONG64)((targetFunction + 5) + relativeOffset); // 计算目标地址
/*
地址计算:
targetFunction = 0x00000001C02486B4
relativeOffset = 0x00028DBF
functionPointerAddress = (0x00000001C02486B4 + 5) + 0x00028DBF = 0x00000001C0271478
所以 0x00000001C0271478 就是 GreProtectSpriteContent 函数的真实地址!
*/
// 验证地址有效性
if (!MmIsAddressValid(functionPointerAddress))
{
LOG_INFO("[NoScreen] Invalid function pointer address: 0x%p", functionPointerAddress);
status = STATUS_INVALID_ADDRESS;
break;
}
// 设置全局函数指针
g_GreProtectSpriteContent = functionPointerAddress;
LOG_INFO("[NoScreen] GreProtectSpriteContent address found: 0x%p", functionPointerAddress);
LOG_INFO("[NoScreen] Target function location: 0x%p", targetFunction);
LOG_INFO("[NoScreen] Relative offset: 0x%08X", relativeOffset);
status = STATUS_SUCCESS;
} while (FALSE);
// 清理资源
if (guiProcess != NULL)
{
// 如果已经附加,先分离
if (NT_SUCCESS(status) || status == STATUS_NOT_FOUND || status == STATUS_INVALID_ADDRESS)
{
DetachFromGuiProcess(&apcState);
}
// 释放进程对象引用
ObfDereferenceObject(guiProcess);
}
return status;
}
/******************************************************************************
* @Function : GreProtectSpriteContent
* @Description : 调用GreProtectSpriteContent函数保护窗口
* @Param : pWnd [in] 窗口对象指针
* @Param : protectFlag [in] 保护标志
* @Return : NTSTATUS 执行状态
******************************************************************************/
NTSTATUS GreProtectSpriteContent(ULONG64 pWnd, ULONG64 protectFlag)
{
if (!pWnd || !g_GreProtectSpriteContent)
{
LOG_INFO("[NoScreen] Invalid parameters - pWnd: 0x%llx, g_GreProtectSpriteContent: 0x%p",
pWnd, g_GreProtectSpriteContent);
return STATUS_INVALID_PARAMETER;
}
PEPROCESS guiProcess = NULL;
KAPC_STATE apcState = { 0 };
NTSTATUS status = STATUS_UNSUCCESSFUL;
__int64 result = 0;
do
{
// 设置组合标志
int isComposed = 1;
LOG_INFO("[NoScreen] Calling GreProtectSpriteContent");
LOG_INFO("[NoScreen] - Window: 0x%llx", pWnd);
LOG_INFO("[NoScreen] - ProtectFlag: 0x%llx (%s)", protectFlag,
(protectFlag == WDA_EXCLUDEFROMCAPTURE) ? "ENABLE" : "DISABLE");
// 调用系统函数 - 这会在系统级别设置窗口保护
result = g_GreProtectSpriteContent(0, pWnd, isComposed, protectFlag);
LOG_INFO("[NoScreen] GreProtectSpriteContent returned: 0x%llX", result);
// 根据返回值判断成功与否
if (result == 1)
{
LOG_INFO("[NoScreen] Window protection applied successfully");
status = STATUS_SUCCESS;
}
else
{
LOG_INFO("[NoScreen] Window protection failed - function returned %d", status);
status = STATUS_UNSUCCESSFUL;
}
} while (FALSE);
return status;
}
传播安全知识、拓宽行业人脉——看雪讲师团队等你加入!
最后于 2025-9-15 00:33
被Onlyxiu编辑
,原因: