Windows本地操作系統服務API由一系列以 Nt 或 Zw 為首碼的函數實現的,這些函數以內核模式運行,內核驅動可以直接調用這些函數,而用戶層程式只能通過系統進行調用。通常情況下用戶層應用程式不會直接調用 Nt 和 Zw 系函數,更多的是通過直接調用Win32函數,這些Win32函數內部會調用 ...
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* 的具體函數列表可查看此處