抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

分析环境为 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;

// Since Windows 10
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, // 40
Information, // PROCESS_INSTRUMENTATION_CALLBACK_INFORMATION[EX]
InformationLength); // 8 or 16

接下来我们就要看下怎么构造这个回调, 也就是要看下这个回调能用的参数是什么?

构造回调

要想知道回调的参数是什么, 我们就要去看一下触发点.

我选了比较简单的 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; // return address
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

其他问题

根据资料和调试分析得知几个注意的问题:

  1. Windows 10 之前的系统必须提升 Debug 特权, 才能正常调用 NtSetInformationProcess()
  2. Windows 10 之前的系统只支持传入 PROCESS_INSTRUMENTATION_CALLBACK_INFORMATION

例子代码

MiroKaku/InstrumentationCallback

引用参考

Hooking via InstrumentationCallback
Windows x64 system service hooks and advanced debugging

评论