In July of this year (2014), an excellent write up was released by Sebastian Apelt of Siberas on the vulnerability described in the MS14-040 advisory. This vulnerability is a dangling pointer in the AFD.sys driver that when successfully exploited can allow a program to execute code in the context of NT_AUTHORITY\SYSTEM. This is a significant vulnerability as there are currently no privilege escalation vulnerabilities that are publicly available and weaponized which target the Windows 8.1 x64 kernel. The whitepaper, while well written was vague when it came to certain technical details necessary to reproduce a working exploit. This blog will explore some of the technical details that an attacker looking to weaponize the exploit would be interested in.
While the white paper mentions that all versions of Windows from XP to 8.1 were affected when the patch was released, not all version are exploitable with the technique that is described. The white paper makes use of the nt!NtCreateWorkerFactory function which is essential to setting up the memory structures in a way for the exploit to work. The nt!NtCreateWorkerFactory call is necessary to replace the freed data on the kernel pool with a controlled object, later this object is used with the nt!NtSetInformationWorkerFactory to achieve an arbitrary write to kernel memory. This system call was not introduced until Windows Vista, and thus older versions of Windows would need to find a different system call suitable for replacing the freed data in the kernel pool for use with a read and write primitive. Additionally, starting in Windows 8.1, the WorkerFactory object is allocated on the NonPagedPoolNx where as it is allocated on a different pool in Windows 7. The location of where the MDL and WorkerFactory are allocated is important to ensure that when the WorkerFactory is created it replaces the MDL in the correct kernel pool so that it can be freed and then corrupted.
The white paper implies that during the Pwn2Own competition where the exploit was first debuted that the target was a 64-bit kernel but that the exploit ran in the WoW64 environment. Regardless of the architecture of the process running the exploit the nt!NtSetInformationWorkerFactory call only allows 4 bytes to be written, while this could be used twice in a row it’s not necessary when using a ROP gadget to disable SMEP. On a 64-bit Windows 8.1 system, modifying the 4 least significant bytes of the nt!HalDispatchTable is sufficient to cause it to point at the ROP gadget in nt!KiConfigureDynamicProcessor. This is because both symbols are relatively close together in the kernel address space.
Like many Windows internal functions, nt!NtCreateWorkerFactory does not have any official documentation. The arguments can be found in the source code to process hacker on SourceForge. To successfully call the function, a completion port needs to be passed in as the 4th parameter. This can be created with a call to CreateIoCompletionPort with the following parameters:
hCompletionPort = CreateIoCompletionPort( INVALID_HANDLE_VALUE, NULL, 1337, 4 ); /* error handling and other stuff */ ntStatus = fNtCreateWorkerFactory( &hWorkerFactory, GENERIC_ALL, NULL, hCompletionPort, (HANDLE)-1, &Exploit, NULL, 0, 0, 0 );
The technique for disabling SMEP in the whitepaper uses a ROP gadget in the nt!KiConfigureDynamicProcessor function to set the value of the cr4 register. The address of this ROP gadget was presumably determined by calculating the base address of the kernel with a read primitive, allowing the exploit to bypass ASLR. Using a read primitive to leak a kernel address would be necessary if the exploit is running in the WoW64 environment. If the exploit however were running on the native architecture then the exploit would be able to load the kernel into the processes and calculate the address of this gadget. This technique is very commonly used to determine the address of the nt!HalDispatchTable. One small problem with loading the kernel to determine the address is that on Windows 8.1 the nt!KiConfigureDynamicProcessor address can not be resolved with a call to GetProcAddress like the address of the nt!HalDispatchTable symbol. This can be worked around though by parsing the PE headers, and finding the section (PAGELK) where the target function resides. From here the exploit can search the memory and find the address of the ROP gadget to disable SMEP. This approach would be much more flexible and would allow for ASLR to be bypassed without using a read primitive during the exploitation process with the only draw back being that the exploit must be running on the native architecture of the target system. This technique of dynamically finding the ROP gadget in nt!KiConfigureDynamicProcessor would also have the benefit of not relying on a static offset to the gadget which can be different for various patch levels of the kernel.
After successful exploitation, multiple structures have been corrupted, this leads to the exploit being very unreliable. There has not been any details released on how to clean up the memory structures in such a way that the system will continue to function after the exploit has run. In the lab environment, successful exploitation occurred around 25% of the time, with the other attempts resulting in a blue screen. Furthermore, after successful exploitation, when the attacking process exits, the system will blue screen when the handles used in the attack are closed and garbage collected.
Exploit instability seems to come from two primary factors. The first is successfully replacing the freed MDL with the WorkerFactory object in the kernel pool. If the WorkerFactory is allocated at a different address then the exploit will not function correctly. The second, is the handling of the WorkerFactory object with the exploitation processes closes. The WorkerFactory has been freed and then corrupted for the purposes of use with the write primitive and as such when it is freed, it causes an unhandled kernel exception to be thrown.