[Windows] 系统调用(R3 进入 R0)

背景

一个线程由用户态进入内核态的途径有3种典型的方式:

  • 通过 int 0x2e(软中断自陷) 或 KiFastSystemCall (快速系统调用),主动进入内核。
  • 引发异常或硬件中断,被迫进入内核。

一般 R3 的 API 最终都会去调用 NT 函数,在 NT 函数中根据该 API 对应的索引号去 SSDT / SSDT Shadow 中查找对应的方法,最后通过 SSDT / SSDT Shadow 进入内核。

通过 int 0x2e 进入内核(xp以下)

在 xp 以下,SSDT / SSDT Shadow 进入内核的指令是 int 0x2e,该指令是一条自陷指令(也叫中断门),之后会将用户线程栈切换成内核线程栈,保存 CONTEXT,到 IDT 中寻找 0x2e 对应的异常处理函数(KiSystemService),至此进入内核代码空间。
在这里插入图片描述

// 这个函数用来保存寄存器现场和其他状态信息
SaveTrap()  
{
    
    
	Push 0   									// LastError
	Push ebp
	Push ebx
	Push esi
	Push edi
	Push fs   									// 此时的 fs 若是从用户空间自陷进来的就指着 TEB,反之指着 kpcr
	Push kpcr.ExceptionList
	Push kthread.PreviousMode
	Sub esp,0x48 								// 腾给调式寄存器保存用
	-----------至此,上面的这些语句连同int 2e中的语句在栈上构造了一个trap帧-----------------
	Mov CurTrapFrame,esp  						// 当前Trap帧的地址
	Mov CurTrapFrame.edx, kthread.TrapFrame 	// 将上次的trap帧地址记录到edx成员中
	Mov kthread.TrapFrame, CurTrapFrame, 		// 修改本线程当前trap帧的地址
	
	Mov kthread.PreviousMode,GetMode(进入内核前的CS)  // 根据CS自动确定上次模式
	Mov kpcr.ExceptionList,-1  						 // 表示刚进入内核时,尚未安装seh
	
	Mov fs,kpcr   									 // 一进入内核就让fs改指向当前cpu的描述符kpcr,不再指向TEB
	
	
	If(当前线程处于调试状态)
	   保存DR0-DR7到trap帧中
}

// 这个函数用来查表,拷贝参数,调用系统服务
FindTableCall() 
{
    
    
	Mov edi,eax  							// 系统函数号,低12位为索引,第13为表示是哪张系统服务表中的索引
	Mov eax, edi.12// eax=真正的服务号
	If(edi.13=1)  						// if这是shadow SSDT中的系统函数号
	{
    
    
	   If(当前线程.服务描述符表!=shadow)
	      当前线程.服务描述符表=shadow  		// 换用另外一张描述符表
	}
	服务表描述符=当前线程.服务描述符表[edi.13]
	Mod edi=服务表描述符.base 				// 这个系统服务表的地址
	Mov ebx,[edi+eax*4]  					// 查表获得这个函数的地址
	Mov ecx=服务表描述符.Number[eax]  		// 查表获得的这个系统函数的参数大小
	
	Mov esi,edx   							// esi = 用户空间中的参数地址
	Mov edi,esp  							// esp已经为内核栈的栈顶地址
	Rep movsb  								// 将所有参数从用户空间复制到内核空间,相当于N个连续push压参
	Call  ebx  								// 调用对应的系统服务函数

}

// int 2e的isr,内核服务函数总入口,注意这个函数可以嵌套、递归!!!
KiSystemService()
{
    
    
    SaveTrap();
	Sti  							// 开中断
	---------------上面保存完寄存器等现场后,开始查SSDT表调用系统服务------------------
	FindTableCall();
	---------------------------------调用完系统服务函数后------------------------------
	Move  esp,kthread.TrapFrame; 	// 将栈顶回到 trap 帧结构体处
	Cli  							// 关中断
	If(上次模式==UserMode)
	{
    
    
	Call  KiDeliverApc 				// 遍历执行本线程的内核APC和用户APC队列中的所有APC函数
	清理Trap帧,恢复寄存器现场
	Iret   							// 返回用户空间
	}
	Else
	{
    
    
	   返回到原call处后面的那条指令处
	}

}

总结一下,KiSystemService 大概做了什么:

  • 保存寄存器现场和其他状态信息
  • 查SSDT表调用对应的系统服务
  • 恢复调用栈
  • 判断上次模式,如果是用户模式则执行 APC,之后恢复现场返回 r3 。如果是内核模式,则返回到调用 call KiSystemService 的下一条指令。

通过 KiFastSystemCall 进入内核(xp以上)

快速调用指令(Intel的是sysenter,AMD的是syscall)调用系统服务。

老式的cpu不支持、不提供sysenter指令,只能由int 2e模拟中断方式进入内核,调用系统服务,但是,那种方式有一个明显的缺点,就是速度慢!(如int 2e内部本身要保存5个寄存器的现场,然后还要去IDT中查找isr,这个过程消耗的时间太多),因此x86系列从奔腾2代开始为系统调用专门增设了一条sysenter指令以及相应的寄存器msr。

在这里插入图片描述

Sysenter()
{
    
    
   Mov ss,msr_ss
   Mov esp,msr_esp 				//关键
   Mov cs,msr_cs
   Mov eip,msr_eip 				//关键
}

Sysexit
{
    
    
	Mov cs,msr_cs
	Mov ss,msr_ss
	Mov esp,ecx 				// 换用用户空间中的栈
	Mov eip,edx  				// 这样,就返回用户空间中了,所有系统调用总是先返回到NTDLL.dll中的某个固定位置,最后一路返回到NTDLL中发起系统调用的那个存根函数体内
}

// 快速系统调用总入口
KiFastCallEntry()  
{
    
    
    Mov fs,kpcr 				// 一进入内核,就将fs改指向处理器描述符kpcr
    Mov esp,TSS.ESP 			// 一进入内核,就换用内核栈(每个线程的内核栈地址保存在TSS中)
    Push ds
    Push edx  					// edx为用户空间栈的栈顶地址,保存在这儿,方便以后回到用户空间时恢复
    Push eflags
    Push cs
    Push sysenter指令的后面那条指令的地址 		// 将用户空间中的返回地址保存在这儿
    --------上面的5条push指令模拟中断、异常发生时cpu自动保存的那5个寄存器的现场------------
	Cli 										// 关中断,构造 Trap 现场帧的过程中需要暂时关中断
	Mov eflags,0x2
	SaveTrap();
	Sti  										// 开中断
	---------------上面保存完寄存器等现场后,查SSDT表调用对应系统服务----------------------
	FindTableCall();
	------------------------------------调用完系统服务函数后--------------------------------
	Move  esp,kthread.TrapFrame; 				// 将栈顶回到trap帧结构体处
	Cli  										// 关中断
	…
	Call  KiDeliverApc 							// 遍历执行本线程的内核APC和用户APC队列中的所有APC函数
	…
	清理Trap帧,恢复寄存器现场
	Sti 										// 开中断
	-----------------------------------下面返回用户空间-------------------------------------
	Mov ecx,保存的用户空间栈顶地址
	Mov edx,保存的返回地址,也即sysenter指令的后面那条指令的地址
	sysexit  									// 可以把这条指令理解为一个fastcall调用约定函数
}

总结下 KiFastCallEntry 大概做了什么:

  • 保存现场,并将环境切入内核
  • 查SSDT表调用对应系统服务
  • 恢复调用栈
  • 执行 APC
  • 返回 r3

猜你喜欢

转载自blog.csdn.net/Simon798/article/details/108457870