Skip to content

blnchdev/Clairvoyance

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 

Repository files navigation

Clairvoyance

Example Implementation

Clairvoyance is a solution to implement Windows' InstrumentationCallback to hook system calls of a given process
Aether is necessary for the NtSetInformationProcess

The following should be done before using this solution:

Instructions

Currently, the offsets used are for Windows 25H2, if your version is different you need to verify and change these offsets if they are different:

_TEB64 Offsets

  • Go to the Vergilius Project website, find your Windows version
  • Look for _TEB64, line 9 & 10 of 'Clairvoyance.asm' should be replaced with matching offsets if necessary
  • The same offsets should be used in 'Clairvoyance.h' at line 43 & 44
  • Also grab the offset of ExceptionCode, add 0x4 to it and change the line 38 of 'Clairvoyance.h' to said offset

A solution to prevent this hardcode would be to edit bytecode & the header at runtime based on RtlGetVersion or KUSER_SHARED_DATA
I might do this later, but this is just a PoC for now! :P

Limitations

The current implementation assumes we only want to hook sysret on system calls, the truth is that InstrumentationCallback will be triggered in multiple scenarios;
Opening ntoskrnl in IDA and cross-referencing KiSetupForInstrumentationReturn reveals the following:

IDA xRef
The first member is the one handling sysret

Meaning our callback will also be triggered in some specific scenarios on exception or when a user APC is queued.
For context, we know that the passed argument in a1 is TrapFrame due to a previous call to KeContextToKframes:

IDA Context
KeContextToKframes(PKTRAP_FRAME TrapFrame, PKEXCEPTION_FRAME ExceptionFrame, PCONTEXT ContextFrame, ULONG ContextFlags, KPROCESSOR_MODE PreviousMode)

We also blindly walk backwards and look for mov eax, imm32 or mov eax, [rsp+18h] for regular and direct syscalls; these are very easily bypassable
The direct syscall boolean ('WasDirectSyscall') relies on the above check for its state, a better check for manual system calls would be to check if Rip points to known DLLs

Usage

You need to first initialize Clairvoyance like so:

Clairvoyance::Initialize();
Clairvoyance::AddCallback(/* pointer to callback */); // A callback can (and should) be added as such
void Callback(CONTEXT* Context, bool WasDirect, uint32_t IDX); // Said callback should have this signature

At the time of the callback, the return address (Rip), the stack pointer (Rsp) and the return value (Rcx) are already restored in Context

Notes

This is for educational purposes only.
A real world implementation of InstrumentationCallback for your process security would need robust testing & a better implementation of the callback itself

Credits

NtDoc for NtAPI Definitions

About

InstrumentationCallback Hooking Framework for Windows

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors