DataptrHooks

May 26, 2024 ยท View on GitHub

ntoskrnl .data pointer hook on NtConvertBetweenAuxiliaryCounterAndPerformanceCounter for UM-KM communication

Motivation

the standard way to communicate between a usermode client and a kernel mode driver is through IOCTLs

IOCTLs require the driver to create both DriverObject & DeviceObject , and the User can send I/O to the device over a Symbolic Link

Speaking specifically of mapped drivers , this makes it extremly trivial to detect. all we have to do is traverse all DriverObjects on the system (feaisble via ZwOpenDirectoryObject + ObReferenceObjectByHandle) and for each driver make sure the DriverObject->DriverInit member points to a module in the PsLoadedModuleList

thus , mapped drivers evolved and started hijacking IRP_MJ_DEVICE_CONTROL dispatch routines of legitimate loaded drivers , this is also fairly trivial to detect by looking for dispatch hooks and patches

In order to communicate with our mapped driver undetected , we need another way

.data pointers

The idea is simple , we want to find a kernel function that:

  • calls another function by a .data pointer (allows us to steal the control flow without patching .text or triggering PatchGuard!)
  • can be triggered from UserMode (well , so we want to use it whenever we have a message to send to the driver from usermode ...)
  • gets at least one useful argument (so we can pass data to our driver)

Sounds good , but how do we find a .data pointer ?

we can take advantage of control flow guard by searching for '_guard_dispatch' prefixed calls , to elaborate :

when a module is compiled with /guard:cf , the compiler will analyze control flow for indirect call targets at compile time and insert code (ie _guard_dispatch_icall and others) to verify the targets at runtime so , simply put , looking for _guard_dispatch calls will lead us to indirect calls

we can follow that by ALT+T in IDA to find all occurences of _guard_dispatch in ntoskrnl (or any other module that is somewhat exposed to usermode, like win32k.sys, win32kfull.sys etc)

CTRL + F to find Nt prefixed functions ( being syscalls they can be called from Usermode)

lastly , we need to reverse the function , mainly to :

  • understand the code path that leads to the indirect call - can we trigger it from UM ?
  • understand the function prototype , can you use it for the way you communicate ?
  • find the corresponding native api function and call it accordingly in your client

PoC : NtConvertBetweenAuxiliaryCounterAndPerformanceCounter

the function converts the specified auxiliary counter value to the corresponding performance counter value; optionally provides the estimated conversion error in nanoseconds due to latencies and maximum possible drift , but we dont care about that! what we do care about , is the fact the function is calling nt!HalpTimerConvertAuxiliaryCounterToPerformanceCounter by a .data pointer in HalPrivateDispatchTable : )

windbgdataptr

In addition , we can pass a pointer (to a pointer) to a struct to it , and it will pass it along to HalpTimerConvertAuxiliaryCounterToPerformanceCounter , which we can hook(by only replacing a pointer and retrive the sent data (identifying our client requests using a predefined magic)

ntaux

the PoC demonstrates exactly that , but keep in mind this specific poitner has been used for a while now in the game hacking community so Anti Cheats will clap you, its best you find your own pointer

datahook

PoC : CiQueryInformation

the function is called by SeCodeIntegrityQueryInformation through a .data pointer in SeSiCallbacks , and SeCodeIntegrityQueryInformation is called when calling NtQuerySystemInformation with SystemCodeIntegrityInformation , as shown below : )

SeQueryCiInfo

Screenshot 2024-03-30 100336

As we did with NtConvertBetweenAuxiliaryCounterAndPerformanceCounter , we make the pointer point to our driver defined function

the hook filters out requests (checking previous mode and magic)

note the SYSTEM_CODE_INTEGRITY_INFORMATION.Lentgh structure member must be initialzied with 8 , otherwise NTQSI will return STATUS_INFO_LENGTH_MISMATCH

to have more control , the SYSTEM_CODE_INTEGRITY_INFORMATION's second ULONG member is splitted into two INT16 members (keeping the overall struct size the same)

datapocci