在你要理解Zw和Nt 系列API时,你首先要知道一点,那就是系统调用。什么是系统调用呢?
系统调用:操作系统为应用程序提供的可被调用的一组函数,也叫“System Call”。
从软件角度来看,这些系统调用都是操作系统为软件提供的服务,所以也称“系统服务”,也可以说两个名词是同名词。
系统调用所提供的服务都是在内核中实现的,而软件是在用户空间运行的,其实两者就是CPU的运行模式不同而已,所以当你调用系统所提供的函数时,CPU就要从Ring3层转为Ring0层。从Ring3 进入Ring0 有三种方式:
① 中断 ②异常 ③自陷
这里说的系统调用就是通过自陷的方式,从Ring3进入Ring0的。(如果想知道自陷具体细节,可以去看毛德操的windows内核情景分析)
简单的说完系统调用后,就可以开始API的理解了。
API分装在应用层的某个DLL库中(例如kernel.dll和user32.dll)文件中。而DLL动态库中的更低层函数包含在ntdll.dll文件中,也就是调用ntdll.dll中的NativeAPI函数。
ntdll.dll中的Native API函数是成对出现的,分别以"Nt" "Zw"开头,在ntdll.dll中,它们的本质是一样的,只是名字不同而已。
当kernel32.dll中的API通过ntdll.dll执行时,会完成参数检查工作,再调用一个中断(int 2E 或者 SysEnter指令),从Ring3进入Ring0。
在内核ntoskrnl.exe中有一个SSDT,里面存放了与ntdll.dll中对应的SSDT系统服务处理函数,即内核态的Nt*系列函数,它们与ntdll.dll中的函数一一对应。
从用户模式调用Nt*和Zw*API连接ntdll.dll
二者没有区别,都是通过设置系统服务表中的索引和在栈中设置参数,经由SYSENTER(或者int 2E指令中断),并最终由KiSystemService跳转到KiServiceTable对应的系统服务历程中的。由于是从用户模式进入内核模式的,代码会严格检查用户空间传入的参数。
从内核模式调用Nt*和Zw*连接ntoskrnl.lib
Nt*系列API将直接调用对应的函数代码,而Zw*系列API则通过KiSystemService最终跳转到对应的函数代码处。重要的是两种调用对内核中的PreviousMode的改变:
如果从用户模式调用Native API,则Previous Mode是用户态。
如果从内核模式调用Native APi,则Previous Mode是内核态。
当PreviousMode为用户态时,Native API将对传递的参数进行严格的检测,当PreviousMode为内核态时则不会。
调用Nt* API时不会改变Previous Mode的状态,调用Zw* API时会将Previous Mode改为内核态,因此在进行Kernel Mode Driver开发时可以使用Zw系列API可以避免额外的参数列表检查,提高效率。(Zw*会设置KernelMode已避免检查,Nt*不会自动设置,如果是KernelMode当然没问题,如果就UserMode就挂了)