After I posted the question to Twitter, Alex Ionescu (god of NT kernel internals, for those who don’t know) suggested that it could be an export by ordinal from the ntoskrnl.exe. It was not the case in this scenario, but it’s a very good direction for investigation and I didn’t think of it when I encountered the issue.
Following on that lead I dug deeper. Further inspection of the condrv.sys driver confirmed that at least 2 functions from ntoskrnl.exe are imported by the ordinal (0x0001, and 0x0002):
->Import Table 1. ImageImportDescriptor: OriginalFirstThunk: 0x0000A1AC TimeDateStamp: 0x00000000 (GMT: Thu Jan 01 00:00:00 1970) ForwarderChain: 0x00000000 Name: 0x0000AA04 ("ntoskrnl.exe") FirstThunk: 0x00002088 Ordinal/Hint API name ------------ --------------------------------------- 0x00FF "ExReleasePushLockExclusiveEx" 0x0002 <------- import by ordinal 0x05C1 "ObCloseHandle" 0x067F "PsGetProcessImageFileName" 0x0001 <------- import by ordinal
so Alex’s explanation points us in a right direction, except the issue is the import table of condrv.sys, not the export table of ntoskrnl.exe; IDA somehow assumes the names of the ‘guessed’ service functions based on the ordinals and does it wrongly – this is what causes confusion and ends up with us staring at an incorrect disassembly.
A few months back I came up with an idea to find out how the conhost.exe process is spawn by the OS to co-exist with pretty much every single console applications on the system.
I theorized that if I find out how it is spawn, I may be able to possibly influence this process and make it behave differently from the expected (wishful thinking that perhaps I could find a way to run this process anytime I want and hijack it for e.g. process hollowing or sth along these lines).
After looking at the code of the AllocConsole function (which is a prime suspect here as it allocates consoles after all), I eventually landed in the code that calls NtDeviceIoControlFile with the IOCTL 0x500037.
Quick google search followed and I re-visited an excellent posts from FireEye:
where the very same conhost.exe spawning mechanism is also described, although briefly.
So, there is a way to launch a conhost.exe process bypassing all the WinExec/ShellExecute/ShellExecuteEx/CreateProcess/CreateProcessInternal/etc. API layer. Still, there is no easy way to obtain the handle to that process so the malware that would try to use it would still need to find that process and hijack it. It looks like OS designers took the malicious bit into account and made it a bit harder for the coders to abuse it.
The reason for this post is not the mechanism itself, but the curious issue I encountered while analysing the condrv.sys driver. It is the driver that actually receives the IOCTL 0x500037 and processes it, calling the ZwCreateUserProcess routine in the end, at least, this is my working hypothesis at the moment.
When you analyze the the driver w/o symbols, the system routine the driver calls to launch conhost.exe is not ZwCreateUserProcess, but ExAcquireRundownProtection. A very strange choice. It is only applying the .pdb file to IDA will allow to map that call to ZwCreateUserProcess. Could it be an intentional way to obfuscate the calls? Or, am I missing something? I will admit my kernel mode analysis skills are really rusty.
This is what I see inside the condrv.sys without symbols (see that 2nd functions):
and with symbols: