本機API

本機API是除了Win32API,NT平台開放了另一個基本接口。本機API也被很多人所熟悉,因為核心模式模組位於更低的系統級別,在那個級別上環境子系統是不可見的。儘管如此,並不需要驅動級別去訪問這個接口,普通的Win32程式可以在任何時候向下調用本機API。並沒有任何技術上的限制,只不過微軟不支持這種套用開發方法。
user32.dll,kernel32.dll,shell32.dll,gdi32.dll,RPCRT4.dll,comctl32.dll,advapi32.dll,version.dll等dll代表了Win32 API的基本提供者。Win32API中的所有調用最終都轉向了ntdll.dll,再由它轉發至ntoskrnl.exe。ntdll.dll是本機API用戶模式的終端。真正的接口在ntoskrnl.exe里完成。事實上,核心模式的驅動大部分時間調用這個模組,如果它們請求系統服務。Ntdll.dll的主要作用就是讓核心函式的特定子集可以被用戶模式下運行的程式調用。Ntdll.dll通過軟體中斷int2Eh進入ntoskrnl.exe,就是通過中斷門切換CPU特權級。比如kernel32.dll導出的函式DeviceIoControl()實際上調用ntdll.dll中導出的NtDeviceIoControlFile(),反彙編一下這個函式可以看到,EAX載入magic數0x38,實際上是系統調用號,然後EDX指向堆疊。目標地址是當前堆疊指針ESP+4,所以EDX指向返回地址後面一個,也就是指向在進入NtDeviceIoControlFile()之前存入堆疊的東西。事實上就是函式的參數。下一個指令是int2Eh,轉到中斷描述符表IDT位置0x2E處的中斷處理程式。
反編匯這個函式得到:
mov eax, 38h
lea edx, [esp+4]
int 2Eh
ret 28h
當然int 2E接口不僅僅是簡單的API調用調度員,他是從用戶模式進入核心模式的main gate。
W2kNative API由248個這么處理的函式組成,比NT4.0多了37個。可以從ntdll.dll的導出列表中很容易認出來:前綴Nt。Ntdll.dll中導出了249個,原因在於NtCurrentTeb()為一個純用戶模式函式,所以不需要傳給核心。令人驚奇的是,僅僅NativeAPI的一個子集能夠從核心模式調用。而另一方面,ntoskrnl.exe導出了兩個Nt*符號,它們不存在於ntdll.dll中:NtBuildNumber,NtGlobalFlag。它們不指向函式,事實上,是指向ntoskrnl.exe的變數,可以被使用C編譯器extern關鍵字的驅動模組導入。Ntdll.dll和ntoskrnl.exe中都有兩種前綴Nt*,Zw*。事實上ntdll.dll中反彙編結果兩者是一樣的。而在ntoskrnl.exe中,nt前綴指向真正的代碼,而zw還是一個int2Eh的stub。也就是說zw*函式集通過用戶模式到核心模式門傳遞的,而Nt*符號直接指向模式切換以後的代碼。Ntdll.dll中的NtCurrentTeb()沒有相對應的zw函式。Ntoskrnl並不導出配對的Nt/zw函式。有些函式只以一種方式出現。
2Eh中斷處理程式把EAX里的值作為查找表中的索引,去找到最終的目標函式。這個表就是系統服務表SST,C的結構SYSTEM_SERVICE_TABLE的定義如下:清單也包含了結構SERVICE_DESCRIPTOR_TABLE中的定義,為SST數組第四個成員,前兩個有著特別的用途。
typedef NTSTATUS (NTAPI *NTPROC) ( ) ;
typedef NTPROC *PNTPROC;
#define NTPROC_ sizeof (NTPROC)
typedef struct _SYSTEM_SERVICE_TABLE
{ PNTPROC ServiceTable; // 這裡是入口指針數組
PDWORD CounterTable; // 此處是調用次數計數數組
DWORD ServiceLimit ; // 服務入口的個數
PBYTE ArgumentTable; // 服務參數位元組數的數組
) SYSTEM_SERVICE_TABLE ,
* PSYSTEM_SERVICE_TABLE ,
* * PPSYSTEM_SERVICE_TABLE ;
/ / _ _ _ _ _ _ _ _ _ _ _ _
typedef struct _SERVICE_DESCRIPTOR_TABLE
{ SYSTEM_SERVICE_TABLE ntoskrnl ; // ntoskrnl所實現的系統服務,本機的API}
SYSTEM_SERVICE_TABLE win32k; // win32k所實現的系統服務
SYSTEM_SERVICE_TABLE Table3; // 未使用
SYSTEM_SERVICE_TABLE Table4; // 未使用
} SERVICE_DESCRIPTOR_TABLE ,
* PSERVICE_DESCRIPTOR_TABLE,
* PPSERVICE_DESCRIPTOR_TABLE ;
ntoskrnl通過KeServiceDescriptorTable符號,導出了主要SDT的一個指針。核心維護另外的一個SDT,就是KeServiceDescriptorTableShadow。但這個符號沒有導出。要想在核心模式組件中存取主要SDT很簡單,只需兩行C語言的代碼:
extern PSERVICE_DESCRIPTOR_TABLE KeServiceDescriptorTable;
PSERVICE_DESCRIPTOR_TABLE psdt= KeServiceDescriptorTable;
NTPROC為本機 API的方便的占位符,他類似於Win32編程中的PROC。NativeAPI正常的返回應該是一個NTSTATUS代碼,他使用NTAPI調用約定,它和_stdcall一樣。ServiceLimit成員有在ServiceTable數組裡找到的入口數目。在2000下,默認值是248。ArgumentTable為BYTEs的數組,每一個對應於ServiceTable的位置並顯示了在調用者堆疊里的參數比特數。這個信息與EDX結合,這是核心從調用者堆疊copy參數到自己的堆疊所需的。CounterTable成員在free buid的2000中並沒有使用到,在debugbuild中,這個成員指向代表所有函式使用計數的DWORDS數組,這個信息能用於性能分析。
可以使用這個命令來顯示:dd KeServiceDescriptorTable,調試器把此符號解析為0x8046e0c0。只有前四行是最重要的,對應那四個SDT成員。
運行這個命令:ln8046e100,顯示符號是KeServiceDescriptorTableShadow,說明第五個開始確實為核心維護的第二個SDT。主要的區別在於後一個包含了win32k.sys的入口,前一個卻沒有。在這兩個表中,Table3與Table4都是空的。Ntoskrnl.exe提供了一個方便的API函式。這個函式的名字為:
KeAddSystemServiceTable
此函式去填充這些位置。
2Eh的中斷處理標記是KisystemService()。這也是ntoskrnl.exe沒有導出的內部的符號,但包含在2k符號檔案中。關於KisystemService的操作如下:
1 從當前的執行緒控制塊檢索SDT指針
2 決定使用SDT中4個SST的其中一個。通過測試EAX中遞送ID的第12和13位來決定。ID在0x0000-0x0fff的映射至ntoskrnl表格,ID在
0x1000與0x1ffff的分配給win32k表格。剩下的0x2000-0x2ffff與
0x3000-0x3ffff則是Table3和Table4保留。
3 通過選定SST中的ServiceLimit成員檢查EAX的0-11位。如果ID超過了範圍,返回錯誤代碼為STATUS_INVALID_SYSTEM_SERVICE。
4 檢查EAX中的參數堆疊指針與MmUserProbeAddress。這是一個ntoskrnl導出的全局變數。通常等於0x7FFF0000,如果參數指針不在這個地址之下,返回STATUS_ACCESS_VIOLATION。
5 查找ArgumentTable中的參數堆疊的位元組數,從調用者的堆疊copy所有的參數至當前核心模式堆疊。
6 搜尋serviceTable中的服務函式指針,並調用這個函式。
7 控制轉到內部的函式KiserviceExit,在此次服務調用返回之後。
從對SDT的討論可以看到與本機API一起還有第二個核心模式接口。這個接口把Win32子系統的圖形設備接口和視窗管理器和核心模式組件Win32k連線起來。Win32k接口一樣是基於int2eh。本機API的服務號是從0x0000到0x0fff,win32k的服務號是從0x1000到0x1fff。(ddW32pServiceTable認定win32k.sys的符號可用。)win32k總共包含639個系統服務。
2Eh的處理過程沒有使用全局SDT KeServiceDescriptorTable。
而是一個與執行緒相關的指針。顯然,執行緒可以有不同得SDT相關到自身。執行緒初試化的時候,KeInitializeThread()把KeServiceDescriptorTable寫到執行緒的控制塊。儘管這樣,這個默認設定之後可能被改變為其它值,例如KeServiceDescriptorTableShadow。
Windows 2000運行時庫
Ntdll.dll至少導出了不少於1179個符號。其中的249/248是屬於Nt*/zw*集合。所以還有682個函式不是通過int 2eh門中轉。很顯然,這么多的函式不依靠2k的核心。
其中一些是和c運行時庫幾乎一樣的函式。其實ntoskrnl也實現了一些類似C運行時庫的一些函式。可以通過ddk里的ntdll.lib來連結和使用這些函式。反彙編ntdll.dll與ntoskrnl.exe的C運行時函式能發現,ntdll.dll並不是依賴ntoskrnl.exe。這兩個模組各自實現了這些函式。
除了C運行時庫外,2000還提供了一個擴展的運行時函式集合。再一次,ntdll.dll與ntoskrnl.exe各自實現了它們。同樣,實現集合有重複,但是並不完全匹配。這個集合的函式都是以Rtl開頭的。2000運行時庫包括一些輔助函數用於C運行時候無法完成的任務。例如有些處理安全事務,另外的操縱2000專用的數據結構,還有些支持記憶體管理。微軟僅僅在DDK中記錄了很有用的406個函式中的115個函式。
Ntdll.dll還提供了另外一個函式集合,以__e前綴開頭。實際上它們用於浮點數模擬器。
還有很多的函式集合,所有這些函式的前綴如下:
__e(浮點模擬),Cc(Cache管理),Csr(c/s運行時庫),DBG(調試支持),Ex(執行支持),FsRtl(檔案系統運行時),Hal(硬體抽象層),Inbv(系統初試化/vga啟動驅動程式bootvid.dll),Init(系統初試化),Interlocked(執行緒安全變數操作),Io(IO管理器),Kd(核心調試器支持),Ke(核心例程),Ki(核心中斷處理),Ldr(映象裝載器),Lpc(本地過程調用),Lsa(本地安全授權),Mm(記憶體管理),Nls(國際化語言支持),Nt(NT本機API),Ob(對象管理器),pfx(前綴處理),Po(電源管理),Ps(進程支持),READ_REGISTER_(從暫存器地址讀),Rtl(2k運行時庫),Se(安全處理),WRITE_REGISTER_(寫暫存器地址),Zw(本機API的替換叫法),<其它>(輔助函式和C運行時庫)。
當編寫從用戶模式通過ntdll.dll或核心模式通過ntoskrnl.exe和2000核心互動的軟體的時候,需要處理很多基本的數據結構,這些結構在Win32世界中很少見到。
常用數據結構
l 整數
ANSI字元是有符號的,而Unicode WCHAR是無符號的
MASM的TBYTE是80位的浮點數,用於高精度浮點運算單元操作,注意它與Win32的TBYTE(text byte)完全不同。
TABLE 2-3. Equivalent Integral Data Types
BITSMASM FUNDAMENTALALIAS #1ALIAS #2SIGNED
8 BYTEunsigned charUCHAR CHAR
16WORDunsigned short USHORT WCHARSHORT
32DWORD unsigned longULONG LONG
32DWORD unsigned intUINT INT
64qword unsigned _int64 ULONGLONG DWORDLONG LONGLONG
80TBYTEN/A
typedef union _LARGE_INTEGER
{struct{
ULONG LowPart;
LONG HighPart;};
LONGLONG QuadPart;
}
LARGE_INTEGER , * PULARGE_INTEGER ;
typedef union _ULARGE_INTEGER{
struct{
ULONG LowPart;
ULONG HighPart;}
ULONGLONG QuadPart;
}ULARGE_INTEGER, *PULARGE_INTEGER;
l 字元
Win32編程中PSTR用戶CHAR*,PWSTR用於WCHAR*。取決於是否定義了UNICODE,PTSTR解釋為PSTR或者PWSTR。在2k核心模式下,常用的數據類型是UNICODE_STRING,而STRING用來表示ANSI字元串:
typedef struct _UNICODE_STRING{
USHORT Length; //當前位元組長度,不是字元!!!
USHORT MaximumLength; //Buffer的最大位元組長度
PWSTR Buffer;}UNICODE_STRING , * PUNICODE_STRING ;
typedef struct _STRING{
USHORT Length;
USHORT MaximumLength;
PCHAR Buffer;}STRING, *PSTRING;
typedef STRING ANSI_STRING, *PANSI_STRING;
typedef STRING OEM_STRING, *POEM_STRING;
操縱函式:RtlCreatUnicodeString(),RtlInitUnicodeString(),
RtlCopyUnicodeString()等等
l 結構
許多核心API函式需要一個固定大小的OBJECT_ATTRIBUTES結構,比如NtOpenFile()。對象的屬性是OBJ_*值的組合,可以從ntdef.h中查到。
IO_STATUS_BLOCK結構提供了所請求操作結果的信息,很簡單,status成員包含一個NTSTATUS代碼, 如果操作成功 information成員提供特定請求的信息。
還有一個結構是list_entry,這是一個雙向環鍊表。
typedef struct _OBJECT_ATTRIBUTES
{
ULONG Length;
HANDLE RootDirectory;
PUNICODE_STRING ObjectName;
ULONG Attributes;
PVOID SecurityDescriptor;
PVOID SecurityQualityOfService;
} OBJECT_ATTRIBDTES, *POBJECT_ ATTRIBUTES;
typedef struct _IO_STATUS_BLOCK
{
NTSTATDS Status;
ULONG Information;
}IO_STATUS_BLOCK , * PIO_STATUS_BLOCK ;
typedef struct _LIST_ENTRY
{
Struct _LIST_ENTRY *flink;
Struct _LIST_ENTRY *Blink;
}LIST_ENTRY, *PLIST_ENTRY;
雙向鍊表的典型例子就是進程和執行緒鏈。內部變數PsActiveProcessHead是一個LIST_ENTRY結構,在ntoskrnl.exe的數據段中,指定了系統進程列表的第一個成員。
CLIENT_ID結構由進程和執行緒ID組成。
typedef struct _CLIENT_ID
{ HANDLE UniqueProcess;
HANDLE UniqueThread;
)CLIENT_ID, *PCLIENT_ID;
想要從用戶模式調用ntdll.dll中的API函式,必須考慮到以下四點:
1 SDK頭檔案沒有包括這些函式的原型
2 這些函式使用的若干基本數據類型沒有包括在SDK檔案中
3 SDK和DDK頭檔案不兼容,不能在win32的c源檔案包含ntddk.h中
4 ntdll.lib沒有包括在VC的默認導入庫列表中。
第4個很容易解決:#progma comment(linker,“/defaultlib:ntdll.lib”)
缺失的定義比較難解決,最簡單的方法是寫一個自定義的頭檔案,剛剛包含需要調用ntdll.dll中函式的定義。幸運的是,已經在光碟的w2k_def.h檔案中做了這個工作。因為這個頭檔案將用於用戶模式和核心模式程式,所以必須在用戶模式代碼中,#include<w2k_def.h>之前#define _USER_MODE_,使得DDK中出現而SDK中沒有的定義可用。

相關詞條

相關搜尋

熱門詞條

聯絡我們