1. 程式人生 > >本地系統服務例程:Nt和Zw系列函式

本地系統服務例程:Nt和Zw系列函式

Windows本地作業系統服務API由一系列以NtZw為字首的函式實現的,這些函式以核心模式執行,核心驅動可以直接呼叫這些函式,而使用者層程式只能通過系統進行呼叫。通常情況下使用者層應用程式不會直接呼叫NtZw系函式,更多的是通過直接呼叫Win32函式,這些Win32函式內部會呼叫NtZw系函式,但也僅限於通常情況下,當Win32函式不支援一些操作時,使用者層也會直接呼叫這些本地系統服務函式。

Nt字首是Windows NT的縮寫,但Zw字首並沒有任何意義,使用Zw只是避免跟其他已存在和未來可能出現的API有命名衝突而已。很多Windows驅動支援函式都以兩到三個特定的簡稱字母為字首進行命名,以此來表示這些例程都是由哪些核心系統元件實現的,比如CmRegisterCallbackEx

中的Cm就表示配置管理器(Configuration manager

每個本地系統服務例程都有兩個有著不同字首的相似名稱的函式版本,比如NtCreateFileZwCreateFile,兩者執行相同的操作,並且事實上兩者也都服務於相同的核心模式系統例程。對於使用者層的系統呼叫,NtZw系函式是沒有什麼區別的,但對於來自於核心驅動的呼叫,NtZw系函式對傳入引數的處理方式有些不一樣。

如果傳入引數是來自於可信任的核心層,那麼核心模式驅動則呼叫Zw版本的本地系統服務例程來通知其他例程,在這種情況下,例程都是不經過驗證就直接使用這些引數。反而,如果這些引數可能來自使用者層或者核心層,那麼驅動則呼叫Nt

版本的例程,這取決於呼叫執行緒的歷程——這些引數是從使用者層還是核心層發起的,執行緒物件中有個PreviousMode的屬性可用於判斷引數是否從使用者層過來的,關於例程如何判斷引數是來自使用者層還是核心層,詳細內容請參見預先模式

當一個使用者層應用程式呼叫NtZw系函式,這些本地系統服務函式始終會認為它接收到的引數來自於不可信任的使用者層,在使用前必先驗證引數的有效性。特別是對於由呼叫者提供的快取區,這些函式將會探測其記憶體地址是否有效並且是否正常對齊。

本地系統服務例程對於接收到引數值還會做額外的設定。如果一個例程接收到一個由指向由核心驅動分配的快取區指標,它會認為這快取區是從系統記憶體而不是從使用者層記憶體分配的,如果例程接收到一個由使用者層應用程式開啟的控制代碼型別引數,那麼例程就會從使用者層控制代碼表中查詢控制代碼而不是從核心層。

在一些情況下,從使用者層呼叫還是從核心層呼叫對傳入引數的意義和後續的使用影響重要。比如說ZwNotifyChangeKey(或說NtNotifyChangeKey)這個函式,其中有兩個輸入引數ApcRoutineAPCContext,從使用者層和從核心層傳過來分別代表不同的意義。如果其從使用者層被呼叫,ApcRoutine指向一個APC例程,ApcContext則指向一個由作業系統在呼叫APC例程時分配的上下文;如果其從核心層被呼叫,ApcRoutine指向一個WORK_QUEUE_ITEM結構,而ApcContext則表示WORK_QUEUE_ITEM佇列項的型別。

使用者層不支援呼叫Zw系函式,而在核心層呼叫Zw系函式時,上面也稍微提到過,系統不檢測呼叫者的訪問許可權,呼叫之前必須檢測從使用者模式下傳來的引數的有效性

大多數Zw 系函式的宣告在Wdm.h中可以找得到,少部分散落在其他標頭檔案裡如Ntddk.hNtifs.h

使用者層可通過引用Ntdll.lib靜態庫(在WDK中可以找到)來呼叫這些本地系統服務例程,大多數文件化的Nt系函式宣告在Windows SDKWinternl.h標頭檔案中,對於未文件化的Nt系函式,微軟一直不建議開發者進行呼叫,因為在未來的Windows版本中這些函式介面可能會有所改動或者直接被廢除,這對使用了這些未文件化函式的應用程式的穩定執行造成一定的影響,但往往是這些未文件化的函式和結構體能夠獲取更多的系統許可權,這也是眾多的Windows應用開發者不聽勸告反而樂此不疲地去挖掘的原因。

核心驅動可通過呼叫NtZwNtoskrnl.exe的動態連結庫的入口點(entry points)來使用這些本地系統服務例程的,該DLL(動態連結庫)包含這些服務例程的具體實現,要訪問這些入口點,驅動程式需要靜態連結到Ntoskrnl.lib(在WDK中也可以找到)

對於Nt*Xxx* and Zw*Xxx* 的具體函式列表可檢視此處