English | 中文
本目录是调研期写的验证代码,用于确认 Windows 调试器 API(DEBUG_PROCESS) 能否在非管理员权限下捕获完整进程树。
| 机制 | 需要管理员 | 能看到子进程 | 备注 |
|---|---|---|---|
CreateProcessW + DEBUG_PROCESS |
否 | 是(整棵树) | 商业静态分析工具在 Windows 上用的就是这条路 |
DebugActiveProcess |
否 | 否(只跟被 attach 的那一个) | 等价于 DEBUG_ONLY_THIS_PROCESS |
CreateProcessW + DEBUG_ONLY_THIS_PROCESS |
否 | 否 | 只跟直接子进程 |
| Detours 注入 hook | 视目标而定 | 是 | 需注入,有竞态,会被 EDR 标记 |
| ETW Kernel-Process | 是(创建内核 session) | 是 | 内核层,但本项目拿不到权限 |
| 编译器 wrapper(拦截调用) | 否 | 是(只对编译) | 见 docs/zh/RESEARCH-COMMAND-CAPTURE-2026-10-27.md 的对比 |
关键:用 DEBUG_PROCESS 启动目标进程后,不需要任何权限提升,就能收到整棵进程树的 CREATE_PROCESS_DEBUG_EVENT。
DbgProbe5.cs—DEBUG_PROCESS整树捕获(主原型,StartupObject)DbgProbe4.cs—DebugActiveProcess对照组(只抓到一个进程)DbgProbe.csproj— 构建配置(同时包含上面两个文件,入口点为DbgProbe5)
cd prototypes\windows-debugproc
dotnet build DbgProbe.csproj -c Release
.\bin\Release\net9.0-windows\DbgProbe.exe想跑对照组,把入口点改成 DbgProbe4(或在 csproj 里改 <StartupObject>)。
[create] ok=True err=0 root=19852
[CREATE] pid=19852 <- cmd.exe (根)
[CREATE] pid=5760 <- cmd.exe (孙进程)
[CREATE] pid=13296 <- whoami.exe (曾孙进程)
[EXIT] pid=13296
[EXIT] pid=19852
[total] create-events=3 rootExitCode=0x00000000
[marker file exists] = True
3 个 CREATE_PROCESS 事件,覆盖了根 → 子 → 孙三层,且 marker 文件证明被调试进程真的执行了(不是被挂起后饿死)。
CreateProcessW必须传ref STARTUPINFO(cb = Marshal.SizeOf(typeof(STARTUPINFO))),传IntPtr.Zero会直接失败返回ok=False err=0。ContinueDebugEvent的 status 用DBG_CONTINUE(0x00010002),不要用0——用0时被调试进程在第一次异常(event 1)后不会继续跑,表现为"只收到 1 个 CREATE 事件"然后卡死。- 事件类型:
1= EXCEPTION_DEBUG_EVENT2= CREATE_THREAD_DEBUG_EVENT3= CREATE_PROCESS_DEBUG_EVENT ← 我们要的4= EXIT_THREAD_DEBUG_EVENT5= EXIT_PROCESS_DEBUG_EVENT6= LOAD_DLL_DEBUG_EVENT(噪音很多,务必过滤)
- 进程映像名在
CREATE_PROCESS_DEBUG_INFO的偏移40(lpImageName),要用被调试进程自己的句柄(偏移8的hProcess)去ReadProcessMemory,用伪句柄读不到。原型里这一项显示为
<none>,是因为lpImageName在CREATE_PROCESS_DEBUG_EVENT时刻可能还没填好;生产实现应改从子进程 PEB 读ProcessParameters->CommandLine,那才是完整命令行。 - 事件循环要有"静默退出"策略:不能一收到 EXIT 就退出,要用 quiet-tick 计数,否则会漏掉还在跑的兄弟进程。
LOAD_DLL(6) 噪音极大 —— 实测一次启动 20+ 个,生产实现必须按事件类型过滤,否则吞吐会塌。
- 被调试进程知道自己被调试(PEB.BeingDebugged = 1)。少数工具会因此改变行为(有些反作弊、部分 JIT)。本项目的缓解手段是
ccap run --clear-being-debugged(默认关闭)。商业静态分析工具对这类场景提供的是另一条捕获路径(编译器 wrapper 拦截,而不是调试器),细节见调研报告。 - 调试器退出 = 被调试进程被杀。需要做好异常处理,避免目标进程被连带终止。
- 性能:每个事件有内核往返开销。
LOAD_DLL事件非常密集(上面实测一次启动就 20+ 个),必须过滤,否则吞吐会塌。 - 异常处理策略:默认用
DBG_CONTINUE会吞掉 first-chance 异常,某些依赖异常语义的工具可能异常。更保险的是区分DBG_EXCEPTION_NOT_HANDLED(未处理异常,让程序自己处理)和DBG_CONTINUE。 - WOW64:x86 目标在 x64 调试器下,寄存器上下文需要
Wow64GetThreadContext。命令行捕获不受影响(命令行在 PEB 里,直接读)。