调试器在启动被调试对象时如何绕过映像文件执行选项



我正在Windows内部进行一些探索,以获得一般启发,并试图了解图像文件执行选项背后的机制。具体来说,我为calc.exe设置了一个Debugger条目,其中"C:WindowsSystem32WindowsPowerShellv1.0powershell.exe" -NoLogo -NoProfile -NoExit -Command "& { start-process -filepath $args[0] -argumentlist $args[1..($args.Length - 1)] -nonewwindow -wait}"作为有效负载。这导致了递归,许多powershell实例正在启动,考虑到我正在拦截它们对calc.exe的调用,这是有意义的。

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

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

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

但让我们先来看看圆形。PowerShell混合物几乎无法帮助简单的演示,calc.exe也今非昔比。让我们将notepad.exeDebugger值设置为c:windowssystem32cmd.exe /k。Windows会将其解释为尝试运行notepad.exe通常应该运行c:windowssystem32cmd.exe /k notepad.exe。CMD会将其解释为运行notepad.exe并挂起。但是notepad.exe的执行也会变成c:windowssystem32cmd.exe /k notepad.exe,依此类推。任务管理器很快就会向您显示数百个cmd.exe实例。(好消息是,它们都在一个控制台上,可以一起被杀死。)

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

从某种意义上说,答案是在一个未记录的结构PS_CREATE_INFO中的一位,该结构在用户模式和内核模式之间为NtCreateUserProcess函数交换。这种结构在一些圈子里已经相当出名了,但他们似乎从来没有说过是如何做到的。我认为这个结构可以追溯到Windows Vista,但在Windows 8之前,从微软的公共符号文件中还不知道,即使在那时,也不是从内核中知道,而是从Internet Explorer组件URLMON.DLL.中知道

无论如何,在PS_CREATE_INFO结构的现代形式中,偏移量0x08(32位)或0x10(64位)处的0x04位控制内核是否检查Debugger值。符号文件告诉我们,Microsoft将此位称为IFEOSkipDebugger。如果该位是清除的,并且存在Debugger值,则NtCreateUserProcess失败。通过PS_CREATE_INFO结构的其他反馈告诉KERNELBASE,为了处理CreateProcessInternalW,查看Debugger值并再次调用NtCreateUserProcess,但(可能)调用其他可执行文件和命令行。

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

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

Geoff伟大答案的TLDR-使用DEBUG_PROCESSDEBUG_ONLY_THIS_PROCESS绕过Debugger/Image File Execution Options (IFEO)全局标志,避免递归。

在C#中,使用优秀的Vanara.PInvoke.Kernel32NuGet:

var startupInfo = new STARTUPINFO();
var creationFlags = Kernel32.CREATE_PROCESS.DEBUG_ONLY_THIS_PROCESS;
CreateProcess(path, null, null, null, false, creationFlags, null, null, startupInfo, out var pi);
DebugActiveProcessStop(pi.dwProcessId);

请注意,DebugActiveProcessStop对我来说是关键(否则打开notepad.exe时看不到窗口)——如果你的程序不是真正的调试器,而你只想绕过它,这是有意义的。

最新更新