如图trapx86所示:
1.首先从ntdll!NtQueryInformationProcess开始,首先保存系统调用号,然后调用nt!KiFastSystemCall。
2.来到nt!KiFastSystemCall,首先保存esp,因为后面执行sysenter指令时,rsp/esp会被覆盖,最后执行sysenter。
此处补上sysenter/sysexit指令的简单说明,详情请参考intel白皮书。
sysenter和sysexit是一对配套指令,是快速在R3和R0之间转换的指令。
sysenter
{
合法性检测
RFLAGS的VM和IF复位
rsp/esp = IA32_SYSENTER_ESP/IA32_SYSENTER_ESP[31:0]
rip/eip = IA32_SYSENTER_EIP/IA32_SYSENTER_EIP[31:0]
CS.Selector = IA32_SYSENTER_CS[15:0] AND 0FFFCh
修正CS,SS,CPL
}
sysexit
{
合法性检测
rsp = rcx/ecx
rip = rdx/edx
CS.Selector = IA32_SYSENTER_CS[15:0] + 32/IA32_SYSENTER_CS[15:0] + 16
修正CS,SS,CPL
}
因为Windows会在处理器初始化进程时,使MSR寄存器IA32_SYSENTER_EIP = nt!KiFastCallEntry。一次sysenter执行后,执行位置会跳转到nt!KiFastCallEntry。
3.进入nt!KiFastCallEntry,我们简略部分细节说一下重点。 KiFastCallEntry是x86 windows的系统调用主分发器(对应x64 KiSystemCall64)。首先调用SSDT[系统索引号]对应的系统服务例程,然后恢复调用sysexit。根据sysexit的机制,下面会跳转到nt!KiFastSystemCallRet。
4.走进nt!KiFastSystemCallRet,省略掉部分细节,我们来到内部的ret指令。
此时的堆栈应该是与ntdll!NtQueryInformationProcess中调用call nt!FastSystemCall时的堆栈相同的。因此nt!KiFastSystemCallRet中的ret指令会返回到
ntdll!NtQueryInformationProcess中的retn 14h处。最后返回到用户领空。
x64
Intel x64下Windows通过syscall实现系统调用。
如图trapx64可知:
1.从ntdll!NtCreateFile开始,首先保存ecx,保存系统调用号,然后调用syscall。值得注意的是,Windows在处理器初始化进程(参见KiInitializeBootStructures)中把MSR寄存器IA32_LSTAR设置为nt!KiSystemCall64。
下面补上syscall/sysret指令的简单说明,详情请参考intel白皮书。
与sysenter/sysexit类似,syscall和sysret是一对配套指令,
是快速在R3和R0之间转换的指令。
syscall
{
合法性检测
rcx = rip
rip = IA32_LSTAR
r11 = RFLAGS
RFLAGS = RFLAGS AND NOT(IA32_FMASK)
CS.Selector = IA32_STAR[47:32] AND 0FFFCh
修正CS,SS,CPL
}
sysret
{
合法性检测
rip = ecx/ecx
根据R11的值对RFLAGS赋值,且RF,VM,部分reserved位复位。
CS.Selector = IA32_STAR[63:48] + 16/IA32_STAR[63:48]
修正CS,SS,CPL
}
2.来到nt!KiSystemCall64。KiSystemCall64是x64 windows中的主系统调用分发函数,其主要责任是保存用户模式上下文,建立内核栈,复制用户模式参数到内核栈,通过eax传过来的索引值确定KiServiceTable(或W32pServiceTable)中的系统调用,最后返回用户模式。