参考
前言
Windows-x64体系下,进入0环系统调用由原来的sysenter变成了syscall。来支持x64平台。
与sysenter一样,syscall同样依赖于MSR寄存器。在进行零环进入时,也是需要从MSR寄存器中找到CS SS RIP等等。与sysenter稍微不同的是,sysenter从MSR找到EIP是KiFastCallEntry,而syscall这是KiSystemCall64.
但是归根结底,他们的共同作用只有一个,无非就是填KTRAP_FRAME,根据rax(rax)找到系统服务。然后sysret退出0环。
分析
Syscall指令
我们知道,系统调用大多是3环程序执行,在ntdll中保存了系统调用的存根和syscall指令。因此作为3->0环的网关,我们首先要知道,syscall这条指令干了什么。
简而言之,就是他干了这几件事
从MSP寄存器找到新RIP,切换,并将RIP保存在RCX。
从MSR寄存器找到新EFLAGS,将旧EFLAGS保存在R11
CS,SS均也是从MSR寄存器找的,SS恒等于CS+8;
syscall不会帮你保存堆栈,因此我们需要OS专门为我们保存。(KTRAP_FRAME)
第一步-KiSystemCall保存3环环境
我们说过,Syscall不会给你保存寄存器环境,这需要手动保存。刚进入KiSystemCall64,我们就看到了切换0环栈,把寄存器保存到KTRAP_FRAME中。
这里有两点需要注意的,第一是划红线的地方,由于syscall会把rcx填充成三环RIP,因此我们也需要保存RCX,在ntdll中。
我们看到,他把rcx保存在了r10中,因此填充KTRAP_FRAME的时候,他先保存了rip,使用rcx。在赋值,保存旧的rcx。
第二点需要注意的就是,他会检测是不是0环调用,如果不是,三环调用的话,由于有SMAP(内核模式访问保护),我们是无法访问3环栈,也就是无法复制参数的。因此这个地方他进行了一些检查。
第二步-防止硬件漏洞
这是和硬件相关的,在周壑的视频中有所涉及。和CFG控制流保护相关。
最后都会执行到同一个地方
第三步-检查是否被调试
在x86系统调用学习时我们知道,会检测线程是否被调试,有选择地保存调试寄存器。
第四步-根据eax,找到SSDT或SSSDT
首先,会判断eax,也就是服务号的12位。(决定了是否是GDI系统服务)
这个就绝对了找到的是SST 还是SSST。
第五步-找到系统服务和参数个数
这里,根据x64系统服务表的特性找到了系统服务的地址以及参数。x64的SST和x86的SST有很大不同。
x64SST和x86SST的区别
首先,无论是SST和SSST,只要第一个参数有用。也就是STB,这个STB+STB[系统服务号]>>4=就等于系统服务的地址。
而右移的这四位,就是参数的个数。
用图表达则是:
第六步-复制参数
我们知道,现在eax的低4位保存的就是参数个数-4;
而在Windows系统调用复制参数的那块地方,我们可以看到,他复制一个,要用八个字节。
我们可以看到KiSystemServiceCopyEnd这个函数的神奇构造。在他的上面紧接着就是参数复制。
因此and eax,0F;然后>>4。减去KiSystemServiceCopyEnd,就可以复制参数了!
第七步-转到系统调用
我们知道,刚刚r10经过计算,已经是系统调用的地址了。
第八步-执行用户APC
他会进行循环判断,如果有用户APC正在等待,那么就执行。
这个地方我还没有完全搞明白,我不明白为什么参考资料上面说的只有特殊的内核APC才会在APC_LEVEL上被执行。而这个明明是执行用户APC,却用了APC_LEVEL,猜测可能是让用户APC尽快执行。
第九步-Sysret返回
Sysret你可以理解为Syscall的逆向指令,就像push和pop一样。因此,我们把寄存器恢复到和刚进入Syscall(RCX==原来的RIP)的时候,即可成功返回0环了。