0x0 前言
看本文之前建议先看一下《剖析InfinityHook原理 掀起一场更激烈的攻与防恶战》这篇文章。
了解一下InfinityHook的基本原理。
0xI InfinityHook寻找指针方式解析
我们先来看看原作者是如何寻找的,在源码的IfhpInternalGetCpuClock函数内
1 2 | PVOID * StackMax = (PVOID * )__readgsqword(OFFSET_KPCR_RSP_BASE);
PVOID * StackFrame = (PVOID * )_AddressOfReturnAddress();
|
StackFrame为栈顶,StackMax为栈底,
获取两个值是为了确定范围,要在这两个值范围之间寻找在堆栈中那个要替换的指针。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | for (PVOID * StackCurrent = StackMax;
StackCurrent > StackFrame;
- - StackCurrent)
{
/ /
/ / This is intentionally being read as 4 - byte magic on an 8
/ / byte aligned boundary.
/ /
PULONG AsUlong = (PULONG)StackCurrent;
if ( * AsUlong ! = INFINITYHOOK_MAGIC_1)
{
continue ;
}
/ /
/ / If the first magic is set , check for the second magic.
/ /
- - StackCurrent;
PUSHORT AsShort = (PUSHORT)StackCurrent;
if ( * AsShort ! = INFINITYHOOK_MAGIC_2)
{
continue ;
}
|
将StackMax栈底给StackCurrent再给AsUlong,用AsUlong地址里的值与INFINITYHOOK_MAGIC_1相比较,
不相等就回到for循环减一,相等就减1继续与INFINITYHOOK_MAGIC_2比较,不相等就回到for减一循环,
直到找到两个值,也就是说,从栈底往上一个个的比对值是否为INFINITYHOOK_MAGIC,直到找到两个值才继续。这两个值分别为 0x501802 和0xF33,这里记下两个值后面有提及。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | for (;StackCurrent < StackMax; + + StackCurrent)
{
PULONGLONG AsUlonglong = (PULONGLONG)StackCurrent;
if (!(PAGE_ALIGN( * AsUlonglong) > = SystemCallEntryPage &&
PAGE_ALIGN( * AsUlonglong) < (PVOID)((uintptr_t)SystemCallEntryPage + (PAGE_SIZE * 2 ))))
{
continue ;
}
void * * SystemCallFunction = &StackCurrent[ 9 ];
if (IfhpCallback)
{
IfhpCallback(SystemCallIndex, SystemCallFunction);
}
break ;
}
|
找到两个值的位置后,再从这个位置向下,寻找系统调用,找到后,以这个系统调用的位置为基准,
往后找9个,每个8字节,也就是偏移72的位置的值,这样这个函数指针就找到了。
0x2逆向HOOK系统调用事件
是不是看的一脸懵逼,没错我也是,为什么是那两个特定值,那两个值是怎么来的。
那篇文章没写 ,源码的注释也没说,那换个其他事件这个寻找方式还能用吗?
不能,我试过了。
这里就要理一理思路了,这两个值既然在堆栈中,那么不是当前函数搞进去的,就是前一个函数,
或者前前的函数.........., 那么这个函数就应该在调用栈上,那我们就下个断点,在windbg中看一下调用栈。
从调用栈分析, ntdll!NtCreateEvent+0xa进入系统调用,然后在nt!KiSystemServiceExit+0x26a进入etw的记录函数,那么关键就应该是PerfInfoLogSysCallEntry函数,通过函数头部的二进制,在ida里搜索
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | .text: 0000000140162A40 mov r11, rsp
.text: 0000000140162A43 sub rsp, 48h
.text: 0000000140162A47 lea rax, [r11 + 8 ]
.text: 0000000140162A4B mov [r11 + 8 ], rcx
.text: 0000000140162A4F lea rcx, [r11 - 18h ]
.text: 0000000140162A53 mov [r11 - 18h ], rax
.text: 0000000140162A57 and [rsp + 48h + var_C], 0
.text: 0000000140162A5C mov r9d, 0F33h
.text: 0000000140162A62 mov edx, 1
.text: 0000000140162A67 mov r8d, 40000040h
.text: 0000000140162A6D mov [rsp + 48h + var_20], 501802h
.text: 0000000140162A75 and qword ptr [r11 - 28h ], 0
.text: 0000000140162A7A mov [rsp + 48h + var_10], 8
.text: 0000000140162A82 call EtwpTraceKernelEvent
.text: 0000000140162A87 add rsp, 48h
.text: 0000000140162A8B retn
|
PerfInfoLogSysCallEntry很短,看完就能发现两个熟悉的数字,
在0000000140162A5C 的 mov r9d, 0F33h
与0000000140162A6D mov [rsp+48h+var_20], 501802h
0F33与501802h不刚好是在堆栈里搜索的两个数,
这里又要理一理思路了,还记得前面说的吗,找到这两个值后,向下寻找系统调用,
我们都知道,堆栈是先push的在底下,后push的在上面,先下寻找意味着寻找之前的调用函数,
看调用栈之前的函数那就是KiSystemServiceExit。
1 2 | void * * SystemCallFunction = &StackCurrent[ 9 ];
DbgBreakPoint();
|
写个断点,断下后查看StackCurrent,这个基准系统调用的值
1 2 3 4 5 6 7 8 9 | kd> dq 0xfffff880 ` 03b82b88
fffff880` 03b82b88 fffff800` 03ee0205 fffff960` 00134164
fffff880` 03b82b98 00000000 ` 00000000 00000000 ` 0000002a
fffff880` 03b82ba8 fffff960` 00133fd6 00000000 ` 00000003
fffff880` 03b82bb8 00000000 ` 00000000 00000000 ` 0192efb0
fffff880` 03b82bc8 00000000 ` 00000005 fffff960` 00134164
fffff880` 03b82bd8 fffff800` 03edff93 fffffa80` 31d69660
fffff880` 03b82be8 fffffa80` 31d63910 00000000 ` 00000000
fffff880` 03b82bf8 fffffa80` 31d63910 00000000 ` 00000000
|
StackCurrent为0xfffff880`03b82b88,dq查看存的是 fffff800 03ee0205
1 2 3 4 5 6 7 8 9 10 | kd> u fffff800` 03ee0205
nt!KiSystemServiceExit + 0x26a :
fffff800` 03ee0205 488b4c2420 mov rcx,qword ptr [rsp + 20h ]
fffff800` 03ee020a 488b542428 mov rdx,qword ptr [rsp + 28h ]
fffff800` 03ee020f 4c8b442430 mov r8,qword ptr [rsp + 30h ]
fffff800` 03ee0214 4c8b4c2438 mov r9,qword ptr [rsp + 38h ]
fffff800` 03ee0219 4c8b542440 mov r10,qword ptr [rsp + 40h ]
fffff800` 03ee021e 4883c450 add rsp, 50h
fffff800` 03ee0222 41ffd2 call r10
fffff800` 03ee0225 488945b0 mov qword ptr [rbp - 50h ],rax
|
u看下汇编,正好是KiSystemServiceExit,正如前面猜想的寻找KiSystemServiceExit
1 2 3 4 5 6 7 8 9 | kd> dq 0xfffff880 ` 03b82b88
fffff880` 03b82b88 fffff800` 03ee0205 fffff960` 00134164
fffff880` 03b82b98 00000000 ` 00000000 00000000 ` 0000002a
fffff880` 03b82ba8 fffff960` 00133fd6 00000000 ` 00000003
fffff880` 03b82bb8 00000000 ` 00000000 00000000 ` 0192efb0
fffff880` 03b82bc8 00000000 ` 00000005 fffff960` 00134164
fffff880` 03b82bd8 fffff800` 03edff93 fffffa80` 31d69660
fffff880` 03b82be8 fffffa80` 31d63910 00000000 ` 00000000
fffff880` 03b82bf8 fffffa80` 31d63910 00000000 ` 00000000
|
以这个基准偏移9个(不是9个字节,偏移一个指8字节,也就是偏移72的地方)的地方,为地址ffff880`03b82bd0,这地址里的值为fffff960 00134164,也就是要替换的函数指针。
1 2 3 4 5 6 7 8 9 10 | kd> u fffff960` 00134164
win32k!NtUserCallNoParam:
fffff960` 00134164 48895c2408 mov qword ptr [rsp + 8 ],rbx
fffff960` 00134169 57 push rdi
fffff960` 0013416a 4883ec20 sub rsp, 20h
fffff960` 0013416e 8bf9 mov edi,ecx
fffff960` 00134170 488b0da1a72000 mov rcx,qword ptr [win32k!gpresUser (fffff960` 0033e918 )]
fffff960` 00134177 ff15f3d21c00 call qword ptr [win32k!_imp_ExEnterPriorityRegionAndAcquireResourceExclusive (fffff960` 00301470 )]
fffff960` 0013417d c605249e200001 mov byte ptr [win32k!gbValidateHandleForIL (fffff960` 0033dfa8 )], 1
fffff960` 00134184 48890565c32000 mov qword ptr [win32k!gptiCurrent (fffff960` 003404f0 )],rax
|
u看一下是什么函数,为win32k!NtUserCallNoParam:,这个地址为UserCallNoParam的开头
1 2 3 4 5 6 7 | MyDriver2!IfhpInternalGetCpuClock + 0x15e [c:\users\hasee\documents\visual studio 2013 \projects\mydriver1\mydriver1\infinityhook.cpp @ 622 ]
nt!EtwpReserveTraceBuffer + 0xe2
nt!EtwpLogKernelEvent + 0x24d
nt!EtwpTraceKernelEvent + 0xa6
nt!PerfInfoLogSysCallEntry + 0x47
nt!KiSystemServiceExit + 0x26a
user32!NtUserCallNoParam + 0xa
|
调用栈的发起者为user32!NtUserCallNoParam+0xa,
我们都知道调用一个函数,都是push 参数1,push参数 2,push 参数3,然后call ,,
这时候堆栈里上面是返回地址,往下是参数,他以返回地址为基准向下加偏移寻找要替换的函数指针,
那会不会他寻找的就是参数,
1 2 | : Args to Child : Call Site
fffff880` 03b82b40 : fffff960` 00134164 : nt!PerfInfoLogSysCallEntry + 0x47
|
windbg查看调用栈显示参数,
为了美观省略了后面的参数,参数为fffff960`00134164,这个不正好和寻找的那个一样
1 2 3 4 5 6 7 | kd> dq fffff880` 03b82b40
fffff880` 03b82b40 fffffa80` 31d69660 fffff880` 03b82c60
fffff880` 03b82b50 00000000 ` 0192f250 00000000 ` 00000001
fffff880` 03b82b60 00000000 ` 00000000 fffff800` 00501802
fffff880` 03b82b70 fffff880` 03b82b90 00000000 ` 00000008
fffff880` 03b82b80 fffff880` 03b82b88 fffff800` 03ee0205
fffff880` 03b82b90 fffff960` 00134164 00000000 ` 00000000
|
查看这个函数的堆栈,在fffff880 03b82b88是我们寻找的基准系统调用的位置
1 2 3 4 5 6 7 8 9 | kd> dq 0xfffff880 ` 03b82b88
fffff880` 03b82b88 fffff800` 03ee0205 fffff960` 00134164
fffff880` 03b82b98 00000000 ` 00000000 00000000 ` 0000002a
fffff880` 03b82ba8 fffff960` 00133fd6 00000000 ` 00000003
fffff880` 03b82bb8 00000000 ` 00000000 00000000 ` 0192efb0
fffff880` 03b82bc8 00000000 ` 00000005 fffff960` 00134164
fffff880` 03b82bd8 fffff800` 03edff93 fffffa80` 31d69660
fffff880` 03b82be8 fffffa80` 31d63910 00000000 ` 00000000
fffff880` 03b82bf8 fffffa80` 31d63910 00000000 ` 00000000
|
回过头来再以这个基准系统调用的位置,要替换的函数指针值为fffff960`00134164,
一处为偏移1的(偏移1不是指偏移一个字节,是8个字节)fffff880`03b82b90,
宁一处为偏移9的fffff880`03b82bd0,原作者替换的是偏移9处的函数指针。
总结一下:
作者通过寻找EtwPerfInfoLogSysCallEntry放入堆栈的特定值来寻找,
EtwPerfInfoLogSysCallEntry函数的堆栈位置,
再向下寻找系统调用找到
KiSystemServiceExit函数返回地址在堆栈中位置,
再通过以KiSystemServiceExit为基准,替换偏移9处的参数指针,达到hook的目的。
0x3 逆向其他事件
1 特定值的寻找
1 2 3 | EVENT_TRACE_FLAG_PROCESS
0x00000001
Property - >EnableFlags = 0x00000001 ;
|
通过官网查询PROCESS事件的falg为1,修改这个值就行了,
1 2 3 4 5 6 7 8 9 | nt!EtwpReserveTraceBuffer + 0xe2
nt!EtwpLogKernelEvent + 0x122
nt!EtwpTraceKernelEvent + 0xa6
nt! ?? ::NNGAKEGL::`string' + 0x219f0
nt!PspExitProcess + 0x4e
nt!PspExitThread + 0x4e9
nt!NtTerminateProcess + 0x138
nt!KiSystemServiceCopyEnd + 0x13
ntdll!NtTerminateProcess + 0xa
|
函数虽然变了,但在调用栈的大概位置没变,查看nt! ?? ::NNGAKEGL::`string'+0x21a03
1 2 3 4 5 6 7 8 | fffff800` 0412de2b 8b5504 mov edx,dword ptr [rbp + 4 ]
fffff800` 0412de2e 488d8d90000000 lea rcx,[rbp + 90h ]
fffff800` 0412de35 450fb7cd movzx r9d,r13w
fffff800` 0412de39 41b801000000 mov r8d, 1
fffff800` 0412de3f c744242803195000 mov dword ptr [rsp + 28h ], 501903h
fffff800` 0412de47 488364242000 and qword ptr [rsp + 20h ], 0
fffff800` 0412de4d e8bedae8ff call nt!EtwpTraceKernelEvent (fffff800` 03fbb910 )
fffff800` 0412de52 488d4d20 lea rcx,[rbp + 20h ]
|
可以在fffff800`0412de3f看见501903h,宁一个特定值不好从代码看,
我们在 mov dword ptr [rsp+28h],501903h在这段系统代码下个断点
断下后记下rsp+28的值,等断在我们的代码后再dq查看内存偏移1处的后三位,经过多次测试,
第二个值为8d0 。
2寻找地址猜想
1 2 3 4 5 6 7 8 9 | nt!EtwpReserveTraceBuffer + 0xe2
nt!EtwpLogKernelEvent + 0x122
nt!EtwpTraceKernelEvent + 0xa6
nt! ?? ::NNGAKEGL::`string' + 0x219f0
nt!PspExitProcess + 0x4e
nt!PspExitThread + 0x4e9
nt!NtTerminateProcess + 0x138
nt!KiSystemServiceCopyEnd + 0x13
ntdll!NtTerminateProcess + 0xa
|
从调用栈看,函数指针应该是PspExitProcess的地址,但我找了半天,u查看没找到PspExitProcess函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | nt!PspExitProcess:
fffff800` 04191288 48895c2408 mov qword ptr [rsp + 8 ],rbx
fffff800` 0419128d 48896c2410 mov qword ptr [rsp + 10h ],rbp
fffff800` 04191292 4889742418 mov qword ptr [rsp + 18h ],rsi
fffff800` 04191297 57 push rdi
fffff800` 04191298 4154 push r12
fffff800` 0419129a 4155 push r13
fffff800` 0419129c 4156 push r14
fffff800` 0419129e 4157 push r15
fffff800` 041912a0 4883ec30 sub rsp, 30h
fffff800` 041912a4 488bda mov rbx,rdx
fffff800` 041912a7 448af9 mov r15b,cl
fffff800` 041912aa f0838a4004000004 lock or dword ptr [rdx + 440h ], 4
fffff800` 041912b2 65488b3c2588010000 mov rdi,qword ptr gs:[ 188h ]
fffff800` 041912bb 4533ed xor r13d,r13d
fffff800` 041912be 41be01000000 mov r14d, 1
fffff800` 041912c4 413acd cmp cl,r13b
fffff800` 041912c7 747c je nt!PspExitProcess + 0xbd (fffff800` 04191345 )
fffff800` 041912c9 ba02030000 mov edx, 302h
fffff800` 041912ce 488bcb mov rcx,rbx
fffff800` 041912d1 e842d6feff call nt!EtwTraceProcess (fffff800` 0417e918 )
fffff800` 041912d6 66ff8fc4010000 dec word ptr [rdi + 1C4h ]
|
看了一下PspExitProcess,在fffff800`041912d1 call nt!EtwTraceProcess,这里距离PspExitProcess开头非常近,基本上保存了参数,经过应该一个判断,就进入了etw。
而系统调用的事件,进入etw是在函数快结束的位置,返回就继续返回就结束了。
据此猜想,process事件的要替换的函数指针应该是PspExitProcess里call进etw时保存函数返回地址,
而不是想系统调用一样PspExitProcess的一个新开始地址指针。
如果是新的会有重入问题,而且既然在头部进入etw记录,
返回就继续执行就行了,没必要那么做,当然这不是百分之百的确定,要把PspExitProcess逆一遍,看看返回后是否真的在继续执行PspExitProcess,或者经过一个判断直接跳到末尾然后返回(当然在ida里看跳转图,不能一次到末尾),如果是返回继续执行,那么替换函数返回地址,自己构造一个fakePspExitProcess函数,应该也是可行的。
0x4结束
以上均为个人的逆向分析,如果有错误或是不同观点,欢迎指出。
阿里云助力开发者!2核2G 3M带宽不限流量!6.18限时价,开
发者可享99元/年,续费同价!