本地系统服务例程:Nt和Zw系列函数
windows本地操作系统服务api由一系列以nt或zw为前缀的函数实现的,这些函数以内核模式运行,内核驱动可以直接调用这些函数,而用户层程序只能通过系统进行调用。通常情况下用户层应用程序不会直接调用nt和zw系函数,更多的是通过直接调用win32函数,这些win32函数内部会调用nt和zw系函数,但也仅限于通常情况下,当win32函数不支持一些操作时,用户层也会直接调用这些本地系统服务函数。
nt前缀是windows nt的缩写,但zw前缀并没有任何意义,使用zw只是避免跟其他已存在和未来可能出现的api有命名冲突而已。很多windows驱动支持函数都以两到三个特定的简称字母为前缀进行命名,以此来表示这些例程都是由哪些内核系统组件实现的,比如cmregistercallbackex
中的cm就表示配置管理器(configuration manager)
每个本地系统服务例程都有两个有着不同前缀的相似名称的函数版本,比如ntcreatefile
和zwcreatefile
,两者执行相同的操作,并且事实上两者也都服务于相同的内核模式系统例程。对于用户层的系统调用,nt和zw系函数是没有什么区别的,但对于来自于内核驱动的调用,nt和zw系函数对传入参数的处理方式有些不一样。
如果传入参数是来自于可信任的内核层,那么内核模式驱动则调用zw版本的本地系统服务例程来通知其他例程,在这种情况下,例程都是不经过验证就直接使用这些参数。反而,如果这些参数可能来自用户层或者内核层,那么驱动则调用nt版本的例程,这取决于调用线程的历程——这些参数是从用户层还是内核层发起的,线程对象中有个previousmode的属性可用于判断参数是否从用户层过来的,关于例程如何判断参数是来自用户层还是内核层,详细内容请参见
当一个用户层应用程序调用nt或zw系函数,这些本地系统服务函数始终会认为它接收到的参数来自于不可信任的用户层,在使用前必先验证参数的有效性。特别是对于由调用者提供的缓存区,这些函数将会探测其内存地址是否有效并且是否正常对齐。
本地系统服务例程对于接收到参数值还会做额外的设定。如果一个例程接收到一个由指向由内核驱动分配的缓存区指针,它会认为这缓存区是从系统内存而不是从用户层内存分配的,如果例程接收到一个由用户层应用程序打开的句柄类型参数,那么例程就会从用户层句柄表中查找句柄而不是从内核层。
在一些情况下,从用户层调用还是从内核层调用对传入参数的意义和后续的使用影响重要。比如说zwnotifychangekey
(或说ntnotifychangekey
)这个函数,其中有两个输入参数apcroutine
和apccontext
,从用户层和从内核层传过来分别代表不同的意义。如果其从用户层被调用,apcroutine
指向一个apc例程,apccontext
则指向一个由操作系统在调用apc例程时分配的上下文;如果其从内核层被调用,apcroutine
指向一个work_queue_item
结构,而apccontext
则表示work_queue_item
队列项的类型。
用户层不支持调用zw系函数,而在内核层调用zw系函数时,上面也稍微提到过,系统不检测调用者的访问权限,调用之前必须检测从用户模式下传来的参数的有效性
大多数zw 系函数的声明在wdm.h中可以找得到,少部分散落在其他头文件里如ntddk.h和ntifs.h
用户层可通过引用ntdll.lib静态库(在wdk中可以找到)来调用这些本地系统服务例程,大多数文档化的nt系函数声明在windows sdk的winternl.h头文件中,对于未文档化的nt系函数,微软一直不建议开发者进行调用,因为在未来的windows版本中这些函数接口可能会有所改动或者直接被废除,这对使用了这些未文档化函数的应用程序的稳定运行造成一定的影响,但往往是这些未文档化的函数和结构体能够获取更多的系统权限,这也是众多的windows应用开发者不听劝告反而乐此不疲地去挖掘的原因。
内核驱动可通过调用nt和zw在ntoskrnl.exe的动态链接库的入口点(entry points)来使用这些本地系统服务例程的,该dll(动态链接库)包含这些服务例程的具体实现,要访问这些入口点,驱动程序需要静态链接到ntoskrnl.lib(在wdk中也可以找到)
对于nt*xxx* and zw*xxx* 的具体函数列表可查看