分析环境为 x64 版本
InstrumentationCallback
回调的技术刚出来的时候就收藏了, 一直没有去研究学习它. 现在有时间来看一下这个东西.
这个回调存储在 KPROCESS-> InstrumentationCallback
.
1 2
| 0: kd> dtx nt!_KPROCESS [+0x100] InstrumentationCallback : 0x7FF6FA0E11AE
|
它很有意思, 当从内核返回到用户层的时候, 就会触发这个回调.
下面是一些触发点:
- LdrInitializeThunk
- KiUserExceptionDispatcher
- KiRaiseUserExceptionDispatcher
- KiUserCallbackDispatcher
- KiUserApcDispatcher
- sysret (KeSystemServiceExit)
怎么用 ?
安装回调
既然这个回调存储在 KPROCESS
, 那么我们就看一下设置进程相关的 NtSetInformationProcess()
根据资料和分析可以知道传入参数的结构为:
1 2 3 4 5 6 7 8 9 10 11 12
| typedef struct _PROCESS_INSTRUMENTATION_CALLBACK_INFORMATION { PVOID Callback; } PROCESS_INSTRUMENTATION_CALLBACK_INFORMATION, * PPROCESS_INSTRUMENTATION_CALLBACK_INFORMATION;
typedef struct _PROCESS_INSTRUMENTATION_CALLBACK_INFORMATION_EX { ULONG Version; ULONG Reserved; PVOID Callback; } PROCESS_INSTRUMENTATION_CALLBACK_INFORMATION_EX, * PPROCESS_INSTRUMENTATION_CALLBACK_INFORMATION_EX;
|
下面代码是我对参数处理相关代码的还原:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| NtSetInformationProcess() { if ((aProcessInformationLength - 8) & 0xFFFFFFF7) return STATUS_INFO_LENGTH_MISMATCH;
if (aProcessInformationLength == 8) InstrumentationCallbackInfo.Callback = *(PVOID *)aProcessInformation; else InstrumentationCallbackInfo.Callback = *(PROCESS_INSTRUMENTATION_CALLBACK_INFORMATION_EX *)aProcessInformation;
if (InstrumentationCallbackInfo.Reserved) return STATUS_INVALID_PARAMETER;
if (InstrumentationCallbackInfo.Version != InstrumentationCallbackInfo.Reserved) return STATUS_UNKNOWN_REVISION;
if (InstrumentationCallbackInfo.Callback != (void *)((_QWORD)InstrumentationCallbackInfo.Callback << 16 >> 16)) return STATUS_INVALID_PARAMETER; }
|
从代码中我们看到几个信息:
1. 函数接受两种长度的参数. 8 和 16
2. Reserved 必须为 0
3. Reversed 必须等于 Version
4. 回调地址必须是用户层回调地址
经过调试发现, Windows 10 之前的系统只支持长度为 8 的参数.
那么 NtSetInformationProcess()
的调用方法就是:
1 2 3 4 5
| NtSetInformationProcess( GetCurrentProcess(), PROCESSINFOCLASS::ProcessInstrumentationCallback, Information, InformationLength);
|
接下来我们就要看下怎么构造这个回调, 也就是要看下这个回调能用的参数是什么?
构造回调
要想知道回调的参数是什么, 我们就要去看一下触发点.
我选了比较简单的 KiDispatchException()
函数来分析.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| KiDispatchException() { _disable(); TrapFrame->SegCs = 0x33; TrapFrame->Rip = KeUserExceptionDispatcher; InstrumentationCallback = KeGetCurrentThread()->ApcState.Process->InstrumentationCallback; if (InstrumentationCallback) { v6->R10 = (unsigned __int64)TrapFrame->Rip; v6->Rip = (unsigned __int64)InstrumentationCallback; } _enable(); }
|
从代码中我们看到, KiDispatchException()
回到应用层是构造了一个 TrapFrame
.
其中有处关键的地方: R10
, 如果存在 InstrumentationCallback
回调, 这个寄存器就用来存储本应该返回的地址. 其它就是正常展开 TrapFrame
. 所以我们要自己构造一个垫片函数, 来正确处理参数.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| include ksamd64.inc EXTERN InstrumentationCallback:NEAR
.CODE
InstrumentationCallbackShim PROC
; https://docs.microsoft.com/en-us/cpp/build/x64-calling-convention
push rax push rcx push RBX push RBP push RDI push RSI push RSP push R12 push R13 push R14 push R15
; homespace (rcx, rdx, r8, r9) sub rsp, 20h mov rdx, rax ; return value mov rcx, r10 ; return address call InstrumentationCallback add rsp, 20h
pop R15 pop R14 pop R13 pop R12 pop RSP pop RSI pop RDI pop RBP pop RBX pop rcx pop rax
jmp R10 InstrumentationCallbackShim ENDP END
|
其他问题
根据资料和调试分析得知几个注意的问题:
- Windows 10 之前的系统必须提升
Debug
特权, 才能正常调用 NtSetInformationProcess()
- Windows 10 之前的系统只支持传入
PROCESS_INSTRUMENTATION_CALLBACK_INFORMATION
例子代码
MiroKaku/InstrumentationCallback
引用参考
Hooking via InstrumentationCallback
Windows x64 system service hooks and advanced debugging