调试0xC0000142错误的通用方法
开篇
我相信用过Windows的人都经历过0xC0000142错误。多年前也解决过此类问题,最近写代码时又遇到了,于是打算将解决过程记录下来。
何为0xC0000142错误?
弹框是一个HardError(不明白的自行搜索),打开OpenArk查看NTSTATUS错误码如图所示:
提示动态库DLL初始化例程失败,说明某关键DLL的DllMain返回失败,那么是谁初始化失败?
GFlags归来
以下是Gflag介绍,具体使用参考windbg帮助文档
GFlags (the Global Flags Editor), gflags.exe, enables and disables advanced debugging, diagnostic, and troubleshooting features. It is most often used to turn on indicators that other tools track, count, and log.
打开windbg目录下的gflags.exe,切换到ImageFile标签,输入cmd.exe后按tab键激活,勾选Show loader snaps,点击应用即可,如图所示:
gflag.exe只是单纯将调试标志位写入注册表HKLM\Software\Microsoft\Windows NT\CurrentVersion\Image File Execution Options,这一切还得靠Windows强大的调试设施支撑。
Loader Snap可以理解为调试输出标志位,用于打印系统PE Loader的日志。上述错误发生在遍历输入表依赖并调用初始化函数,所以开启该选项。
Windbg登场
回到代码本身,当时写的一个伪造父进程的代码片段,执行调用:test.exe cmd.exe {ppid}
BOOL TestFakeParentCreateProcess(char *exepath, DWORD ppid)
{
//打开要伪造的父进程句柄
HANDLE pphd = OpenProcess(PROCESS_CREATE_PROCESS, TRUE, ppid);
SIZE_T size = 0x30;
STARTUPINFOEXA si = { 0 };
PROCESS_INFORMATION pi = { 0 };
si.StartupInfo.cb = sizeof(STARTUPINFOEX);
*(PVOID*)(&si.lpAttributeList) = malloc(size);
si.StartupInfo.dwFlags = STARTF_USESHOWWINDOW;
si.StartupInfo.wShowWindow = SW_SHOW;
if (si.lpAttributeList) {
if (InitializeProcThreadAttributeList(si.lpAttributeList, 1, 0, &size)) {
if (UpdateProcThreadAttribute(si.lpAttributeList, 0, PROC_THREAD_ATTRIBUTE_PARENT_PROCESS, &pphd, sizeof(HANDLE), 0, 0)) {
if (CreateProcessA(0, exepath, 0, 0, 0, CREATE_UNICODE_ENVIRONMENT | EXTENDED_STARTUPINFO_PRESENT, 0, NULL, (LPSTARTUPINFOA)&si, &pi)) {
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
CloseHandle(pphd);
return TRUE;
}
}
}
if (si.lpAttributeList)
DeleteProcThreadAttributeList(si.lpAttributeList);
free(si.lpAttributeList);
}
CloseHandle(pphd);
return FALSE;
}
Windbg打开test.exe,勾选调试子进程(或.childdbg 1),输入参数cmd.exe {ppid},随后调试进入cmd.exe时,如期产生了大量加载日志:
4498:4c50 @ 01184515 - LdrpGetProcedureAddress - INFO: Locating procedure "EtwEventActivityIdControl" by name
4498:4c50 @ 01184515 - LdrpGetProcedureAddress - INFO: Locating procedure "EtwEventWriteTransfer" by name
4498:4c50 @ 01184515 - LdrpGetProcedureAddress - INFO: Locating procedure "EtwEventSetInformation" by name
4498:4c50 @ 01184515 - LdrpInitializeNode - INFO: Calling init routine 00007FFC39170710 for DLL "C:\windows\System32\KERNELBASE.dll"
4498:4c50 @ 01184515 - LdrpInitializeNode - ERROR: Init routine 00007FFC39170710 for DLL "C:\windows\System32\KERNELBASE.dll" failed during DLL_PROCESS_ATTACH
4498:4c50 @ 01184515 - LdrpUnloadNode - INFO: Unmapping DLL "C:\windows\System32\KERNEL32.DLL"
4498:4c50 @ 01184515 - LdrpProcessDetachNode - INFO: Uninitializing DLL "C:\windows\System32\KERNELBASE.dll" (Init routine: 00007FFC39170710)
4498:4c50 @ 01184515 - LdrpUnloadNode - INFO: Unmapping DLL "C:\windows\System32\KERNELBASE.dll"
4498:4c50 @ 01184515 - LdrpLoadDllInternal - RETURN: Status: 0xc0000142
4498:4c50 @ 01184515 - LdrLoadDll - RETURN: Status: 0xc0000142
4498:4c50 @ 01184515 - LdrpInitializeProcess - ERROR: Loading Windows subsystem DLL "KERNEL32.DLL" failed with status 0xc0000142
4498:4c50 @ 01184515 - _LdrpInitialize - ERROR: Process initialization failed with status 0xc0000142
4498:4c50 @ 01184515 - LdrpInitializationFailure - ERROR: Process initialization failed with status 0xc0000142
从日志可以看出,是因为初始化KERNELBASE.dll初始化失败。
动态Trace和静态分析
重新调试,执行命令sxe ld kernelbase,进入cmd进程空间时,在kernelbase的DllMain入口(即KERNELBASE!KernelBaseDllInitialize函数)下断点,执行wt -or开始动态Trace,得到结果如下:
32 278 [ 3] KERNELBASE!ConsoleCreateConnectionObject
22 0 [ 4] ntdll!RtlAllocateHeap
88 0 [ 4] ntdll!RtlpAllocateHeapInternal
463 0 [ 5] ntdll!RtlpAllocateHeap
19 0 [ 6] ntdll!RtlLeaveCriticalSection rax = 0
487 19 [ 5] ntdll!RtlpAllocateHeap rax = 0000011b`74ec44c0
115 506 [ 4] ntdll!RtlpAllocateHeapInternal rax = 0000011b`74ec44c0
48 921 [ 3] KERNELBASE!ConsoleCreateConnectionObject
1 0 [ 4] KERNELBASE!memcpy
291 0 [ 4] ntdll!memcpy rax = 0000011b`74ec44cf
55 1213 [ 3] KERNELBASE!ConsoleCreateConnectionObject
41 0 [ 4] ntdll!RtlInitUnicodeString rax = 12
75 1254 [ 3] KERNELBASE!ConsoleCreateConnectionObject
6 0 [ 4] ntdll!NtCreateFile rax = c000003b
从下往上可以很明显看到,调用了NtCreateFile失败,返回c000003b(指定的路径无效)。再次调试下断点NtCreateFile,得到ObjectAttributes参数如下:
+0x000 Length : 0x30
+0x008 RootDirectory : 0x4
+0x010 ObjectName : "\Connect"
+0x018 Attributes : 0x40
!handle 4查看RootDirectory句柄得到是ALPC Port(很明显这里有问题,因为不是Directory句柄),总结来说就是句柄有问题。
打开IDA阅读ConsoleInitialize和ConsoleCreateConnectionObject代码,这里我就不细说了,反汇编看这点逻辑应该是很轻松的事。
上面的RootDirectory句柄实际上是ConsoleHandle,来源于PEB.ProcessParameters.ConsoleHandle
[+0x010] ConsoleHandle : 0x4 [Type: void *]
[+0x018] ConsoleFlags : 0x0 [Type: unsigned long]
[+0x020] StandardInput : 0x0 [Type: void *]
[+0x028] StandardOutput : 0x0 [Type: void *]
[+0x030] StandardError : 0x0 [Type: void *]
原因找到,对于Console程序,这里的句柄默认都是从父进程继承过来(attach),而PEB在初始化时更新了ConsoleHandle值而父进程里没有对应的句柄(因为父进程已经换了),因此得到的是无效句柄。
关于控制台相关研究,可参考《win32-console-docs》
问题解决:在CreateProcess时增加CREATE_NEW_CONSOLE标志即可,无需attach父进程的console
结尾
本文提出了一种解决此类问题的通用思路:从原理出发,Gflags开启日志,Trace跟踪Loader,静态分析代码找到出错原因。