Int 2e 与 Sysenter区别
参考:张银奎《软件调试》第八章
Int 2e:
Windows将2e号向量专门用作系统调用,在启动早起初始化中断描述表时便注册好了适合的服务例程。因此当NtDll中的NtReadFile发出int 2e指令后,cpu便会通过idt表找到KisystemService函数。因为KiSystemService函数是位于内核空间的,所以cpu在把执行权交给KiSystemService函数前,会做好从用户态换到内核态的各种工作,包括:
1:权限检查 即检查源位置和目标位置所在的代码段权限,核实是否可以转移。
2:准备内核态使用的栈,为了保证内核安全,所有线程在内核态执行时都必须使用位于内核的内核栈(kernel stack),内核栈的大小一般为8KB或者12KB.
KiSystemService会根据服务ID从系统服务分发表(System Service Dispatch Table)中查找到需要的服务函数地址和参数描述,然后将参数从用户态复制到该线程的内核栈中,最后KiSystemService调用内核中真正的NtReadFile()函数,执行读文件操作,操作结束后会返回到KiSystemService(),KISystemService()会将操作结果复制回线程用户态栈,最后通过IRET指令将执行权交回给NtDll.dll中的NtReadFile()函数,继续执行INT 2E后面的那条指令。
快速系统调用(Sysenter)
准备工作:
1,在GDT中建立4个段描述符,分别用来描述供SYSENTER指令进入内核模式时使用的代码段(CS)和栈段(SS),以及分别用来描述供SYSENTER指令进入内核模式时使用的代码段(CS)和栈(SS).这四个描述符在GDT表中的排列应该严格按照以上顺序,这样只要指定一个段描述符的位置便能计算出其他的。
2,设置专门用于系统调用的MSR寄存器。
3,将一小段名为SystemCallStub的代码复制到SharedUserData内存区,该内存区会被映射到每个Win32进程的进程空间中。这样当应用程序每次进入系统调用时,NtDll中的残根(stub)函数便调用这段SystemCallStub代码。SystemCallStub中的内容会因系统硬件的不同而不同。
逆向调用(Ring0 -> Ring3)
首先内核代码使用内核函数KiCallUserMode发起调用。接下来的执行过程与从系统调用返回(KiServiceExit)时类似,不过进入用户态时执行的是NtDll中的KiUserCallbackDispatcher。而后KiUserCallbackDispatcher会调用内核希望调用的内核态函数。当用户态的工作完成后,执行返回动作的函数会执行INT 2B指令,也就是触发一个0x2B异常。这个异常的处理函数是内核态的KiCallbackReturn函数。于是,通过INT 2B异常,CPU便又跳回到内核态继续执行了。
lkd>!idt 2b
Dumping IDT:
2b:8053d070 nt!KiCallbackReturn