码迷,mamicode.com
首页 > Windows程序 > 详细

浅析Windows系统调用——2种切换到内核模式的方法

时间:2015-11-09 01:46:48      阅读:872      评论:0      收藏:0      [点我收藏+]

标签:内核   windows   寄存器   汇编指令   sysenter   



首先总结2种切换到内核模式方法的各自流程:


内存法(中断法):

(用户模式)WriteFile() -> ntdll!NtWriteFile() -> ntdll!KiIntSystemCall() -> int 2Eh -> 查找IDT的内存地址,偏移0x2E处 ->(内核模式)nt!KiSystemService() 

-> nt!KiFastCallEntry() -> nt!NtWriteFile()

通过0x2E中断转移控制到内核模式后,系统服务分发/调度器为 nt!KiFastCallEntry(),它负责调用内核空间中的同名异前缀函数 nt!NtWriteFile(),后者有一个系统服务号;也叫做分发 ID,该 ID 需要在执行 int 2Eh 前,加载到EAX 寄存器,以便通知 nt!KiSystemService()要它分发的系统调用(本机API),但是最终还是经由 nt!KiFastCallEntry() 来分发



MSR寄存器法(快速法):

(用户模式)WriteFile() -> ntdll!NtWriteFile() ->  ntdll!KiFastSystemCall() -> 分别设置 IA32_SYSENTER_CS 寄存器的值为 Ring0 权限代码段描述符对应的段选择符;设置 IA32_SYSENTER_ESP 寄存器的值为 Ring0 权限的内核模式栈地址;设置 IA32_SYSENTER_EIP 寄存器指向 nt!KiFastCallEntry() 的起始地址 ->

SYSENTER ->(内核模式)nt!KiFastCallEntry() ->  nt!NtWriteFile()

通过 SYSENTER 转移控制到内核模式后,系统服务分发/调度器为 nt!KiFastCallEntry() ,它负责调用内核空间中的同名异前缀函数 nt!NtWriteFile()

SYSENTER指令隐含了6步操作:

1.从 IA32_SYSENTER_CS 取出段选择符加载到 CS 中。

2.从 IA32_SYSENTER_EIP 取出指令指针放到 EIP 中

3.将 IA32_SYSENTER_CS 的值加上8,将其结果加载到 SS 中。(也就是将Ring0权限代码段选择符+8,来计算 Ring0 权限的内核模式堆栈段地址对应的段描述符)

4.从 IA32_SYSENTER_ESP 取出堆栈指针放到 ESP 寄存器中

5. 从 EIP 指向的地址处取指令,从而真正进入内核模式

6.若 EFLAGS 中 VM 标志已被置,则清除 VM 标志。

寄存器法看似比内存法多了很多步骤,尤其是 SYSENTER 指令的前置准备工作与隐含的内部操作,但是所有这些加起来,与访问内存中的 IDT 并取回数据相比,仍然快了数十至数百个处理器时钟周期。另外,中断法在进入内核模式后还要多一次对 nt!KiSystemService() 的调用,因此增加了性能开销。

ntdll!Nt* 为 nt!Nt* 系统调用的用户模式代理,前者在其中一个叫做SytemCallStub 的变量中保存 ntdll!KiFastSystemCall() 的地址(后面会验证);

ntdll!KiFastSystemCall() 中的 SYSENTER 指令负责实际从Ring3 到 Ring0 的转移,即进入内核模式。

在 Intel Pentium II 或 Windows XP 以前,系统调用只能通过 INT 2Eh 中断切换到内核模式,并且 nt!KiSystemService() 作为实际的系统服务分发/调度器。

在这之后,无论使用 INT 2Eh 或 SYSENTER,实际的系统服务分发/调度器都是 nt!KiFastCallEntry(),如前所述,这就没有必要使用 INT 2Eh 来多执行一次nt!KiSystemService()。


下面结合用户模式调试与内核模式调试来验证上述内容,首先用 WinDbg 打开 calc.exe (Windows 计算器)或其它任意可执行 PE 文件,在底部的命令行输入 

