Skip to content

Repository files navigation

wda-inject

真正单文件命令行工具:通过 shellcode 注入(无 DLL、无外部文件),对任意窗口设置 / 查询 「截图亲和力」(Window Display Affinity),控制该窗口在截图 / 录屏中的表现:

模式 取值 效果
none WDA_NONE (0x0) 不限制,截图正常
monitor WDA_MONITOR (0x1) 截图里该窗口区域变黑
exclude WDA_EXCLUDEFROMCAPTURE (0x11) 截图 / 录屏里该窗口整个消失(Win10 2004+)

窗口在屏幕上始终正常显示,只影响「被其它进程捕获」的结果。

用法

wda-inject list [--hidden] [--pid N] [--exe 名称] [--title 文本]
wda-inject set  <target> <none|monitor|exclude> [--all]
wda-inject get  <target> [--all]

target:

写法 含义
0x00A01234 窗口句柄(list 输出的第一列)
pid:4532 该进程的顶级窗口
exe:notepad.exe 按进程名(不区分大小写,子串匹配,exe:notepad 也能匹配到)
title:记事本 标题子串(不区分大小写)

示例:

wda-inject list --exe chrome              :: 列出 Chrome 的顶层窗口
wda-inject set  exe:notepad.exe exclude   :: 记事本从截图/录屏中消失
wda-inject set  title:记事本 monitor       :: 标题含「记事本」的窗口截图变黑
wda-inject set  0x00000000000A01234 none  :: 按句柄恢复
wda-inject get  pid:4532                  :: 查询该进程窗口当前的亲和力
wda-inject set  exe:chrome.exe exclude --all   :: 匹配到的所有窗口全部设置

退出码:0 全部成功;1 参数错误;2 没匹配到窗口;3 部分/全部失败。

注入原理(为什么单文件就够?)

微软官方文档对 SetWindowDisplayAffinity 的要求:

"A handle to the top-level window. The window must belong to the current process."

—— 窗口必须属于调用进程。所以必须在目标进程自己的上下文里调用该 API。

v2.0 的注入方式是 shellcode 注入,把原来的「注入 DLL」升级成「注入机器码」, 因此 EXE 里不需要打包任何 DLL:

  1. 解析地址:在注入器进程内 GetProcAddress 拿到 SetWindowDisplayAffinity / GetWindowDisplayAffinity / GetLastError 的地址。 Windows 的 ASLR 是系统级的——同一会话里 user32.dll / kernel32.dll 在所有进程中的加载基址一致,所以这些地址在目标进程里同样有效。
  2. 写入参数VirtualAllocEx 在目标进程分配参数/结果区(RW), WriteProcessMemory 写入 {函数地址, 窗口句柄, 亲和力, 结果槽指针, GetLastError地址, 错误码槽}
  3. 写入载荷:再分配一块 RWX 内存,写入内嵌在 EXE 里的 shellcode 字节 (x64: 60 字节;x86: 44 字节,见 src/shellcode_*.S,已汇编成 src/sc*.inc 内嵌)。
  4. 执行CreateRemoteThread 让目标进程运行 shellcode——由目标进程上下文调用 Set/GetWindowDisplayAffinity,把返回值、错误码写回结果区。
  5. 收尾:等待远程线程结束 → ReadProcessMemory 读结果 → 释放内存、关闭句柄。

相比 DLL 注入:没有 DLL 文件、没有临时文件落盘、不会被杀软隔离或留下文件; 「进程内调用 API」的语义完全相同。

关于 32 位目标:x64 注入器遇到 32 位(WOW64)进程时,需要 32 位上下文来解析 32 位地址并注入 32 位 shellcode。v2.3 起 32 位子程序已经内嵌在 wda-inject.exe 里: 运行时自动释放到 %TEMP%、执行、用完即删——你只需要一个 wda-inject.exe, 无论目标进程是 32 位还是 64 位都能处理。

get 默认走进程内 shellcode 查询(最准确);set/get 都可以加 --direct 强制直接跨进程调用(注入不可用时的兜底,官方文档不保证跨进程 set 成功)。

list 里的「亲和力」列是跨进程快速查询的结果,exclude 状态在跨进程查询时 可能显示为 monitor(隐私保护设计),以 get 的进程内查询为准。

构建

方式一:MSVC(推荐,Windows 上)

需要 VS2019/VS2022(含 C++ 桌面开发或 Build Tools)。在项目目录执行:

build.bat

shellcode 已预生成成 C 字节数组(src/sc64.incsrc/sc86.inc)随源码提交, MSVC 直接编译,不需要汇编器

方式二:MinGW / MSYS2

make           # 构建 wda-inject.exe(64) + wda-inject32.exe + testwin.exe
make testwin.exe

修改了 src/shellcode_*.S 后需要重新生成字节数组(需要 mingw 的 as):

./tools/gen_shellcode.sh

产物(全部为单文件,无 DLL)

wda-inject.exe        x64 主程序(平时就用它;已内嵌 32 位子程序,处理 32/64 位目标都行)
wda-inject64.exe      x64 主程序(同上)
wda-inject32.exe      x86 主程序(独立版,仅调试用;正常使用不需要)
testwin.exe           测试窗口工具(见下)

依赖仅系统自带的 KERNEL32 / USER32 / msvcrt,任何 Windows 10/11 直接可用。 建议在目标机器上用 MSVC 重新构建以排除交叉编译差异。

快速验证

testwin.exe 60              :: 弹出一个标题为 wda-test-window 的窗口,60 秒后退出
testwin.exe cjk             :: 弹出一个中文长标题窗口(验证中文控制台渲染/截断)
wda-inject get  title:wda-test-window
wda-inject set  title:wda-test-window exclude
:: 用 Win+Shift+S 截图:应该看不到这个窗口
wda-inject get  title:wda-test-window
wda-inject set  title:wda-test-window none   :: 恢复

