调试器在启动调试器时如何绕过图像文件执行选项?

Rei*_*kin 4 debugging windows-nt createprocess

我正在 Windows 内部进行一些探索以进行一般性教育,并且我正在尝试了解 Image File Execution Options 背后的机制。具体来说,我为 calc.exe 设置了一个调试器条目,"C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" -NoLogo -NoProfile -NoExit -Command "& { start-process -filepath $args[0] -argumentlist $args[1..($args.Length - 1)] -nonewwindow -wait}"作为有效负载。这导致递归,启动了许多 powershell 实例,考虑到我正在拦截他们对calc.exe.

不过,这就引出了一个问题:普通调试器如何在不引起这种递归行为的情况下启动被测程序?

小智 9

真的没有接盘侠吗?

无论如何,这是一个很好的 Windows 内部问题,但我现在感兴趣的原因是它对我来说已经成为一个实际问题。在我从事有偿工作的地方是三台计算机,每台计算机都有不同的 Windows 版本,甚至不同的调试器,为此使用 IFEO 技巧会导致调试器本身进行调试,显然陷入了困扰 OP 的相同循环中。

调试器通常如何避免这种循环?嗯,他们自己没有。Windows 为他们避免了它。

但让我们先看看圆形。PowerShell 混合物几乎无法帮助进行简单的演示,并且 calc.exe 已不再是以前的样子。让我们将 notepad.exe 的 Debugger 值设置为“c:\windows\system32\cmd.exe /k”(不带引号)。Windows 会将其解释为尝试运行 notepad.exe 通常应改为运行“c:\windows\system32\cmd.exe /k notepad.exe”。CMD 会将此解释为运行 notepad.exe 并挂起的意思。但是notepad.exe的这个执行也会变成“c:\windows\system32\cmd.exe /k notepad.exe”等等。任务管理器很快就会向您显示数百个 cmd.exe 实例。(好消息是他们都在一个控制台上并且可以一起被杀死。)

OP 的问题是为什么 CMD 及其用于运行子项的 /k(或 /c)开关在 Debugger 值中循环,但例如 WinDbg 则没有。

从某种意义上说,答案是在一个未记录的结构 PS_CREATE_INFO 中的一位,它在用户模式和内核模式之间为 NtCreateUserProcess 函数交换。这种结构在某些圈子中已经众所周知,但他们似乎从未说过如何。我认为该结构可以追溯到 Windows Vista,但直到 Windows 8 之前,Microsoft 的公共符号文件才知道它,甚至那时也不是从内核中知道的,而是从 Internet Explorer 组件 URLMON.DLL 之类的东西中知道的。

无论如何,在 PS_CREATE_INFO 结构的现代形式中,偏移量 0x08(32 位)或 0x10(64 位)处的 0x04 位控制内核是否检查调试器值。符号文件告诉我们这个位被微软称为 IFEOSkipDebugger。如果该位清零并且有 Debugger 值,则 NtCreateUserProcess 失败。通过 PS_CREATE_INFO 结构的其他反馈告诉 KERNELBASE,为了它对 CreateProcessInternalW 的处理,它自己查看 Debugger 值并再次调用 NtCreateUserProcess 但对于(大概)其他一些可执行文件和命令行。

当该位被设置时,内核不关心 Debugger 值并且 NtCreateUserProcess 可以成功。通常如何设置位是由 KERNELBASE 设置的,因为调用者不仅要求创建一个进程,而且特别要求成为新进程的调试器,即在进程创建标志中设置了 DEBUG_PROCESS 或 DEBUG_ONLY_THIS_PROCESS。这就是我所说的调试器自己不会做任何事情来避免循环的意思。Windows 为他们做这件事只是因为他们想要调试可执行文件。

将 Debugger 值视为可执行 X 的图像文件执行选项的一种方法是,该值的存在意味着 X 无法执行,除非在调试器下执行,并且该值的内容可能会告诉如何执行此操作。正如黑客早就注意到的那样,内核的程序员早就注意到了,内容不需要指定调试器,并且可以调整值,以便尝试运行 X 而不是运行 Y。不太注意的是 Y 将无法除非 Y 调试 X(或禁用 Debugger 值),否则运行 X。同样不太注意的是,并非所有运行 X 的尝试都会运行 Y:调试器将 X 作为调试对象运行的尝试不会被转移。

好了,够了。现在我必须为我的网站写 PS_CREATE_INFO。