本文中,我想介绍一种非常有趣的尾部挂钩方法,该方法在2015年的REcon会议上由Alex提出。
尾部绕过技术允许后处理,这对于在原始流程执行完毕之后对输出参数进行过滤十分有用。
这并不是一种新的技术,但我认为,这种技术及其我试验过的所有会引发崩溃的POC代码都十分有趣。
我尝试构建不会引发崩溃(至少对我而言)并且能够正常结束的POC(顺便提一句,它生成一个EXE文件,而不是DLL)。
在KPROCESS结构的偏移地址0x2c8处,包含一个名为InstrumentationCallback的域(在Windbg调试器中利用相应的命令能够看到该域,具体如下所示):
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 Bit
+0x1b8 ActiveGroupsMask : Pos 9, 20 Bit
+0x1b8 ReservedFlags : Pos 29, 3 Bit
+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 ContextSwtiches : 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 :
在Windows系统Vista以及之后的版本中,你可以使用InstrumentationCallback域来指定回调函数的地址,每次函数从内核态返回用户态之后系统都会调用指定的回调函数。
一种指定回调函数的方法是使用驱动实现,但实践证明还有一种更简单的方式,即使用无需任何特殊权限的用户态API函数NtSetInformationProcess,我们只需要配置正确的结构体就够了。具体代码如下所示:
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) /*0x08*/);
然而有个困难是,由于需要直接与寄存器打交道,我们必须使用汇编语言编写回调函数的代码。具体如下所示:
include ksamd64.inc
EXTERN hook:NEAR
.code
medium PROC
; 为什么要压入这些寄存器的值?请参考网址:
; https://docs.microsoft.com/en-us/cpp/build/caller-callee-saved-registers
push rax ; 返回值
push rcx
push rbx
push rbp
push rdi
push rsi
push rsp
push r12
push r13
push r14
push r15
; 没有以下这条语句,程序将引发崩溃
; 其实一个小得多的值就足够了,详见网址:
; https://www.reddit.com/r/ReversEngineering/comments/7gpkw4/hooking_via_instrumentationcallback/
; P.S. 感谢反馈
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
以上:) 代码成功运行于Windows 10 v1709 x64系统
你可以从如下网址获取POC代码:
https://github.com/secrary/Hooking-via-InstrumentationCallback
资源:
·对Nirvana进行挂钩(https://www.youtube.com/watch?v=bqU0y4FzvT0)
·Windows x64系统的系统服务挂钩技术和高级调试(https://www.codeproject.com/Articles/543542/Windows-x-system-service-hooks-and-advanced-debug)
·反调试技巧(https://pastebin.com/9TqRGsM5)
·Windows 10系统对Nirvana进行挂钩操作简介(https://sww-it.ru/2016-04-11/1332)
本文由 看雪翻译小组 木无聊偶 编译转载请注明来源