The nature of writing kernel exploits is tricky. The necessity for reliable exploitation is paramount given that a failure will likely result in system instability usually manifested in the form of a kernel panic / BSOD. Depending on the nature of the vulnerability, maintaining stability after the attacker’s shellcode has run can be a real challenge. Often times structures are corrupted during the exploitation process which may cause the system to panic after the shellcode has been executed (see the analysis of MS14-040 for an example). Many kernel exploits leverage a technique to execute their shellcode which relies on overwriting a pointer in the kernel’s nt!HalDispatchTable. When the address of nt!HaliQuerySystemInformation is over written in the nt!HalDispatchTable this isn’t an issue because NtQuerySystemInformation can be reliably returned to. This reliable technique is not always applicable depending on the vulnerability being exploited.
One such vulnerability that does not leverage this technique is the recently released exploit for MS14-070, provided by KoreLogicSecurity. It is a local vulnerability targeting Windows Server 2003, and an exploit module was submitted to the Metasploit Framework to leverage it. The vulnerability exists within the tcpip.sys driver and allows an attacker to inject controlled memory into the kernel through a specially crafted IOCTL. Simply returning to the calling function from the shellcode was proving to be unstable and causing the host process to crash, leading to the original meterpreter session dying. The module instead uses a shellcode stub after the main payload to walk the stack to find a suitable address to return to. Walking up the stack is made possible by the standard assembly preamble used in most functions which preserves the values of the stack pointer by putting it in ebp and storing the previous value on the stack. For more information, see Josh Haberman’s excellent blog Deep Wizardry: Stack Unwinding.
Just after the token stealing shellcode has been executed and the attacking process has been elevated the stack is in the following state:
# ChildEBP RetAddr 00 ba683b38 ba9be588 0x203e 01 ba683b60 ba9a5aff tcpip!ProcessAORequests+0x15f 02 ba683b80 ba98516b tcpip!SetAddrOptions+0x96 03 ba683bb4 ba9850de tcpip!TdiSetInformationEx+0x539 04 ba683be8 ba984b24 tcpip!TCPSetInformationEx+0x8c 05 ba683c04 ba984b51 tcpip!TCPDispatchDeviceControl+0x149 06 ba683c3c 8081e185 tcpip!TCPDispatch+0xf9 07 ba683c50 808f787b nt!IofCallDriver+0x45 08 ba683c64 808f861d nt!IopSynchronousServiceTail+0x10b 09 ba683d00 808f1164 nt!IopXxxControlFile+0x5e5 0a ba683d34 8088b658 nt!NtDeviceIoControlFile+0x2a 0b ba683d34 7c82845c nt!KiSystemServicePostCall 0c 00dbfe4c 7c826e39 ntdll!KiFastSystemCallRet 0d 00dbfe50 00ee64d9 ntdll!ZwDeviceIoControlFile+0xc 0e 00dbfec8 00ee5a92 0xee64d9 0f 00dbff38 00a1bc03 0xee5a92 10 00dbff8c 00a1bb1d 0xa1bc03 11 00dbffb8 77e6484f 0xa1bb1d 12 00dbffec 00000000 0x77e6484f
Hard coding addresses in exploits is rarely a good idea, especially for return addresses. In order to return to a frame which will allow the execution to complete with out throwing an exception the attacker needs to have a point of reference. In this case all of the frames with return addresses to kernel functions are easily identified due to being greater than 0x7fffffff. The exploit returns to the last address which will clean the resources from the syscall and continue to execute in a safe way.
As was described earlier, this exploit is triggered by a call to ntdll!ZwDeviceIOControlFile. In the stack it can be seen as the second to last userland address before execution is passed to the kernel. On modern Windows systems, syscalls use ntdll!KiFastSystemCall which executes the sysenter instruction to enter the kernel. The return address from this instruction can be seen as ntdll!KiFastSystemCallRet, which is the last return address in userland. On the other side of the proverbial kernel land fence is a return address to nt!KiSystemServicePostCall which will switch the execution context back to userland.
In the case of the MS14-070 exploit, it was desirable to skip the execution of the kernel frames after the shellcode executed. To accomplish this a small assembly stub is used to read up the stack, inspecting the addresses to find the first one in userland by comparing it to 0x80000000. Presumably the last address in userland will be ntdll!KiFastSystemCallRet and the first address in kernel land will be nt!KiSystemServicePostCall. The assembly stub counts up the frames before it reaches the last userland address and then modifies this to find the offset to nt!KiSystemServicePostCall. Once this offset has been found it moves itself up the stack, allowing it to return safely to nt!KiSystemServicePostCall where the execution context will be changed back to userland.
References: