以NtReadFile调用为例。
一.NtDll.Dll中,NtReadFile过程如下:
ntdll!NtReadFile:
7c92d9b0 b8b7000000 mov eax,0B7h
7c92d9b5 ba0003fe7f mov edx,offset SharedUserData!SystemCallStub (7ffe0300)
7c92d9ba ff12 call dword ptr [edx]
7c92d9bc c22400 ret 24h
二.0x7ffe0300地址存放0x7c92e4f0,反汇编它:
ntdll!KiFastSystemCall:
7c92e4f0 8bd4 mov edx,esp
7c92e4f2 0f34 sysenter
因为SYSENTER执行时,CPU并不会向CALL一样保存返回地址和状态信息,所以需要这样一个“桩”(STUB)段,通过它来保存返回信息。同样的,下面的(五)就是利用这里保存的返回信息返回值NtReadFile中。
这里的KiFastSystemCall和(五)中的SystemCallReturn 都是保存在 _KUSER_SHARED_DATA结构中。用户态下的0X7FFE0000和系统态下的0XFFDF0000同时指向它。结构如下:
阅读全文…
内核编程
SYSENTER
在 x86 下,以前的系统调用都是通过 int 指令来发出,然后跳转到内核中的服务例程。但 int 指令执行时要做一些不必要的安全检查,而且因为系统调用是系统内被调用最频繁的地方,因此采用 int 指令会影响系统的性能。
这时 Intel 和 AMD 就专门设计了系统调用指令来代替以前的 int 指令。
Intel 中采用的指令是 SYSENTER/SYSEXIT , AMD 中采用的指令是 SYSCALL/SYSRETURN.
下面我简单的描述一下 Intel 下的 SYSENTER/SYSEXIT 指令的执行前提和执行过程。
准备工作:
1. 在调用 SYSENTER 指令进行系统调用时,需要在 GDT 中建立四个段,分别用来描述 SYSENTER 指令进入内核模式时使用的代码段 (CS) 和栈段 (SS), 以及 SYSEXIT 指令从内核模式返回用户模式时使用的代码段 (CS) 和栈段 (SS)。这四个描述符在 GDT 表中的排列应该严格按照以上顺序,这样只要指定一个段描述符的位置便能计算出其他的。
2. 设置下表中专门用于系统调用的 MSR 寄存器(使用 WRMSR 指令来设置这些寄存器):
| MSR |
Address |
| IA32_SYSENTER_CS |
174H |
| IA32_SYSENTER_ESP |
175H |
| IA32_SYSENTER_EIP |
176H |
- IA32_SYSENTER_CS 寄存器保存了内核代码段的描述符的 index。
- IA32_SYSENTER_EIP 寄存器保存了内核里的快速系统调用分发例程的线性地址。
- IA32_SYSENTER_ESP 寄存器保存了内核里能获得本地 TSS 描述符的基地址. (只有这样, 进入内核态后才能正确的设定 esp 为正确的值)
sysenter指令执行步骤如下:
- 将IA32_SYSENTER_CS保存到CS中
- 将IA32_SYSENTER_EIP保存到EIP中
- 将IA32_SYSENTER_CS + 8 保存到SS中
- 将IA32_SYSENTER_ESP保存到ESP寄存器中
- 切换到ring 0级别
- 如果EFLAGS中的VM标志被设定,那么清0该标志
- 开始执行ring0代码
下面再说说 sysexit
这条指令执行前需要如下准备工作:
- 设置 EDX 为 ring3 下要执行的指令的首地址
- 设置 ECX 为 ring3 下的栈指针
sysexit指令的执行步骤如下:
- 将IA32_SYSENTER_CS + 16保存到CS中。(ring3下代码段)
- 将EDX赋值给EIP
- 将IA32_SYSENTER_CS + 24保存到SS中
- 将ECX赋值给ESP
- 切换到ring3下继续执行ring3代码
参考:
《软件调试》 张银奎 8.3.3节
ia32 intel architecture software developer manuals 指令卷2 sysenter/sysexit部分
内核编程
SYSENTER, SYSEXIT
近期评论