Contents:
Introduction
Linux people who have to work in Windows are often talking about the basic tools it has which are absent from Microsoft’s product. While recent developments of Windows are slowly catching up with variously featured versions of whoami, ln (mklink) cat (copy con), grep (find), ps (tasklist, taskkill) and chmod (icacls), one app that’s so far evaded the conversion is chroot.
For those unaware, chroot allows you to run an application using some specified directory as its filesystem root dir instead of the normal filesystem root. Whatever the reason may be for its absence, it is definitely not because there’s is no support mechanism for it. Just like in Linux, it’s a single function call.
NtSetInformationProcess(hProcess, ProcessDeviceMap, &hObjectDirectory, sizeof(hObjectDirectory));
What’s a Device Map?
Ok, so it’s technically one function call, but there needs to be a bit of setup beforehand. The first step is understanding what a device map is, and the answer is… a bog standard object manager directory [1]. Sllightly underwhelming I know, though that’s only half the story. It’s actually a bog standard object manager directory filled with bog standard symlinks.
If you’ve never poked around before or read anything about what drive letters represent to the object manager, then you may not know that drive letters are merely syntactically sugary symbolic links pointing to the portion of the actual device, volume, or file system directory they reside on:
The symbolic links in the device map directory perform exactly the same purpose. In fact, they take exactly the same format too. In a chroot situation, the only thing that you wouldn’t want to be the same are the targets of the symlinks.
How’s it Work?
The process device map is the first port of call in the lookup of dos device names. This MSDN page explains the lookup process and seperation between Local [1] and Global names. Setting an explicit device map effectively replaces the local DosDevices directory in the lookup process.
Every time a program passes a path starting with the \??\ prefix to a kernel function, this two stage lookup process is activated [2]. The searching looks at the first component of the path, if an entry for it exists in the device map directory then that’s used and everything is well and good. If it doesn’t, the same thing is looked for in the global directory before being given up as a bad job. it’s called an invalid name.
Two things we can make from that:
1) Drive letters aren’t the only things you can redirect. The Pipe, Mailslot and UNC roots are in the global dos directory, so they’re fair game too.
2) You cannot take away any devices from being looked up, only overwrite them or add new ones. Of course, you can overwrite entries to point to an invalid location
Usages
For typical chrooting purposes, starting a suspended process and setting the device map before resuming it is the optimal usage but there’s nothing stopping you from using it on any already running process you have the required access to.
Also, unlike the Linux call, you don’t need elevated permissions to foist it upon a process. As long as you can open a process handle with the PROCESS_SET_INFORMATION right (the Vista+ PROCESS_SET_LIMITED_INFORMATION won’t cut it) you can change the device map and thus curtail its access.
What’s the Catch?
There are a few points to consider on its ultimate usefulness. Since anybody can set a device map at any time with no special privileges, just like in Linux, there’s no guarantee that a rooted process will stay rooted (though in Linux it requires root permissions to escape).
Another downside is that, as of Win7, child processes don’t inherit the device map of their parent. Instead they revert to the default global and local directories.
A third is that, unlike Linux, you can’t use this to create a second ‘version’ of Windows in the chroot dir due the effects of KnownDLL loading, which overrides the device map paths. You will need to copy any non-KnownDll system dlls a program depends on to the chroot dir in order to run them.
There’s also a bug in the WoW64 layer which makes it impossible for a 32-bit process to set a new device map on a 64-bit process [3].
Enough Talk, Gimme
Even with all its flaws, somebody might find a use for such a utility. with that, you can find the C++ source code and a 32/64 bit exe for a chroot-esque program here. Happy rooting.
Notes:
[1]: The default Local directory isn’t actually \Sessions\
[2]: If you’ve never seen paths starting like this, that’s because CreateFile internally prepends it to the normal C:\blah\de\blah for you. If you pass CreateFile with a path starting \\.\ (like pipes and mailslots), \\?\ (like for long paths), or \\ (as in a UNC \\server\share path) that prefix is switched to \??\ and still follows the lookup process.
[3]: There is an oversight in the WoW64 layer for NtSetInformationProcess which makes it impossible for any 32-bit app to use this method. In native circumstances, a call to NtSetInformationProces enters the kernel immediately and validates that you passed a buffer whose size equalled sizeof(HANDLE) and you live and die by that.
In WoW64 circumstances, a call to NtSetInformationProcess is routed through wow64cpu’s X86SwitchTo64BitMode to whNtSetInformationProcess. This function is responsible for ensuring buffer sizes are big enough to call the real NtSetInformationProcess and for unpacking what it returns so the values are manageable to the 32-bit code, herein lies the bug.
00000000`73ab1fec mov edx,17h ; 17h is ProcessDeviceMap 00000000`73ab1ff1 cmp r10d,edx ; if the info level is higher than that, jump 00000000`73ab1ff4 jg wow64!whNtSetInformationProcess+0x285 (00000000`73ab2245) 00000000`73ab1ffa cmp r10d,edx ; if it ProcessDeviceMap, jump to below 00000000`73ab1ffd je wow64!whNtSetInformationProcess+0x25b (00000000`73ab221b) .... 00000000`73ab221b cmp r9d,24h ; if the buffer size is greater than or equal to 36 (0x24) jump to the call below 00000000`73ab221f jae wow64!whNtSetInformationProcess+0x26b (00000000`73ab222b) 00000000`73ab2221 mov eax,0C0000004h ; otherwise return STATUS_INFO_LENGTH_MISMATCH 00000000`73ab2226 jmp wow64!whNtSetInformationProcess+0x46b (00000000`73ab242b) 00000000`73ab222b mov ecx,dword ptr [r8] 00000000`73ab222e mov qword ptr [r8],rcx 00000000`73ab2231 mov r9d,28h 00000000`73ab2237 mov rcx,r11 00000000`73ab223a call qword ptr [wow64!_imp_NtSetInformationProcess (00000000`73aa1ad8)]
The thunk checks the input buffer size as greater than or equal to the whole PROCESS_DEVICE_MAP structure, including the significant portion used only by NtQueryInformationProcess. Since you cannot satisfy both equal to sizeof(HANDLE) (8 on x64) and greater than 36, it is impossible to use.