文章总结: 本文介绍了一种通过DLL注入技术实现反截屏的方法,通过编写反截屏DLL和注入程序,可以在不修改源代码的情况下使现有程序的界面对截屏软件不可见。文章详细讲解了DLL的编写、远程线程注入技术的实现步骤,并演示了将反截屏DLL注入到Windows任务管理器中的效果。这种方法可以保护大部分应用程序不被截屏,但对虚拟机和远程桌面截屏无效。
综合评分: 91
文章分类: 二进制安全,代码审计,安全工具,应用安全,内网渗透
通过 DLL 注入实现对指定窗口反截屏
原创
MyStackTrace
MyStackTrace
2025年11月15日 19:53
在截屏与反截屏那篇文章中我们写了一个简单的反截屏的例子程序,在那个代码中我们首先创建了一个窗口,然后使用Windows API 函数 SetWindowDisplayAffinity 把这个窗口的 Display Affinity 设置为 WDA_EXCLUDEFROMCAPTURE,这就会让这个窗口可以不被大部分截屏或者录屏软件捕获,从而达到反截屏的效果。
后来有朋友看到这篇文章就跟我说文章中的例子只是把程序自己创建的窗口设置成具有反截屏的效果,这其实是有局限性的,如果用户想把一个现成的软件的图形界面设置成无法截屏,或者是想把一个已经跑起来的程序的图形界面设置为无法截屏,这种方式就不适用了。这话确实在理,如果我们想让现有的程序也具有反截屏的能力,就需要让现有的程序调用 SetWindowDisplayAffinity 函数设置自己窗口的 Display Affinity,怎么才能在不修改现有程序的源代码(我们也未必能拿到源代码)甚至是不重启程序的情况下,让程序做到这一点呢?一个词儿闪现在我的脑子里: DLL 注入。
DLL 注入 (DLL Injection) 是一种强大的 Windows 系统编程技术,它强制一个正在运行的目标程序(进程)加载并执行一个它原本并不需要的 DLL 文件。打个比方,我们可以把 DLL 注入这种技术想象成你(攻击者/外部程序)设法让一个陌生人(目标程序)吃下了一颗“特制药丸”(你的 DLL),这颗药丸进入他体内后,就会开始按照你的意愿去影响他的言行举止(改变程序行为)。
现在让我们再回到上面提到的那个反截屏例子程序的局限性上,要想解决这个问题,我们可以先写一个 DLL,这个 DLL 的 DllMain 函数的作用就是找到当前进程的窗口句柄,并在这个句柄上调用函数 SetWindowDisplayAffinity 将这个窗口的 Display Affinity 设置为 WDA_EXCLUDEFROMCAPTURE,那么这个程序的图形界面就可以对截屏程序不可见,然后我们再写一个 DLL 注入的程序,把这个 DLL 注入到目标程序所在的进程中,当目标程序加载这个 DLL 的时候,就会执行它的 DllMain 函数,那么该程序图形界面就会被设置为不可截屏。
不说废话了,我们开始动手,第一步先写一个能够把进程主界面设置为不可截屏的 DLL,代码如下。在 DllMain 函数中调用 Windows API 函数 EnumWindows 枚举当前系统中的窗口,这个 API 函数会把每个窗口的句柄传给它的第一个参数,一个 EnumWindowsProc 类型的回调函数,我们可以在这个回调函数中判断这个窗口是否属于当前进程,如果是接着判断这个窗口是否可见并且是否是主窗口,如果这些条件都满足的话,就将其设置为不可截屏,然后这个程序的主界面就无法被截屏了。
有了这个反截屏的 DLL 之后,我们再写一个可以做 DLL 注入的程序。可以做 DLL 注入的技术有很多,比如:APC 注入,远程线程注入,手动注入等。下面我们就采用最常用的远程线程注入技术写一个程序,在这个程序中我们需要使用一个重要的 Windows API 函数:CreateRemoteThread,该函数可以在指定进程中创建一个线程,我们可以利用这个线程在目标进程中做我们想要做的事,比如这里就是加载这个反截屏的 DLL。这里需要注意的一点是,在另一个进程中创建线程和在当前进程中创建线程有一个重要区别就是我们传递的线程执行函数的地址和线程执行函数的参数的地址都需要是一个远程的地址,也就是目标进程中的地址,原因很简单,目标进程只认识自己进程虚拟地址空间中的地址,你传当前进程中的地址在目标进程中是非法的,会导致目标进程直接崩溃掉。
下面我们来具体解释一下这个 DLL 注入程序的代码:
1、调用 Windows API 函数 OpenProcess 根据指定的 pid 打开目标进程,拿到它的句柄,我们后面针对目标进程的所有操作都会在这个句柄上进行。
2、调用 Windows API 函数 VirtualAllocEx 在目标进程中分配一块内存用来存放要注入的 DLL 文件路径,并且调用 Windows API 函数 WriteProcessMemory 向这个远程内存写入要注入 DLL 的文件路径。
3、调用 Windows API 函数 GetModuleHandle 获取 Kernel32.dll 的 MODULE 句柄,这是 Windows 系统中的一个很重要的 DLL,用于加载 DLL 的函数 LoadLibrary 就在其中,所以接下来我们使用 Windows API 函数 GetProcAddress 从 Kernel32.dll 中获取 LoadLibrary 函数的地址,这个函数地址将成为我们要创建的远程线程的执行函数。有同学可能质疑了,前面不是说远程线程的执行函数和参数都必须使用目标进程中的地址吗?但是这里获取的 LoadLibrary 函数的地址为什么是当前进程的呢?没错,你质疑的很对,不过在大部分情况下,系统 DLL (比如这里的 Kernel32.dll)在所有进程中的加载地址是固定的,因此它里面的函数的地址在所有进程中通常都是相等的,所以我们这里直接获取当前进程中的 LoadLibrary 函数地址给远程线程使用是没问题的。
4、调用 Windows API 函数 CreateRemoteThread 在目标进程中创建线程,线程的执行函数为 LoadLibrary,参数是要注入 DLL 的文件路径,只不过这个参数是放在前面分配的远程内存中的。需要注意的一点是这里之所以直接选择 LoadLibrary 函数作为线程执行函数主要是因为它和 CreateRemoteThread 要求的线程执行函数的函数原型比较接近,是可以兼容的。
5、成功创建远程线程之后,该线程就会在目标进程中注入我们指定的 DLL,然后我们就调用 Windows API 函数 WaitForSingleObject 等待远程线程执行结束,最后做一些清理,这个程序就结束了。
代码就介绍到这里,下面我们看下效果。下面的视频是我使用 PixPin 截图工具的录屏功能录下来的,我们使用刚才写的 DLL 注入程序把那个反截屏的 DLL 注入到 Windows 的任务管理器中,让任务管理器的界面从录屏中消失。从这个录下来的视频中可以看到,一开始是可以录到任务管理器的界面的,当执行完 DLL 注入程序之后,任务管理器的界面立刻从录到的画面中消失了,这说明反截屏 DLL 注入成功,并且成功的将任务管理器程序的主界面窗口的 Display Affinity 设置为 WDA_EXCLUDEFROMCAPTURE,让这个窗口对截屏程序不可见,而且这个效果是即时生效的。
执行完 DLL 注入程序之后,我们可以通过 ProcessExplorer 程序查看任务管理器进程中加载的 DLL,可以发现是有我们刚刚注入的 SetAntiScreenShot.dll。
下面这个视频是从宿主机视角(在宿主机上运行录屏软件录取虚拟机中的画面)进行录屏的,我们在一个 Windows 虚拟机中运行这个 DLL 注入程序对任务管理器界面进行反截屏保护,然后在这个虚拟机中使用 PixPin 截屏程序进行截屏,可以看到只要我们点开了 PixPin 的截屏模式,任务管理器的界面就会消失,最后我们还使用了 Windows 自带的截屏工具 (Snipping Tool,通过 Shift + Win + S 组合键呼出) 进行截屏,在最后截取到的图片上也是没有任务管理器的界面的。由于是在宿主机上录的屏,因此当虚拟机中没有运行截屏程序(截屏模式)的时候,我们还是可以看得到任务管理器的界面的,这是与上面那个视频的区别。
使用这种方法可以让大部分应用程序的主界面对截屏软件不可见,但是对于在宿主机上截取虚拟机的画面或者是通过远程桌面截屏的方式是不管用的。
Have Fun 😀
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:MyStackTrace MyStackTrace《通过 DLL 注入实现对指定窗口反截屏》