|
此文只能說是一篇筆記,是關于本機API的.本機API是除了Win32 API,NT平臺開放了另一個基本接口。本
機API也被很多人所熟悉,因為內核模式模塊位于更低的系統(tǒng)級別,在那個級別上環(huán)境子系統(tǒng)是不可見的 。盡管如此,并不需要驅動級別去訪問這個接口,普通的Win32程序可以在任何時候向下調用本機API。并 沒有任何技術上的限制,只不過微軟不支持這種應用開發(fā)方法。 User32.dll,kernel32.dll,shell32.dll,gdi32.dll,rpcrt4.dll,comctl32.dll,advapi32.dll,version.d ll等dll代表了Win32 API的基本提供者。Win32 API中的所有調用最終都轉向了ntdll.dll,再由它轉發(fā)至 ntoskrnl.exe。ntdll.dll是本機 API用戶模式的終端。真正的接口在ntoskrnl.exe里完成。事實上,內 核模式的驅動大部分時間調用這個模塊,如果它們請求系統(tǒng)服務。Ntdll.dll的主要作用就是讓內核函數(shù) 的特定子集可以被用戶模式下運行的程序調用。Ntdll.dll通過軟件中斷int 2Eh進入ntoskrnl.exe,就是 通過中斷門切換CPU特權級。比如kernel32.dll導出的函數(shù)DeviceIoControl()實際上調用ntdll.dll中導 出的NtDeviceIoControlFile(),反匯編一下這個函數(shù)可以看到,EAX載入magic數(shù)0x38,實際上是系統(tǒng)調 用號,然后EDX指向堆棧。目標地址是當前堆棧指針ESP+4,所以EDX指向返回地址后面一個,也就是指向 在進入NtDeviceIoControlFile()之前存入堆棧的東西。事實上就是函數(shù)的參數(shù)。下一個指令是int 2Eh, 轉到中斷描述符表IDT位置0x2E處的中斷處理程序。 反編匯這個函數(shù)得到: mov eax, 38h lea edx, [esp+4] int 2Eh ret 28h 當然int 2E接口不僅僅是簡單的API調用調度員,他是從用戶模式進入內核模式的main gate。 W2k Native API由248個這么處理的函數(shù)組成,比NT 4.0多了37個??梢詮膎tdll.dll的導出列表中很容易 認出來:前綴Nt。Ntdll.dll中導出了249個,原因在于NtCurrentTeb()為一個純用戶模式函數(shù),所以不需 要傳給內核。令人驚奇的是,僅僅Native API的一個子集能夠從內核模式調用。而另一方面, ntoskrnl.exe導出了兩個Nt*符號,它們不存在于ntdll.dll中: NtBuildNumber, NtGlobalFlag。它們不 指向函數(shù),事實上,是指向ntoskrnl.exe的變量,可以被使用C編譯器extern關鍵字的驅動模塊導入。 Ntdll.dll和ntoskrnl.exe中都有兩種前綴Nt*,Zw*。事實上ntdll.dll中反匯編結果兩者是一樣的。而在 ntoskrnl.exe中,nt前綴指向真正的代碼,而zw還是一個int 2Eh的stub。也就是說zw*函數(shù)集通過用戶模 式到內核模式門傳遞的,而Nt*符號直接指向模式切換以后的代碼。Ntdll.dll中的NtCurrentTeb()沒有相 對應的zw函數(shù)。Ntoskrnl并不導出配對的Nt/zw函數(shù)。有些函數(shù)只以一種方式出現(xiàn)。 2Eh中斷處理程序把EAX里的值作為查找表中的索引,去找到最終的目標函數(shù)。這個表就是系統(tǒng)服務表SST ,C的結構SYSTEM_SERVICE_TABLE的定義如下:清單也包含了結構SERVICE_DESCRIPTOR_TABLE中的定義,為 SST數(shù)組第四個成員,前兩個有著特別的用途。 typedef NTSTATUS (NTAPI *NTPROC) ( ) ; typedef NTPROC *PNTPROC; #define NTPROC_ sizeof (NTPROC) typedef struct _SYSTEM_SERVICE_TABLE { PNTPROC ServiceTable; // 這里是入口指針數(shù)組 PDWORD CounterTable; // 此處是調用次數(shù)計數(shù)數(shù)組 DWORD ServiceLimit ; // 服務入口的個數(shù) PBYTE ArgumentTable; // 服務參數(shù)字節(jié)數(shù)的數(shù)組 ) SYSTEM_SERVICE_TABLE , * PSYSTEM_SERVICE_TABLE , * * PPSYSTEM_SERVICE_TABLE ; / / _ _ _ _ _ _ _ _ _ _ _ _ typedef struct _SERVICE_DESCRIPTOR_TABLE { SYSTEM_SERVICE_TABLE ntoskrnl ; // ntoskrnl所實現(xiàn)的系統(tǒng)服務,本機的API} SYSTEM_SERVICE_TABLE win32k; // win32k所實現(xiàn)的系統(tǒng)服務 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。Native API正常的返回應該是一個 NTSTATUS代碼,他使用NTAPI調用約定,它和_stdcall一樣。ServiceLimit成員有在ServiceTable數(shù)組里 找到的入口數(shù)目。在2000下,默認值是248。ArgumentTable為BYTEs的數(shù)組,每一個對應于ServiceTable 的位置并顯示了在調用者堆棧里的參數(shù)比特數(shù)。這個信息與EDX結合,這是內核從調用者堆棧copy參數(shù)到 自己的堆棧所需的。CounterTable成員在free buid的2000中并沒有使用到,在debug build中,這個成員 指向代表所有函數(shù)使用計數(shù)的DWORDS數(shù)組,這個信息能用于性能分析。 可以使用這個命令來顯示:dd KeServiceDescriptorTable,調試器把此符號解析為0x8046e0c0。只有 前四行是最重要的,對應那四個SDT成員。 運行這個命令:ln 8046e100,顯示符號是KeServiceDescriptorTableShadow,說明第五個開始確實為 內核維護的第二個SDT。主要的區(qū)別在于后一個包含了win32k.sys的入口,前一個卻沒有。在這兩個表中 ,Table3與Table4都是空的。Ntoskrnl.exe提供了一個方便的API函數(shù)。這個函數(shù)的名字為: KeAddSystemServiceTable 此函數(shù)去填充這些位置。 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中的參數(shù)堆棧指針與MmUserProbeAddress。這是一個ntoskrnl導出的全局變量。通常等于 0x7FFF0000,如果參數(shù)指針不在這個地址之下,返回STATUS_ACCESS_VIOLATION。 5 查找ArgumentTable中的參數(shù)堆棧的字節(jié)數(shù),從調用者的堆棧copy所有的參數(shù)至當前內核模式堆棧。 6 搜索serviceTable中的服務函數(shù)指針,并調用這個函數(shù)。 7 控制轉到內部的函數(shù)KiserviceExit,在此次服務調用返回之后。 從對SDT的討論可以看到與本機API一起還有第二個內核模式接口。這個接口把Win32子系統(tǒng)的圖形設備接 口和窗口管理器和內核模式組件Win32k連接起來。Win32k接口一樣是基于int 2eh。本機API的服務號是從 0x0000到0x0fff,win32k的服務號是從0x1000到0x1fff。(ddW32pServiceTable認定win32k.sys的符號可 用。)win32k總共包含639個系統(tǒng)服務。 2Eh的處理過程沒有使用全局SDT KeServiceDescriptorTable。 而是一個與線程相關的指針。顯然,線程可以有不同得SDT相關到自身。線程初試化的時 候,KeInitializeThread()把KeServiceDescriptorTable寫到線程的控制塊。盡管這樣,這個默認設置之 后可能被改變?yōu)槠渌担鏚eServiceDescriptorTableShadow。 Windows 2000運行時庫 Ntdll.dll至少導出了不少于1179個符號。其中的249/248是屬于Nt*/zw*集合。所以還有682個函數(shù)不是通 過int 2eh門中轉。很顯然,這么多的函數(shù)不依靠2k的內核。 其中一些是和c運行時庫幾乎一樣的函數(shù)。其實ntoskrnl也實現(xiàn)了一些類似C運行時庫的一些函數(shù)??梢?br> 通過ddk里的ntdll.lib來鏈接和使用這些函數(shù)。反匯編ntdll.dll與ntoskrnl.exe的C運行時函數(shù)能發(fā)現(xiàn) ,ntdll.dll并不是依賴ntoskrnl.exe。這兩個模塊各自實現(xiàn)了這些函數(shù)。 除了C運行時庫外,2000還提供了一個擴展的運行時函數(shù)集合。再一次,ntdll.dll與ntoskrnl.exe各自 實現(xiàn)了它們。同樣,實現(xiàn)集合有重復,但是并不完全匹配。這個集合的函數(shù)都是以Rtl開頭的。2000運行 時庫包括一些輔助函數(shù)用于C運行時候無法完成的任務。例如有些處理安全事務,另外的操縱2000專用的 數(shù)據(jù)結構,還有些支持內存管理。微軟僅僅在DDK中記錄了很有用的406個函數(shù)中的115個函數(shù)。 Ntdll.dll還提供了另外一個函數(shù)集合,以__e前綴開頭。實際上它們用于浮點數(shù)模擬器。 還有很多的函數(shù)集合,所有這些函數(shù)的前綴如下: __e(浮點模擬),Cc(Cache管理),Csr(c/s運行時庫),Dbg(調試支持),Ex(執(zhí)行支持),FsRtl(文件系統(tǒng)運行 時),Hal(硬件抽象層),Inbv(系統(tǒng)初試化/vga啟動驅動程序bootvid.dll),Init(系統(tǒng)初試 化),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的替換叫法) ,<其它>(輔助函數(shù)和C運行時庫)。 當編寫從用戶模式通過ntdll.dll或內核模式通過ntoskrnl.exe和2000內核交互的軟件的時候,需要處理 很多基本的數(shù)據(jù)結構,這些結構在Win32世界中很少見到。 常用數(shù)據(jù)結構 l 整數(shù) ANSI字符是有符號的,而Unicode WCHAR是無符號的 MASM的TBYTE是80位的浮點數(shù),用于高精度浮點運算單元操作,注意它與Win32的TBYTE(text byte)完全 不同。 TABLE 2-3. Equivalent Integral Data Types BITS MASM FUNDAMENTAL ALIAS #1 ALIAS #2 SIGNED 8 BYTE unsigned char UCHAR CHAR 16 WORD unsigned short USHORT WCHAR SHORT 32 DWORD unsigned long ULONG LONG 32 DWORD unsigned int UINT INT 64 QWORD unsigned _int64 ULONGLONG DWORDLONG LONGLONG 80 TBYTE N/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內核模式下,常用的數(shù)據(jù)類型是UNICODE_STRING,而STRING用來表示ANSI字符串: typedef struct _UNICODE_STRING{ USHORT Length; //當前字節(jié)長度,不是字符?。?! USHORT MaximumLength; //Buffer的最大字節(jié)長度 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; 操縱函數(shù):RtlCreatUnicodeString(),RtlInitUnicodeString(), RtlCopyUnicodeString()等等 l 結構 許多內核API函數(shù)需要一個固定大小的OBJECT_ATTRIBUTES結構,比如NtOpenFile()。對象的屬性是OBJ_* 值的組合,可以從ntdef.h中查到。 IO_STATUS_BLOCK結構提供了所請求操作結果的信息,很簡單,status成員包含一個NTSTATUS代碼, 如果 操作成功 information成員提供特定請求的信息。 還有一個結構是LIST_ENTRY,這是一個雙向環(huán)鏈表。 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的數(shù)據(jù)段中,指定了系統(tǒng)進程列表的第一個成員。 CLIENT_ID結構由進程和線程ID組成。 typedef struct _CLIENT_ID { HANDLE UniqueProcess; HANDLE UniqueThread; )CLIENT_ID, *PCLIENT_ID; 想要從用戶模式調用ntdll.dll中的API函數(shù),必須考慮到以下四點: 1 SDK頭文件沒有包括這些函數(shù)的原型 2 這些函數(shù)使用的若干基本數(shù)據(jù)類型沒有包括在SDK文件中 3 SDK和DDK頭文件不兼容,不能在win32的c源文件包含ntddk.h中 4 ntdll.lib沒有包括在VC的默認導入庫列表中。 第4個很容易解決:#progma comment(linker,“/defaultlib:ntdll.lib”) 缺失的定義比較難解決,最簡單的方法是寫一個自定義的頭文件,剛剛包含需要調用ntdll.dll中函數(shù)的 定義。幸運的是,已經(jīng)在光盤的w2k_def.h文件中做了這個工作。因為這個頭文件將用于用戶模式和內核 模式程序,所以必須在用戶模式代碼中,#include<w2k_def.h>之前#define _USER_MODE_,使得DDK中出 現(xiàn)而SDK中沒有的定義可用。 本文部分翻譯于一篇電子書<win api about>.也感謝朋友GameHunter這位英語極好的朋友幫忙.與Free的指導 |
|
|