cmd 没有设置 CreateProcess STARTUPINFO 结构的 STARTF_USESTDHANDLES 标志。相反,它会暂时重定向自己的标准句柄并依赖继承。即使 cmd 必须调用 ShellExecuteEx,这种方法也有效,因为它缺少显式设置标准句柄的方法。
但是,如果在进程创建标志中设置了CREATE_NEW_CONSOLE(这是start 命令的默认设置),则重定向其自己的标准句柄不起作用。要避免此问题,请使用/b 选项来防止创建新控制台。
您可能还想将 stderr 重定向到 stdout 或文件。这可以防止错误被写入控制台。例如:
start /b program.exe <in.txt >out.txt 2>&1
start /b program.exe <in.txt >out.txt 2>err.txt
start /b program.exe <in.txt >out.txt 2>nul
使用Debugging Tools for Windows的示例
(test) C:\>cdb -Goxi ld cmd
Microsoft (R) Windows Debugger Version 6.12.0002.633 AMD64
Copyright (c) Microsoft Corporation. All rights reserved.
CommandLine: cmd
Symbol search path is: symsrv*symsrv.dll*
C:\Symbols*http://msdl.microsoft.com/download/symbols
Executable search path is:
(ed0.1770): Break instruction exception - code 80000003 (first chance)
ntdll!LdrpDoDebuggerBreak+0x30:
00000000`77848700 cc int 3
0:000> .reload /f
Reloading current modules
.....
0:000> bp CreateProcessW
0:000> g
在新控制台中运行 where.exe。
Microsoft Windows [Version 6.1.7601]
Copyright (c) 2009 Microsoft Corporation. All rights reserved.
(test) C:\>start /w where.exe <nul >nul
Breakpoint 0 hit
kernel32!CreateProcessW:
00000000`775a0660 4883ec68 sub rsp,68h
请注意,cmd.exe 在调用CreateProcess 之前会重定向其StandardOutput:
0:000> ?? ((ntdll!_PEB *)@$peb)->ProcessParameters->StandardOutput
void * 0x00000000`00000060
0:000> !handle 60 3
Handle 60
Type File
Attributes 0
GrantedAccess 0x120196:
ReadControl,Synch
Write/Add,Append/SubDir/CreatePipe,WriteEA,ReadAttr,WriteAttr
HandleCount 2
PointerCount 3
进程创建标志,即dwCreationFlags,第6个参数:
0:000> dd (@rsp + 6*8) l1
00000000`00182c58 00080410
作为 0x80410 传递,这是以下标志的按位或:
-
EXTENDED_STARTUPINFO_PRESENT
-
CREATE_UNICODE_ENVIRONMENT
CREATE_NEW_CONSOLE
因为创建了一个新的控制台,所以 where.exe 没有继承 cmd 的标准句柄:
0:000> g
Symbol search path is: symsrv*symsrv.dll*
C:\Symbols*http://msdl.microsoft.com/download/symbols
Executable search path is:
(1550.1a80): Break instruction exception - code 80000003 (first chance)
ntdll!LdrpDoDebuggerBreak+0x30:
00000000`77848700 cc int 3
1:001> ?? ((ntdll!_PEB *)@$peb)->ProcessParameters->StandardOutput
void * 0x00000000`00000007
注意:在 Windows 8+ 中,控制台句柄只是一个常规文件句柄,因此您必须深入了解。
我在此示例中使用的是 Windows 7,因此控制台句柄是通过设置低 2 位标记的假句柄(例如 3、7、11 => 0b0011、0b0111、0b1011)。 “假”是指它们不在用于内核对象句柄的进程句柄表中。因此,例如,您不能使用调试器 !handle 命令来检查句柄 7:
1:001> !handle 7 f
Could not duplicate handle 7, error 87
在 Windows 7 中,控制台句柄由控制台主机进程 conhost.exe 分配和管理。它们被标记,因此 Windows 基本函数可以通过 NtRequestWaitReplyPort 对 conhost.exe 进行所需的 LPC 调用。
上面的例子演示了如何创建一个新的控制台覆盖继承 cmd 的重定向标准句柄。现在让我们添加/b 选项以防止创建新控制台。
1:001> g
(test) C:\>start /b /w where.exe <nul >nul
Breakpoint 0 hit
kernel32!CreateProcessW:
00000000`775a0660 4883ec68 sub rsp,68h
dwCreationFlags 是 0x80600:
0:000> dd (@rsp + 6*8) l1
00000000`00182c58 00080600
这是以下创建标志的按位或:
EXTENDED_STARTUPINFO_PRESENT
-
CREATE_UNICODE_ENVIRONMENT
CREATE_NEW_PROCESS_GROUP
(指定/b 的副作用是将进程创建为新进程组的领导者。如果它是控制台进程,则允许生成针对该组的Ctrl+Break 事件。)
在这种情况下,where.exe 确实从 cmd.exe 继承了重定向的标准句柄:
0:000> g
Symbol search path is: symsrv*symsrv.dll*
C:\Symbols*http://msdl.microsoft.com/download/symbols
Executable search path is:
(1508.1534): Break instruction exception - code 80000003 (first chance)
ntdll!LdrpDoDebuggerBreak+0x30:
00000000`77848700 cc int 3
1:001> ?? ((ntdll!_PEB *)@$peb)->ProcessParameters->StandardOutput
void * 0x00000000`00000064
1:001> !handle 64 3
Handle 64
Type File
Attributes 0
GrantedAccess 0x120196:
ReadControl,Synch
Write/Add,Append/SubDir/CreatePipe,WriteEA,ReadAttr,WriteAttr
HandleCount 3
PointerCount 4
同样,在 Windows 7 中,很容易发现控制台伪句柄,因为它是通过设置句柄值的低 2 位来标记的。对于 Windows 8+,快速检查是查看文件授予访问权限的低半字节(4 位),其中读取数据访问权限为 1,写入数据访问权限为 2,附加数据访问权限为 4。控制台缓冲区具有读取和写入访问权限,而 cmd 的重定向使用读取 (<) 或写入 (>),但不能同时使用两者。以上是重定向的输出,您可以看到文件以写入和附加访问权限(2+4)打开,但没有读取访问权限。这是一个快速检查,但如果您想确定可以使用内核调试器(如 kd.exe)或工具(如 Sysinternals Process Explorer 或 handle.exe)。这些可以向您显示 NT 内核对象路径,例如 Windows 8+ 控制台输入句柄的\Device\ConDrv\Input。