u ntdll!KiIntSystemCall,反汇编这个函数,可以看到其 77c071c4 地址处的2字节机器指令序列,int     2Eh :

技术分享


在WinDbg菜单中选择停止调试,然后退出程序,再次用 LiveKD.exe打开 WinDbg,这将直接调试内核,执行  !idt 2e 命令,获取处理int     2Eh 的 ISR,可以看到,这个8字节的门描述符最终指向的就是 nt!KiSystemService() 的地址 842447fe;注意,线性地址7FFFFFFF是用户与内核空间的分水岭,往上80000000属于内核空间:

技术分享


执行 u 842447fe L25 命令,反汇编nt!KiSystemService() 的前25行,发现其最终跳转到了nt!KiFastCallEntry+0x8f 偏移处(8424495f地址处):

技术分享


使用KD.EXE 也可以验证:

技术分享



技术分享

技术分享


由此证实了通过中断进行系统调用的流程。但是,在calc.exe进程中,究竟是选择中断法还是MSR寄存器法,还需要加以验证。为此,再次以 WinDbg 打开 calc.exe,按照前面的流程,先执行 u ntdll!NtOpenFile 命令,因为 OpenFile() 是任何一个应用使用机率最大的 Windows API 之一,它将导致调用用户模式代理:ntdll!NtOpenFile() ,因此我们选择后者来反汇编:

技术分享

可以看到在上图的 A 处,将地址 7ffe0300 处的 ShareUserData!SystemCallStub(系统调用存根)复制到 EDX 寄存器中,然后使用带有存储器寻址格式操作数的汇编指令 call dword ptr [edx],也就是调用这个存根保存的函数地址,换言之,我们下一步要转储地址 7ffe0300 保存的内容,看看是什么函数的地址。输入指令 dd 7ffe0300:

技术分享从上图得知, 7ffe0300 地址处开始的 4 字节16 进制数为 77c071b0,换言之,前面的 call dword ptr [edx] 指令等价于 call 77c071b0,于是我们继续反汇编这个地址。输入指令 u 77c071b0:

技术分享从上图得知,77c071b0 是 ntdll!KiFastSystemCall() 的起始地址,换言之,系统调用存根就保存了指向这个地址的指针(7ffe0300);ntdll!KiFastSystemCall() 的内容为只有4字节的机器指令,其中第2条的2字节指令 0f34 ,也就是 Intel Pentium II 处理器以后新增的 SYSENTER 指令,它将程序对 CPU 的控制权转移到 Ring0 特权的代码,也就是切换到内核模式。

如前所述,SYSENTER 指令隐含的6步中最为关键的就是从 IA32_SYSENTER_EIP 寄存器取出指令指针放到EIP中,而 IA32_SYSENTER_EIP 寄存器保存的即是 nt!KiFastCallEntry() 的起始地址。(通过内核调试器命令 rdmsr 0x176 可以获取该地址,这3个寄存器的地址如下图所示)

技术分享


这样就跳转到了nt!KiFastCallEntry(),它将调度内核空间中的同名函数 nt!NtOpenFile(),实际执行用户应用请求的操作。下面这个图对寄存器法的整个过程进行了总结:

技术分享


最后,把注意力放回上面那张反汇编 ntdll!KiFastSystemCall() 的图,细心的你或许已经发现, ntdll!KiFastSystemCall() 的内存地址后面不远处,就是 ntdll!KiIntSystemCall() 的起始地址,既然 calc.exe 进程的用户空间中存在2条进入内核空间的途径,或许意味着程序中有一个类似 CMP..... JE/JGE 的汇编判断逻辑,用于向前兼容不支持 SYSENTER 指令的旧型 Intel 处理器使用 INT 2Eh 进入内核空间。(只是猜测,各位有兴趣可以自行验证)  



本文出自 “自由,平等,共享,互助” 博客,请务必保留此出处http://shayi1983.blog.51cto.com/4681835/1710861

浅析Windows系统调用——2种切换到内核模式的方法

标签:内核   windows   寄存器   汇编指令   sysenter   

原文地址:http://shayi1983.blog.51cto.com/4681835/1710861

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!