限制与注意事项

  • exclude 需要 Windows 10 2004 (build 19041) 及以上;更老的系统上会自动按 monitor 处理(官方文档行为)。
  • 依赖 DWM 桌面合成(默认开启)。远程桌面 / 无 GPU 直通的虚拟机里可能不生效。
  • 目标进程权限高于当前进程(如管理员进程)时,注入会被拒绝 —— 以管理员身份运行 wda-inject
  • 受保护进程(部分系统进程、DRM 媒体进程等)无法打开/注入。
  • WDA_EXCLUDEFROMCAPTURE 只对顶级窗口生效(官方文档)。
  • 主流截图/录屏(Win+Shift+S、OBS、Xbox Game Bar、QQ/微信截图等)都会遵守 WDA; 个别绕过 DWM 的 GDI 抓屏老软件可能仍然能抓到。
  • 部分自带防截屏的应用(播放器、网银控件)本身已设置 WDA_MONITORset ... none 可解除(若权限允许)。
  • GetWindowDisplayAffinity 官方文档说明仅对「分层窗口 + DWM 合成」保证成功, 个别窗口在 list 里亲和力可能显示 ?,属正常。
  • 不要并发对同一个进程跑两个 set/get
  • 本工具不是安全/DRM 功能:WDA 只约束操作系统公开的捕获 API,拦不住拍照、直接读显存等。
  • 注入载荷是 shellcode(CreateRemoteThread 执行机器码),部分安全软件可能对此告警; 请仅用于你自己的系统与程序。

常见问题

Q: 为什么 set 报「拒绝访问」? 目标进程比 wda-inject 权限高(如管理员/系统进程)。右键「以管理员身份运行」命令行再试。

Q: 为什么 exclude 设了之后窗口还在截图里? 确认系统是 Win10 2004+;确认不是远程桌面会话;确认目标窗口是顶级窗口; 个别应用会周期性地把亲和力改回去,可配合 --all 循环设置。

Q: 32 位进程注入不了? v2.3 起的 wda-inject.exe 已内嵌 32 位子程序,无需任何额外文件即可处理 32 位目标。 若仍失败,运行 wda-inject set <target> <mode> --verbose 查看:应能看到 「已释放 32 位载荷到 …」的调试输出,随后子进程执行注入。

Q: 设置之后如何恢复? wda-inject set <target> none

Q: set 报「错误码 87 参数错误」? 先运行 wda-inject set <target> <mode> --verbose

  • 若显示 注入失败(阶段: OpenProcess) —— 请升级到 v2.2.0。v2.2 修复了一个关键 bug: 之前把 GetWindowThreadProcessId 的**返回值(线程 ID)**误当成进程 ID, 导致 OpenProcess 拿线程 ID 当 PID 打开而报 87。v2.2 起 list 显示的 PID 为真实进程 ID。
  • 若显示 目标进程内调用 ... 返回失败,则按下面排查:
    1. 传入了子窗口句柄(如从 Spy++ 拿到的控件句柄)——工具会自动检测并改用其 顶层窗口,并打印「已自动改用其顶层窗口」提示;也可先 wda-inject list --pid <pid> 查看该进程的顶级窗口。
    2. 系统版本太旧exclude 需要 Win10 2004 (build 19041)+。set exclude 时工具会 自动检查系统版本并告警;可先用 wda-inject set <target> monitor 试试—— 若 monitor 成功而 exclude 失败,基本就是系统版本问题。
    3. 个别特殊窗口(无边框/半透明/自定义框架,如部分 WinUI 应用)可能拒绝设置 参考

Q: list 输出中文乱码 / 列对不齐? 输出到控制台用 WriteConsoleW 原生宽字符输出,不依赖代码页(chcp 936/65001 都正常), 列宽按「显示宽度」计算(中文按 2 格),长标题自动截断。若仍显示方块,把 cmd 窗口字体 设置为「新宋体」等含中文字形的字体;输出重定向到文件时文件为 UTF-8 编码。

Q: list 里「进程」列是空的? v2.2.1 起已修复:此前进程名缓冲区只有 64 字符,而现代记事本等打包应用的路径 (C:\Program Files\WindowsApps\...)很长,会导致 QueryFullProcessImageNameW 缓冲区不足而失败。现在缓冲区扩到 MAX_PATH 并回退到 Toolhelp 快照取进程名, 即使 OpenProcess 因权限被拒也能显示。

项目结构

wda-inject/
├── README.md
├── build.bat               MSVC 一键构建(先 32 位后 64 位,自动生成内嵌数据)
├── Makefile                MinGW/MSYS2 构建
├── tools/
│   ├── gen_shellcode.sh    shellcode 汇编 -> C 字节数组
│   ├── gen_embed.py        wda-inject32.exe -> C 字节数组(Linux/CI)
│   ├── gen_embed.ps1       wda-inject32.exe -> C 字节数组(Windows/MSVC)
│   └── testwin.c           测试窗口工具
├── src/
│   ├── wda_inject.c        主程序(CLI + 注入器 + 32位载荷释放逻辑)
│   ├── wda_common.h        共享常量
│   ├── shellcode_x64.S     x64 注入载荷汇编源(60B)
│   ├── shellcode_x86.S     x86 注入载荷汇编源(44B)
│   ├── sc64.inc / sc86.inc shellcode 字节数组
│   └── wda32_exe.inc       32 位子程序的字节数组(构建时生成)
└── *.exe                   构建产物(单文件)

About

通过 shellcode 注入,对任意窗口设置/查询截图亲和力 (Window Display Affinity: none/monitor/exclude)。单文件、无 DLL、无临时文件。

Topics

Resources

Stars

3 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages