系统调用计算KiFastCallEntry地址的过程以及由此得到的一种获取KiFastCallEntry地址的方式
SYENTER和SYSEXIT指令首次出现于Pentium II处理器中,其目的是为了提供一个快速(低负载) 的调用操作系统或者执行体过程的机制.SYSENTER用于运行特权级别为3的用户
调用特权级0 的系统内核代码。
WinDbg中反汇编 KiFastSystemCall可以看到SYSENTER指令。
在调用SYSENTER指令前,CPU会通过下面的MSR寄存器,获取ring0的代码段和代码指针,ring0的堆栈段和堆栈指针:
MSR寄存器可以通过指令RDMSR/WRMSR来进行读写。寄存器地址如下表。
当执行SYSENTER,处理器会
1.从IA32_SYSENTER_CS取出值(seg selector)加载到CS中。
2.从IA32_SYSENTER_EIP取出指令指针放到EIP中
3.将IA32_SYSENTER_CS的值加上8,将其结果加载到SS中。
4.从IA32_SYSENTER_ESP取出堆栈指针放到ESP寄存器中
这里不讨论SYSEXIT,关于SYSEXIT执行的主要操作见《SYSENTER和SYSEXIT》
看了上面的流程之后,下面开始手工一步步计算出SYSENTER指令行执行过程。
先计算CS段地址,CS 的Seg Selector在MSR的0x174号地址中存放,Windbg内核态下使用如下命令
lkd>rdmsr 174
msr[174]= 00000000`00000008
即CS=0008H,Seg Selector为16位,结构如下:
Index:(3~15)指向GDT或者LDT中8192个描述符之一。处理器将该Index值乘8加上GDT的基址(GDTR中)或者LDT的基址(LDTR中)就得到在该描述符地址。
TI(Table Indicator)(2):指定是在GDT(TI=0)中还是在LDT中(TI=1)
RPL:(0~1):请求的特权级,该值在0~3之间。0为优先级最高的级别。
由上面可以算出index=1,并且TI=0,所以段描述符保存在GDT(Globlal Descriptor Table )中(保护模式下)。
下面是段描述符的结构:
其中3个base构成的是32位的段基址,对我们来说只要找到基址就行了,其余的结构暂时不讨论。
段描述符是保存在GDT中的,Selector则包含了index指明该段描述符在GDT中的索引。由此可知,我们要找到段基址还需要找到段描述符,一个描述符占用8个字节,我们要找第一个描述符,计算得到描述符的地址为:GDT 基址 +8*1。Windbg执行:
!pcr
lkd> !pcr
KPCR for Processor 0 at 83f7cc00:
Major 1 Minor 1
NtTib.ExceptionList: 8e21e9ac
NtTib.StackBase: 00000000
NtTib.StackLimit: 00000000
NtTib.SubSystemTib: 801e4000
NtTib.Version: 0016ed4b
NtTib.UserPointer: 00000001
NtTib.SelfTib: 7ffde000
SelfPcr: 83f7cc00
Prcb: 83f7cd20
Irql: 00000002
IRR: 00000000
IDR: ffffffff
InterruptMode: 00000000
IDT: 80495400
GDT: 80b95000
TSS: 801e4000
CurrentThread: 8661b8b0
NextThread: 00000000
IdleThread: 83f86280
DpcQueue:
即可得到GDT基址0x80b95000,在计算得到段描述符的地址为0x80b95008 ,WinDbg查看该地址:dq /c 1 80b95008
80b95008 00cf9a00`0000ffff
80b95010 00cf9200`0000ffff
80b95018 00cffa00`0000ffff
80b95020 00cff200`0000ffff
80b95028 80008b1e`400020ab
80b95030 834092f7`cc003748
80b95038 7f40f2fd`e0000fff
80b95040 0000f200`0400ffff
得到段描述符为:0x00cf9a00 0000ffff。由此得到:段基址=0x00000000
然后计算EIP:
EIP放在MSR的176H号地址中,内核态Windbg用如下指令读取MSR:
rdmsr 176
lkd> rdmsr 176
msr[176] =00000000`83e49300
EIP = 0x83e49300
这样就得到了一个逻辑地址:CS:EIP=0x83e49300
Windbg中 反汇编查看改地址:
lkd>u 83e49300
nt!KiFastCallEntry:
83e49300 b923000000 mov ecx,23h
83e49305 6a30 push 30h
83e49307 0fa1 pop fs
83e49309 8ed9 mov ds,cx
83e4930b 8ec1 mov es,cx
83e4930d 648b0d40000000 mov ecx,dword ptr fs:[40h]
83e49314 8b6104 mov esp,dword ptr [ecx+4]
83e49317 6a23 push 23h
得到了函数Nt!KiFastCallEntry,
nt!KiFastCallEntry地址的获取
系统调用过程一定会通过KiFastCallEntry函数,所以无论是对于安全软件还是恶意软件,这个函数都是十分重要的。
而为了监控所有系统调用,就可以通过Hook这个函数来实现。
栈回溯是一个常用的获取KiFastCallEntry地址的方法。根据网上的说法,360就是通过栈回溯获取地址。
360 先Hook住了一个nt!NtSetEvent函数,系统是先调用KiFastCallEntry,再在KiFastCallEntry中在调用 nt!NtSetEvent。因此在刚进入nt!NtSetEvent的时候利用栈回溯,找到当前函数的返回地址,这个地址一定是在上层函数内的,得到这 个地址就以后,就可以根据特征码搜索需要Inline Hook的目标地址了。
栈回溯看起来很简单巧妙,但是内核中对KiFastCallEntry函数进行FPO(帧指针优化),那这种方法就不能使用了。
也可以通过本文所说的利用上面的计算方法和MSR获取KiFastcallEntry地址。
已编译通过的代码如下:
好文章,内容惟妙惟肖.禁止此消息:nolinkok@163.com