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 _KPROCESSntdll!_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 functionnirvana.Reserved = 0; // always 0nirvana.Version = 0; // 0 for x64, 1 for x86
// just one callNtSetInformationProcess( 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.incEXTERN 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 R10medium ENDP
ENDThat’s all :) Works on Windows 10 v1709 x64
You can get POC code from GitHub
Resourses: