【问题标题】:How do debuggers bypass Image File Execution Options when launching their debugee?调试器在启动他们的被调试者时如何绕过图像文件执行选项?
【发布时间】:2019-06-24 11:32:58
【问题描述】:

我正在 Windows 内部进行一些探索,以进行一般的教育,并且我正在尝试了解图像文件执行选项背后的机制。具体来说,我为 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 的调用,这是有道理的。

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

【问题讨论】:

    标签: debugging createprocess windows-nt


    【解决方案1】:

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

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

    但让我们先看看循环性。 PowerShell concoctions 对简单的演示几乎没有帮助,而且 calc.exe 也不再像以前那样了。让我们将notepad.exeDebugger 值设置为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 位控制内核是否检查 Debugger 值。符号文件告诉我们这个位被微软称为IFEOSkipDebugger。如果该位被清除并且存在 Debugger 值,则 NtCreateUserProcess 失败。通过PS_CREATE_INFO 结构的其他反馈告诉KERNELBASE,因为它处理CreateProcessInternalW,有自己的查看调试器值并再次调用NtCreateUserProcess,但(可能)一些其他可执行文件和命令行。

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

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

    【讨论】:

    • 很好的信息。但是你有一个在实际产生进程的上下文中使用它的例子吗?这将非常有用。
    • @Adrian 同意,请参阅我的回答中的 TLDR:stackoverflow.com/a/64212045/67824
    【解决方案2】:

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

    在 C# 中,使用出色的 Vanara.PInvoke.Kernel32 NuGet:

    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 时看不到窗口)- 如果您的程序不是真正的调试器并且您只是想要绕过,那么无论如何都是有意义的。

    【讨论】:

      猜你喜欢
      • 2011-05-07
      • 1970-01-01
      • 1970-01-01
      • 2015-10-20
      • 2017-12-05
      • 1970-01-01
      • 2019-09-12
      • 2018-08-07
      • 1970-01-01
      相关资源
      最近更新 更多