首页
社区
课程
招聘
[原创]SetWindowDisplayAffinity 反反截图&反截图思路
发表于: 2025-9-15 00:23 2443

[原创]SetWindowDisplayAffinity 反反截图&反截图思路

2025-9-15 00:23
2443

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


  1. hWnd: 指向要设置显示亲和性的窗口句柄。
  2. dwAffinity: 指定窗口的显示亲和性。可以是以下值之一:
    1. WDA_NONE (0x00000000): 窗口没有显示亲和性限制,可以在任何显示器上显示。
    2. 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_MONITORWDA_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编辑 ,原因:
收藏
免费 2
支持
分享
最新回复 (4)
雪    币: 1087
活跃值: (1970)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
所以怎么反 反截图
2025-9-15 10:06
0
雪    币: 1541
活跃值: (4383)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
可以可以
2025-9-18 15:11
0
雪    币: 4781
活跃值: (7645)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
4
2025-9-21 02:00
0
雪    币: 0
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
NVH
5
mb_hzfcelhs 所以怎么反 反截图
dwm可以截图这种
2025-9-25 19:22
1
游客
登录 | 注册 方可回帖
返回