HEVD Windows Kernel Exploitation 4 – Null Pointer Dereference

So as we exploit the 3rd vulnerability on HEVD, I’ll use this as a tradition and will give history on what we’ve done so far and whats up next:

  • With Stack Overfow:  put your shellcode in userland in an allocated memory and execute in kernelland
  • With Arbitraty Overwrite: writing the value pointed by what to the memory location referenced by where
  • With Null Pointer Dereference: writing to a pointer location where the value of the pointer is NULL and used by an application that points to a valid memory location


The strategy for this blogpost will be as follows:

Initial Phase:

  • Some Theory
  • Source Code Review – very brief
  • Finding IOCTL
  • Verifying the vulnerability with the initial script
  • IDA Analysis #2 After the Initial Exploit:

Final Exploit Steps:

  • create shellcode variable from token stealing shellcode (refer to the previous blogs)
  • place the shellcode into a string buffer (we’ll use create_string_buffer from ctypes)
  • VirtualAlloc() to bypass DEP and allocate memory to copy our shellcode there (we’ll use memmove() from ctypes this time)
  • Place a pointer to our shellcode buffer at memory address 0x4 (we’ll use memmove() from ctypes this time)
  • call the shellcode

Theory 1: Non-Paged vs Paged Memory:

At each bootup, the memeory manager creates 2 memory pool (Paged Pool and NonPaged Pool)

  • these are dynamically sized
  • they are used for kernel-components to allocate system memory
  • they start at a certain size (based on the physical memory in the system) 
  • the size of the pool can grow up to a maximum size (determined by the system at boot time) 
  • The paged pool can page out or can be lowered
  • The paged pool consists of virtual memory that can be paged in and out of the system.
  • The NonPaged Pool cannot be paged out (used by drivers so they can be accessed at any Interrupt Request Level)
  • The nonpaged pool consists of virtual memory addresses that are guaranteed to reside in physical memory as long as the corresponding kernel objects are allocated.
  • To improve performance, systems with a single processor have three “paged pools”, and multiprocessor systems have five paged pools.

Theory 2: NULL pointer dereference in Windows OS

We’ll use Windows 7 x86 on this blogpost. This can be exploited on Win 10 x32 as well however starting with Win 8, Microsoft mitigated this vulnerability by making NULL page unavailable. 

Source Code Review:

We have 2 files again: the header file and the source file

As the previous blogpost explained, header file will create a few variables to initialize and call later in the source code file. We’ll have a look at the source code file to understand the vulnerability:

The initial part of the code compare the UserValue with the value 0xBAD0B0B0, if they are different, NullPointerDereference is set to NULL. 

In the secure version, the developer checks if NullPointerDereference is not set to NULL

In the vulnerable version, no checks are made.

The vulnerability is so easy to read and detect in the source file so I’ll focus on more how it looks like on IDA and WinDBG in this blogpost.

Finding the IOCTL Code:

As always, we’ll check IrpDeviceIoCtlHandler function and where NullPointerDereferenceIOCTLHandler call is, which will follow to the TriggerNullPointerDereference call. When we follow in this order, the IOCTL shows up as 0x22202Bh. 

Initial Exploit

IDA Analysis #2 After the Initial Exploit:

Let’s look at the TriggerNullPointerDereference function in disassembly:

there is a ExAllocatePoolWithTag API call with the following parameters before the call and between the previous call:

  • push 6B636148h (which is kacH in ASCII)
  • push 8 
  • push edi (which is 0 as it was cleared few lines before with xor edi, edi)

When we look at the API call reference: https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/nf-wdm-exallocatepoolwithtag

According to the syntax each paramter value refers as below:

  • PoolType: 0 (which refers to NonPagedPool)
  • NumberOfBytes: 8
  • Tag: Hack

The next 3 lines after ExAllocatePoolWithTag call is also worth noting:

  • mov esi,eax: move the API call return value to esi
  • cmp esi, edi: compare the API call return value  with esi (0) if it’s 0
  • jnz short loc_14E5B –> when the result is 0, we’ll trigger the exploit

When we check the manual for ExAllocatePoolWithTag  API call, its mentioned under Return value section as below:

  • ExAllocatePoolWithTag returns NULL if there is insufficient memory in the free pool to satisfy the request.

When we follow the path, we see that our user value is compared with 0xBAD0B0B0h

jnz will take place if those values do NOT match. So we need to make sure our user provided value is not 0xBAD0B0B0h

When we go down in the code, we see the following last line which triggers the exploit:

call dword ptr [esi+4] –> esi was 0 as we saw before and we have a pointer at address 0x00000004

  • That’s going to reside on the NULL page
  • Our code will dereference a null pointer


We’ll refer to the original API documentation: https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/ntifs/nf-ntifs-ntallocatevirtualmemory

Well use NtAllocateVirtualMemory call to allocate a shellcode buffer at the pointer location esi+4 as given above

result = ntdll.NtAllocateVirtualMemory(GetCurrentProcess(),pointer(c_void_p(1)),0,pointer(c_ulong(4096)),0x3000,0x40)    

  • ProcessHandle –>we’ll get the handle to the current process using GetCurrentProcess()
  • BaseAddress –> will be a pointer to a PVOID 0x1
  • Zerobits –> 0
  • RegionSize –> we will give 4096 bytes
  • AllocationType –> 0x3000  (the constant hex value for MEM_COMMIT | MEM_RESERVE)
  • Protect –> 0x40 (the constant hex value for PAGE_EXECUTE_READWRITE)

result returning 0 means we mapped a NULL page


Final Exploit

import struct, sys, ctypes
from ctypes import *
from ctypes.wintypes import *
from subprocess import * 

## DLLs

## NtAllocateVirtualMemory  

## shellcode  

## VirtualAlloc  

## CreateFileA & DeviceIOControl   

## buffer and call the shell