skip to content
secrary[dot]com

Hooking via InstrumentationCallback

/

I want to write about a very interesting epilogue hooking method presented by Alex Ionescu at REcon 2015.

An epilogue detour allows for post-processing. It is useful for filtering output parameters once an original routine has performed its duties.

I know it’s not a new technique, but I found it very interesting. Additionally, all the POC codes I found were crashing.

I tried to create a POC that does not crash (at least for me) and ends normally (by the way, it’s an EXE, not a DLL).

The KPROCESS structure contains a field called InstrumentationCallback at 0x2c8:

0:000> dt _KPROCESS
ntdll!_KPROCESS
+0x000 Header : _DISPATCHER_HEADER
+0x018 ProfileListHead : _LIST_ENTRY
+0x028 DirectoryTableBase : Uint8B
+0x030 ThreadListHead : _LIST_ENTRY
+0x040 ProcessLock : Uint4B
+0x044 ProcessTimerDelay : Uint4B
+0x048 DeepFreezeStartTime : Uint8B
+0x050 Affinity : _KAFFINITY_EX
+0x0f8 ReadyListHead : _LIST_ENTRY
+0x108 SwapListEntry : _SINGLE_LIST_ENTRY
+0x110 ActiveProcessors : _KAFFINITY_EX
+0x1b8 AutoAlignment : Pos 0, 1 Bit
+0x1b8 DisableBoost : Pos 1, 1 Bit
+0x1b8 DisableQuantum : Pos 2, 1 Bit
+0x1b8 DeepFreeze : Pos 3, 1 Bit
+0x1b8 TimerVirtualization : Pos 4, 1 Bit
+0x1b8 CheckStackExtents : Pos 5, 1 Bit
+0x1b8 PpmPolicy : Pos 6, 3 Bits
+0x1b8 ActiveGroupsMask : Pos 9, 20 Bits
+0x1b8 ReservedFlags : Pos 29, 3 Bits
+0x1b8 ProcessFlags : Int4B
+0x1bc BasePriority : Char
+0x1bd QuantumReset : Char
+0x1be Visited : UChar
+0x1bf Flags : _KEXECUTE_OPTIONS
+0x1c0 ThreadSeed : [20] Uint4B
+0x210 IdealNode : [20] Uint2B
+0x238 IdealGlobalNode : Uint2B
+0x23a Spare1 : Uint2B
+0x23c StackCount : _KSTACK_COUNT
+0x240 ProcessListEntry : _LIST_ENTRY
+0x250 CycleTime : Uint8B
+0x258 ContextSwitches : Uint8B
+0x260 SchedulingGroup : Ptr64 _KSCHEDULING_GROUP
+0x268 FreezeCount : Uint4B
+0x26c KernelTime : Uint4B
+0x270 UserTime : Uint4B
+0x274 ReadyTime : Uint4B
+0x278 Spare2 : [80] UChar
+0x2c8 InstrumentationCallback : Ptr64 Void <<< That's it :)
+0x2d0 SecureState : <unnamed-tag>

Windows Vista and later you can specify callback address using InstrumentationCallback field and the callback will be called after each time any function returns from kernel to user mode.

One way to specify callback address is by using a driver, but as turns out there is a much easier way via NtSetInformationProcess API from user mode without any special privileges, we just need to specify correct structures and that’s all.

PROCESS_INSTRUMENTATION_CALLBACK_INFORMATION nirvana;
nirvana.Callback = (PVOID)(ULONG_PTR)medium; // callback function
nirvana.Reserved = 0; // always 0
nirvana.Version = 0; // 0 for x64, 1 for x86
// just one call
NtSetInformationProcess(
GetCurrentProcess(),
(PROCESS_INFORMATION_CLASS)ProcessInstrumentationCallback,
&nirvana,
sizeof(nirvana) /*0x8*/ );

There are pitfalls, we have to use assembly inside our code for the callback function, due to we need to deal with registers directly.

include ksamd64.inc
EXTERN hook:NEAR
.code
medium PROC
; why pushing this registers? >> https://docs.microsoft.com/en-us/cpp/build/caller-callee-saved-registers
push rax ; return value
push rcx
push RBX
push RBP
push RDI
push RSI
push RSP
push R12
push R13
push R14
push R15
; without this it crashes :)
; Much smaller value is enough: https://www.reddit.com/r/ReverseEngineering/comments/7gpkw4/hooking_via_instrumentationcallback/
; P.S. thank you for feedback
sub rsp, 1000h
mov rdx, rax
mov rcx, r10
call hook
add rsp, 1000h
pop R15
pop R14
pop R13
pop R12
pop RSP
pop RSI
pop RDI
pop RBP
pop RBX
pop rcx
pop rax
jmp R10
medium ENDP
END

That’s all :) Works on Windows 10 v1709 x64

You can get POC code from GitHub

instr

Resourses: