内容回顾
在前文讲到,如果CPU支持sysenter/sysexit指令,那么API调用时,会通过sysenter指令进入0环,如果不支持sysenter指令,就会通过中断门进入0环。
默认的中断号:0x2E(IDT表)。
INT 0x2E进Ring0
- 在IDT表中找到0x2E号的门描述符。
- 分析CS/SS/ESP/EIP的来源。
- 分析EIP是什么。
首先来看一下IDT表:
当通过中断门进入0环的时候,对应的就是 804dee00`0008f631,它具体对应哪个门呢?IDT存储了3种门,中断门、任务门、陷阱门。
804dee00`0008f631,通过ee可以得知,它是中断门。(如果不知道请参考前面保护模式相关的文章:中断门)
804dee00`0008f631,0008提供CS。
804dee00`0008f631,最高的2字节加上最低的2字节,拼出来的就是EIP。也就是进入0环后要执行的代码在哪里。
那么,SS和ESP是哪里来的的?是由TSS提供的。(如果不知道请参考前面保护模式相关的文章:TSS)
当通过中断门进入0环,代码是从哪里开始执行的?
当通过中断门进入0环的时候是804dee00`0008f631标红的四个字节(804df631)。
使用指令u,可以对地址进行反汇编:
注意观察,当通过中断门进入0环以后,要执行的函数叫做:KiSystemService,该函数不再是ntdll模块,而是真正的内核模块。
也就是说,通过中断门进入0环的时候,对应的函数就是内核模块中的一个叫做 KiSystemService 的函数。
通过中断门,现在知道了怎么进入0环,替换哪些寄存器,并且进入0环以后从哪个函数开始执行。
Sysenter进Ring0
在执行sysenter指令之前,操作系统必须指定0环的CS段、SS段、EIP以及ESP。
MSR | 地址 |
---|---|
IA32_SYSENTER_CS | 174H |
IA32_SYSENTER_ESP | 175H |
IA32_SYSENTER_EIP | 176H |
也就是有3个值已经存储在MSR寄存器中了,但是还差一个SS,而SS并没有存到该寄存器中,而是计算出来的:CS + 8 = SS的段选择子。
可以通过 RDMSR/WRMST 来进行读写(操作系统使用WRMST写该寄存器):
kd> rdmsr 174 //查看CS
kd> rdmsr 175 //查看ESP
kd> rdmsr 176 //查看EIP
参考:Intel白皮书第二卷(搜索sysenter)
查看EIP反汇编可以知道,当通过sysenter进入0环后,执行的函数是KiFastCallEntry()。它也是内核模块的函数。
与中断门进入0环的函数是不一样的。
总结
- API通过中断门进入0环:
- 固定中断号为:0x2E。
- CS、EIP由门描述符提供,ESP、SS由TSS提供。
- 进入0环后执行的内核函数:NT!KiSystemService()。
- API通过sysenter指令进入0环:
- CS、ESP、EIP由MSR寄存器提供(SS是计算出来的:CS + 8 = SS的段选择子)。
- 进入0环后执行的内核函数:NT!KiFastCallEntry()。
- 内核模块:ntoskrnl.exe/ntkrnlpa.exe。
- 内核模块路径:C:\WINDOWS\System32
- 具体使用哪个取决于系统启动时的分页模式,ntoskrnl.exe(10-10-12分页)、ntkrnlpa.exe(2-9-9-12分页)。
练习
- 实现通过中断门直接调用内核函数。
- 通过IDA打开内核模块(具体打开哪个取决于操作系统启动时的分页模式),并找到KiSystemService()、KiFastCallEntry()函数,然后进行分析。
- 进入0环后,原来的寄存器存在哪里?
- 如何根据系统调用号(EAX寄存器中储存)找到要执行的内核函数?
- 调用时的参数是储存到3环的堆栈,如何传递给内核函数?
- 2种调用方式入口(KiSystemService()、KiFastCallEntry())是不一样的,它们是如何返回到3环的?