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

首先总结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 进入内核空间。(只是猜测,各位有兴趣可以自行验证)

时间: 2024-10-12 16:52:11

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

Windows 7突然断电默认进入修复模式解决方法

[关键词]:修复模式 [适用版本]:Windows [问题描述]: Windows虚拟机不正常关机下,再启动系统默认进入恢复模式而无法恢复. [问题分析]: 虚拟化平台上运行的Windows虚拟机,在突然断电(主机奔溃等)后,虚拟化平台上虚拟机在HA作用下,会把虚拟机漂移到另外台正常主机上继续开机运行,但由于Windows默认针对于硬件上运行,所以为了保护系统而默认进入恢复模式. [解决方法]: 进入windows系统,使用管理员打开cmd,运行以下两条命令: 1.bcdedit /set {d

系统调用syscall---用户态切换到内核态的唯一途径

1.应用程序有时需要内核协助完成一些处理,但是应用程序不可能执行内核代码(主要是安全性考虑), 那么,应用程序需要有一种机制告诉内核,它现在需要内核的帮助,这个机制就是系统调用. 2.系统调用的本质是,应用程序主动触发软中断,这个软中断异常立即被系统捕获到(cpu指令产生异常,触发异常处理程序),在异常处理程序中发现产生的异常是128号异常,于是执行这个异常的处理程序,这个程序就是系统调用的处理程序,通过指定不同的软中断号,异常处理程序跳转到对应的系统调用的内核态实现程序中执行,于是内核态代替用

[转载]Windows系统调用架构分析—也谈KiFastCallEntry函数地址的获取

原文地址:点击打开链接 为什么要写这篇文章 1.      因为最近在学习<软件调试>这本书,看到书中的某个调试历程中讲了Windows的系统调用的实现机制,其中讲到了从Ring3跳转到Ring0之后直接进入了KiFastCallEntry这个函数. 2.      碰巧前天又在网上看到了一篇老文章介绍xxx安全卫士对Windows系统调用的Hook,主要就是Hook到这个函数 3.      刚刚做完毕业设计,对使用中断来实现系统调用的方式记忆犹新. 以上原因导致我最近眼前总是出现系统调用这

Windows Socket五种I/O模型——代码全攻略(转)

Winsock 的I/O操作: 1. 两种I/O模式 阻塞模式:执行I/O操作完成前会一直进行等待,不会将控制权交给程序.套接字 默认为阻塞模式.可以通过多线程技术进行处理. 非阻塞模式:执行I/O操作时,Winsock函数会返回并交出控制权.这种模式使用 起来比较复杂,因为函数在没有运行完成就进行返回,会不断地返回 WSAEWOULDBLOCK错误.但功能强大.为了解决这个问题,提出了进行I/O操作的一些I/O模型,下面介绍最常见的三种: Windows Socket五种I/O模型——代码全攻

理解Windows内核模式与用户模式

 1.基础 运行 Windows 的计算机中的处理器有两个不同模式:"用户模式"和"内核模式".根据处理器上运行的代码的类型,处理器在两个模式之间切换.应用程序在用户模式下运行,核心操作系统组件在内核模式下运行.多个驱动程序在内核模式下运行,但某些驱动程序在用户模式下运行. 当启动用户模式的应用程序时,Windows 会为该应用程序创建"进程".进程为应用程序提供专用的"虚拟地址空间"和专用的"句柄表格"

浅析 Linux 系统调用

浅析 Linux 系统调用 用户态.内核态以及中断 具有高执行级别的程序可以执行特权指令 intel X86 CPU 具有4种级别:0 ~ 3 Linux 只用了0和3(0表示内核态,3表示用户态) 特权级的表示:使用 CS 寄存器的低2位 内核态逻辑地址空间:0xc0000000以上 用户态逻辑地址空间:0x00000000 ~ 0xbfffffff 中断是从用户态到内核态的一种方式,即通过系统调用(系统调用是一种特殊的中断) 中断过程寄存器上下文的保存 保存到什么地方?堆栈 保存的内容: 用

线程的3种实现方式--内核级线程, 用户级线程和混合型线程

之前降解过内核线程.轻量级进程.用户线程三种线程概念解惑(线程≠轻量级进程), 但是一直对其中提到的线程的实现模型比较迷惑, 这次就花了点时间怎么学习了一下子 1 线程的3种实现方式 在传统的操作系统中,拥有资源和独立调度的基本单位都是进程.在引入线程的操作系统中,线程是独立调度的基本单位,进程是资源拥有的基本单位.在同一进程中,线程的切换不会引起进程切换.在不同进程中进行线程切换,如从一个进程内的线程切换到另一个进程中的线程时,会引起进程切换 根据操作系统内核是否对线程可感知,可以把线程分为内

Windows系统调用中API从3环到0环(下)

 Windows内核分析索引目录:https://www.cnblogs.com/onetrainee/p/11675224.html Windows系统调用中API从3环到0环(下) 如果对API在三环的部分不了解的,可以查看 Windows系统调用中的API三环部分(依据分析重写ReadProcessMemory函数 上篇:Windows系统调用中API从3环到0环(上) 这篇文章分为上下两篇,其中上篇初步讲解大体轮廓,下篇着重通过实验来探究其内部实现,最终分析两个函数(快速调用与系统中断)

windows安装使用python、环境设置、多python版本的切换、pyserial与多版本python安装、windows命令行下切换目录

1.windows下安装python 官网下载安装即可 2.安装后的环境设置 我的电脑--属性--高级--设置path的地方添加python安装目录,如C:\Python27;C:\Python33 3.多版本的切换三种方法: 1)修改C:\Python27;C:\Python33内python.exe为python2.7.exe.python3.3.exe,即可区别开来 2)path路径中去除不用的,保留要用的 3)切换到想使用版本的python.exe目录下使用python解释器 4.下载好