《Undocumented Windows 2000 Secrets》翻譯 --- 第二章(2)
第二章 The Windows 2000 Native API
翻譯: Kendiv
更新: Friday, February 04, 2005
Windows 2000 運(yùn)行時(shí)庫(kù)
Nt*() 和 Zw*() 函數(shù)構(gòu)成了 Native API 的基本部分,但并不是主要部分,還有一部分代碼位于 ntdll.dll 中。該 DLL 至少導(dǎo)出了 1179 個(gè)符號(hào)。其中的 249 和 248 個(gè)分別屬于 Nt*() 和 Zw*() 函數(shù)集,剩余的 682 個(gè)函數(shù)并不通過(guò) INT 2eh 中斷進(jìn)行調(diào)用。顯然,這一大組函數(shù)并不依賴(lài) Windows 2000 內(nèi)核。那提供它們的目的何在呢?讓我們繼續(xù)往下看。
C 運(yùn)行時(shí)庫(kù)
如果你研究過(guò)位于 ntdll.dll 導(dǎo)出節(jié)( export section )的符號(hào),你會(huì)發(fā)現(xiàn)很多在 C 程序員看來(lái)很熟悉的小寫(xiě)的函數(shù)名稱(chēng)。這些都是眾所周知的名子,如 memcpy() 、 sprintf() 和 qsort() ,這些 C 運(yùn)行時(shí)庫(kù)中的函數(shù)都合并到了 ntdll.dll 中。對(duì)于 ntoskrnl.exe 也是如此,它同樣提供了一組與 C 運(yùn)行時(shí)函數(shù)十分相像的函數(shù),雖然這兩組函數(shù)并不相同。 附錄 B 的 表 B-3 列出了這兩組函數(shù),并指出了每個(gè)函數(shù)分別屬于哪個(gè)模塊。
你可以簡(jiǎn)單的將 ntdll.lib (來(lái)自 Windows 2000 DDK )添加到導(dǎo)入庫(kù)列表(鏈接器在解析符號(hào)期間將掃描該列表)中,就可以鏈接到這些函數(shù)。如果你更喜歡對(duì)話(huà)框,你可以選擇 Visual C/C++ 的工程菜單中的 Settings 子菜單,然后單擊 Linke 頁(yè),選擇 Category General ,然后將 ntdll.dll 添加到 Object/Library 模塊列表中。還有一種方法:在源文件中,添加如下的內(nèi)容:
#pragma comment(linker,”/defaultlib:ntdll.lib”)
這同樣有效,好處是,其他開(kāi)發(fā)人員可以使用 Visual C/C++ 的默認(rèn)設(shè)置來(lái) rebuild 你的工程。
反編譯這些與 C 運(yùn)行時(shí)函數(shù)類(lèi)似的函數(shù)(來(lái)自 ntdll.dll 和 ntoskrnl.exe ),會(huì)發(fā)現(xiàn) ntdll.dll 并不依賴(lài)于 ntoskrnl.exe ,這和 ndll.dll 中的 Native API 不一樣。事實(shí)上,這兩個(gè)模塊分別實(shí)現(xiàn)了這些函數(shù)。本節(jié)出現(xiàn)的其他函數(shù)也是如此。注意,表 B-3 中的一些函數(shù)并不使用其導(dǎo)出的名稱(chēng)。例如,如果在內(nèi)核模式的驅(qū)動(dòng)程序中針對(duì)一個(gè) 64 位的 LARGE_INTEGER 使用移位操作符 << 和 >> ,編譯器和鏈接器會(huì)自動(dòng)導(dǎo)入 ntoskrnl.exe 的 _allshr() 和 _allshl() 。
擴(kuò)展的運(yùn)行時(shí)函數(shù)
隨同標(biāo)準(zhǔn)的 C 運(yùn)行時(shí)函數(shù), Windows 2000 還提供了一組擴(kuò)展的運(yùn)行時(shí)函數(shù)。在次強(qiáng)調(diào), ntdll.dll 和 ntoskrnl.exe 分別實(shí)現(xiàn)了它們。并且其中有些函數(shù)是重疊的。這些擴(kuò)展函數(shù)的名字都有一個(gè)共同的前綴 Rtl ( for Runtime Library )。 附錄 B 的 表 B-4 列出了所有這些擴(kuò)展函數(shù)。 Windows 2000 提供的這些運(yùn)行時(shí)函數(shù)還包含用于普通任務(wù)的助手函數(shù)( helper function ),這些任務(wù)都超過(guò)了 C 運(yùn)行時(shí)函數(shù)的能力范圍。例如,其中的某些用于管理安全性,另一些用于操作 Windows 2000 特有的數(shù)據(jù)結(jié)構(gòu),還有一些對(duì)內(nèi)存管理提供支持。很難理解為什么微軟僅在 Windows 2000 DDK 中提供了其中 115 個(gè)函數(shù)的文檔,而扔掉了其余 406 個(gè)非常有用的函數(shù)。
浮點(diǎn)模擬器( The Floating-Point Emulator )
讓我用 ntdll.dll 提供的另一組函數(shù)集合來(lái)結(jié)束這次 API 函數(shù)匯展。 表 2-1 列出了這些函數(shù)的名稱(chēng),這些名稱(chēng)可能對(duì)于匯編程序員有些眼熟。去了名稱(chēng)前的 __e 前綴,你就會(huì)得到 i386 系列 CPU 中的 FPU ( Floating-Point Unit )匯編助記符。事實(shí)上,從 表 2-1 中列出的函數(shù)來(lái)看, ntdll.dll 包含了一個(gè)完整的浮點(diǎn)模擬器。這再次證明了這個(gè) DLL 是一個(gè)龐大的代碼倉(cāng)庫(kù),這吸引了眾多的 System Spelunker 去反編譯它。
表 2-1. ntdll.dll 的浮點(diǎn)模擬器接口
函數(shù)名稱(chēng)
_eCommonExceptions
_eFIST32
_eFLD64
_eFSTP32
_eEnulatorInit
_eFISTP16
_eFLD80
_eFSTp64
_eF2XM1
_eFISTP32
_eFLDCW
_eFSTP80
_eFABS
_eFISTP64
_eFLDENV
_eFSTSW
_eFADD32
_eFISUB16
_eFLDL2E
_eFSUB32
_eFADD64
_eFISUB32
_eFLDLN2
_eFSUB64
_eFADDPreg
_eFISUBR16
_eFLDPI
_eFSUBPreg
_eFADDreg
_eFISUBR32
_eFLDZ
_eFSUBR32
_eFADDtop
_eFLDI
_eFMUL32
_eFSUBR64
_eFCHS
_eFIDIVR16
_eFMUL64
_eFSUBreg
_eFCOM
_eFIDIVR32
_eFMULPreg
_eFSUBRPreg
_eFCOM32
_eFILD16
_eFMULreg
_eFSUBRreg
_eCOM64
_eFILD32
_eFMULtop
_eFSUBRtop
_eFCOMP
_eFILD64
_eFPATAN
_eFSUBtop
_eFCOMP32
_eFIMUL16
_eFPREm
_eFTST
_eFCOMP64
_eFIMUL32
_eFPREM1
_eFUCOM
_eFCOMPP
_eFINCSTP
_eFPTAN
_eFUCOMP
_eFCOS
_eFINIT
_eFRNDINT
_eFUCOMPP
_eFDECSTP
_eFIST16
_eFRSTOR
_eFXAM
_eFIDIVR16
_eFIST32
_eFSAVE
_eFXCH
_eFIDIVR32
_eFISTP16
_eFSCALE
_eFXTRACT
_eFILD16
_eFISTP32
_eFSIN
_eFYL2X
_eFILD32
_eFISTP64
_eFSQRT
_eFYL2XP1
_eFILD64
_eFISUB16
_eFST
_eGetStatusWord
_eFIMUL16
_eFISUB32
_eFST32
NPXEMULATORTABLE
_eFIMUL32
_eFISUBR16
_eFST64
RestoreEm87Context
_eFINCSTP
_eFISUBR32
_eFSTCW
SaveEm87Context
_eFINIT
_eFLD16
_eFSTENV
_eFIST16
_eFLD32
_eFSTP
有關(guān)浮點(diǎn)指令集的更多信息,請(qǐng)參考 Intel 80386 CPU 的原始文檔。可以從 Intel 官方網(wǎng)站: http://developer.intel.com/design/pentium/manuals/ 來(lái)下載 PDF 格式的 Pentium 手冊(cè)。講解這些機(jī)器碼指令集的手冊(cè)是: Intel Architecture SoftWare Developer's Manual . Volume 2 : Instruction Set Reference ( Intel 1999b )。
其它的 API 函數(shù)
除 附錄 B 和 表 2-1 列出的函數(shù)外, ntdll.dll 和 ntoskrnl.exe 還為多個(gè)內(nèi)核組件導(dǎo)出了為數(shù)眾多的函數(shù)。為了避免更長(zhǎng)的表格,我這里僅列出可用函數(shù)的名稱(chēng)前綴及其所屬類(lèi)別( 表 2-2 )。
表 2-2 函數(shù)名前綴及其所屬分類(lèi)
前綴
ntdll.dll
ntoskrnl.exe
分類(lèi)
_e
N/A
浮點(diǎn)模擬器
Cc
N/A
Cache 管理器
Csr
ClIEnt-Server 運(yùn)行時(shí)庫(kù)
Dbg
N/A
調(diào)試支持
Ex
N/A
執(zhí)行支持( Executive Support )
FsRtl
N/A
文件系統(tǒng)運(yùn)行時(shí)庫(kù)
Hal
N/A
硬件抽象層調(diào)度器
Inbv
N/A
系統(tǒng)初始化 /VGA 啟動(dòng)驅(qū)動(dòng)( bootvid.dll )
Init
N/A
系統(tǒng)初始化
Interlocked
N/A
處理線(xiàn)程安全的變量
Io
N/A
I/O 管理器
Kd
N/A
內(nèi)核調(diào)試支持
Ke
N/A
內(nèi)核例程
Ki
內(nèi)核中斷例程
Ldr
映像加載器
Lpc
N/A
本地過(guò)程調(diào)用( LPC )設(shè)備
Lsa
N/A
本地安全授權(quán)
Mm
N/A
內(nèi)存管理器
Nls
National Language Support (NLS)
Nt
NT Native API
Ob
N/A
對(duì)象管理器
Pfx
前綴處理
Po
N/A
電源管理器
Ps
N/A
進(jìn)程支持
READ_REGISTER_
N/A
從寄存器地址中讀取
Rtl
Windows 2000 運(yùn)行時(shí)庫(kù)
Se
N/A
安全處理
WRITE_REGISTER_
N/A
向寄存器地址中寫(xiě)入
Zw
另一組 Native API
<other>
幫助函數(shù)和 C 運(yùn)行時(shí)庫(kù)
很多內(nèi)核函數(shù)都使用統(tǒng)一的命名規(guī)則 ----PrefixOperationObject() 。例如, NtQueryInformationFile() 函數(shù)屬于 Native API ,這是因?yàn)槠?Nt 前綴,而且該函數(shù)顯然針對(duì)一個(gè)文件對(duì)象執(zhí)行了 QueryInformation 操作。但并不是所有函數(shù)都遵循這一規(guī)則,不過(guò)絕大多數(shù)都是如此。因此,可以很容易的通過(guò)函數(shù)的名稱(chēng)猜測(cè)其功能。
經(jīng)常使用的數(shù)據(jù)類(lèi)型
當(dāng)編寫(xiě)與 Windows 2000 內(nèi)核有關(guān)的軟件時(shí) --- 不管是和用戶(hù)模式的 ntdll.dll 還是和內(nèi)核模式的 ntoskrnl.exe ,你都必須處理幾個(gè)基本的數(shù)據(jù)類(lèi)型,而這些數(shù)據(jù)類(lèi)型在 Win32 世界里非常少見(jiàn)。它們中的多數(shù)都會(huì)在本書(shū)中反復(fù)出現(xiàn)。下面的章節(jié)將介紹使用頻率最高的數(shù)據(jù)類(lèi)型。
整型
一般說(shuō)來(lái),整數(shù)類(lèi)型有多個(gè)不同的變體。 Win32 SDK 的頭文件和 SDK 文檔使用了其專(zhuān)有的術(shù)語(yǔ),這些術(shù)語(yǔ)很容易和 C/C++ 的基本類(lèi)型以及一些派生類(lèi)型相混淆。 表 2-3 列出了這些整數(shù)類(lèi)型,以及它們之間的等價(jià)關(guān)系。在“ MASM ”列中,給出了微軟宏匯編語(yǔ)言( MASM )使用的類(lèi)型名稱(chēng)。 Win32 SDK 為 C/C++ 的基本數(shù)據(jù)類(lèi)型定義了對(duì)應(yīng)的 BYTE 、 WORD 、 DWORD 別名。“別名 1 ”和“別名 2 ”兩列包含其經(jīng)常使用的別名。例如, WCHAR 代表基礎(chǔ)的 Unicode 字符類(lèi)型。最后一列“有符號(hào)的”,列出了對(duì)應(yīng)的有符號(hào)類(lèi)型的常見(jiàn)別名。一定要記住 ANSI 字符類(lèi)型 CHAR 是有符號(hào)的,而 Unicode 類(lèi)型 WCHAR 是無(wú)符號(hào)的。當(dāng)編譯器將表達(dá)式或計(jì)算中的這些類(lèi)型轉(zhuǎn)換為整數(shù)類(lèi)型時(shí),這種不一致性將導(dǎo)致意外的錯(cuò)誤。
表 2-3 最后一行的 MASM 的 TBYTE 類(lèi)型(讀做“ 10-byte ”)是一個(gè) 80 位的浮點(diǎn)數(shù),用于高精度的浮點(diǎn)運(yùn)算操作。 Microsoft Visual C/C++ 沒(méi)有為 Win32 程序員提供對(duì)應(yīng)的數(shù)據(jù)類(lèi)型。需要注意的是, MASM 的 TBYTE 和 Win32 的 TBYTE (讀做“ text byte ”)沒(méi)有任何關(guān)系,后者只是一個(gè)用于轉(zhuǎn)換的宏,根據(jù)源文件中是否有 #define UNICODE 而分別對(duì)應(yīng) CHAR 或 WCHAR 。
表 2-3. 等價(jià)的整數(shù)類(lèi)型
位數(shù)
MASM
基本類(lèi)型
別名 1
別名 2
有符號(hào)的
8
BTYE
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
由于在 32 位編程環(huán)境中較難處理 64 位整數(shù), Windows 2000 通常不提供 64 位的基本類(lèi)型,如 __int64 或其派生類(lèi)型。替代的, DDK 頭文件 ntdef.h 中定義了一個(gè)精巧的 union 結(jié)構(gòu),可以將一個(gè) 64 位數(shù)解釋為一對(duì) 32 位數(shù)或一個(gè)完整的 64 位數(shù),參見(jiàn) 列表 2-3 給出了 LARGE_INTEGER 和 ULARGE_INTEGER 類(lèi)型定義。該類(lèi)型可分別表示有符號(hào)和無(wú)符號(hào)的整數(shù)。通過(guò)使用 LONGLONG/ULONGLONG (針對(duì) 64 位的 QuadPart 成員)或者 LONG/ULONG (針對(duì) 32 位的 HighPart 成員)來(lái)控制有無(wú)符號(hào)。
typedef union _LARGE_INTEGER
{
struct
{
ULONG LowPart;
LONG HighPart;
}
LONGLONG QuadPart;
} LARGE_INTEGER,*PLARGE_INTEGER;
typedef union _ULARGE_INTEGER
{
struct
{
ULONG LowPart;
ULONG HighPat;
}
ULONGLONG QuadPat;
} ULARGE_INTEGER,*PULARGE_INTEGER;
列表 2-3. LARGE_INTEGER 和 ULARGE_INTEGER
字符串
在 Win32 程序設(shè)計(jì)中,常使用 PSTR 和 PWSTR 來(lái)分別代替 ANSI 和 Unicode 字符串。 PSTR 被定義為 CHAR* , PWSTR 則定義為 WCHAR* (參見(jiàn)表 2-3 )。通過(guò)源文件中是否出現(xiàn) #define UNICODE 指示符,附加的 PTSTR 類(lèi)型分別對(duì)應(yīng) PSTR 或 PWSTR ,這樣就可通過(guò)單一的源文件來(lái)維護(hù)應(yīng)用程序的 ANSI 和 Unicode 版本。基本上,這些字符串都是簡(jiǎn)單的指向以零結(jié)尾的 CHAR 或 WCHAR 類(lèi)型的數(shù)組。如果你常和 Windows 2000 內(nèi)核打交道,你將必須處理一種很不同的字符串表示法。最常見(jiàn)的類(lèi)型是 UNICODE_STRING ,這是一個(gè)第三方類(lèi)型, 列表 2-4 給出了它的定義。
typedef struct _UNICODE_STRING
{
USHORT Length;
USHORT MaximumLength;
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;
列表 2-4. 字符串類(lèi)型
Length 成員給出了當(dāng)前字符串的字節(jié)數(shù)(注意,不是字符個(gè)數(shù)), MaximumLength 成員指出 Buffer 所指向內(nèi)存塊的大小,實(shí)際的字符串?dāng)?shù)據(jù)將保存在該內(nèi)存塊中。注意, MaximumLength 也是字節(jié)數(shù)。由于 Unicode 字符寬度為 16 位,所有其長(zhǎng)度總是字符個(gè)數(shù)的兩倍。通常, Buffer 指向的字符串都是以零結(jié)尾的。然而,有些內(nèi)核模塊可能僅依賴(lài)字符串的長(zhǎng)度值,而不考慮結(jié)尾的 0 字符,這種情況下要小心處理。
Windows 2000 的 ANSI 字符串叫做 STRING ,如 列表 2-4 中所示。為了方便, nedef.h 分別定義了 ANSI_STRING 和 OEM_STRING 來(lái)代表使用不同代碼頁(yè)的 8 位字符串( ANSI 默認(rèn)代碼頁(yè)為 1252 ; OEM 默認(rèn)代碼頁(yè)為 437 )。不過(guò), Windows 2000 內(nèi)核使用的主要字符串類(lèi)型還是 UNICODE_STRING 。你可能偶爾會(huì)碰到 8 位字符串。
在 圖 2-3 中,我給出了兩個(gè)典型的 UNICODE_STRING 示例。左面的那個(gè)包含兩個(gè)獨(dú)立的內(nèi)存塊:一個(gè) UNICODE_STRING 結(jié)構(gòu)和一個(gè) 16 位 PWCHAR 類(lèi)型的 Unicode 字符數(shù)組。這或許是在 Windows 2000 數(shù)據(jù)類(lèi)型中最常見(jiàn)的字符串類(lèi)型。右邊的是一種頻繁出現(xiàn)的特殊類(lèi)型,在此種類(lèi)型中, UNICODE_STRING 和 PWCHAR 數(shù)組位于同一個(gè)內(nèi)存塊中。有些內(nèi)核函數(shù),包括 Native API 內(nèi)部使用的一些函數(shù),都在連續(xù)的內(nèi)存塊中保存其返回的結(jié)構(gòu)化的系統(tǒng)信息。如果數(shù)據(jù)中包含字符串,它們通常都存儲(chǔ)在嵌入式的 UNICODE_STRING 中,如 圖 2-3 右面所示。例如, NtQuerySystemInformation() 函數(shù)就頻繁使用了這種特殊的字符串類(lèi)型。
這些字符串結(jié)構(gòu)不許要手工維護(hù), ntdll.dll 和 ntoskrnl.exe 導(dǎo)出了一組豐富的運(yùn)行時(shí) API 函數(shù),如 RtlCreateUnicodeString() 、 RtlInitUnicodeString() 、 RtlCopyUnicodeString() 等。通常, STRING 和 ANSI_STRING 也有對(duì)應(yīng)的等價(jià)函數(shù)。這些函數(shù)中的大多數(shù)在 DDK 中都有文檔記錄,但其中有些沒(méi)有。不過(guò),很容易猜出這些未文檔化的字符串函數(shù)的功能及其需要的參數(shù)。使用 UNICODE_STRING 、 STRING 的好處是,可以隱示的指定 Buffer 可容納的字符串的大小。如果你給一個(gè)函數(shù)傳遞了一個(gè) UNICODE_STRING 類(lèi)型的字符串,而該函數(shù)需要適當(dāng)改變?cè)撟址闹担@可能會(huì)增加該字符串的長(zhǎng)度,那這個(gè)函數(shù)只需要簡(jiǎn)單的檢查 MaximumLength 成員就可確定是否有足夠的空間來(lái)存放結(jié)果。
結(jié)構(gòu)體
個(gè)別的幾個(gè)內(nèi)核 API 函數(shù)期望其處理的對(duì)象有一個(gè)合適的 OBJECT_ATTRIBUTES 結(jié)構(gòu), 列表 2-5 給出了該結(jié)構(gòu)的定義。例如, NtOpenFile() 函數(shù)沒(méi)有 PWSTR 或 PUNICODE_STRING 參數(shù)用來(lái)指定要打開(kāi)的文件的路徑。替代的, OBJECT_ATTRIBUTES 結(jié)構(gòu)中的 ObjectName 成員給出了該路徑。通常,設(shè)置該結(jié)構(gòu)很容易。除 ObjectName 外,還需要設(shè)置 Length 和 Attributes 成員。 Length 必須設(shè)置為: sizeof(OBJECT_ATTRIBUTES) , Attributes 是一組來(lái)自 ntdef.h 的 OBJ_* 常量。例如,如果你對(duì)象名稱(chēng)不區(qū)分大小寫(xiě)的話(huà), Attributes 應(yīng)設(shè)置為 OBJ_CASE_INSENSITIVE 。當(dāng)然, ObjectName 成員是一個(gè) UNICODE_STRING 指針,并不是通常的 PWSTR 。剩余的成員只要不使用,都可設(shè)置為 NULL 。
typedef struct _OBJECT_ATTRIBUTES
{
ULONG Length;
HANDLE RootDirectory;
PUNICODE_STRING ObjectName;
ULONG Attributes;
PVOID SecurityDescriptor;
PVOID SecurityQualityOfService;
} OBJECT_ATTRIBUTES, *POBJECT_ATTRIBUTES;
列表 2-5. OBJECT_ATTRIBUTES 結(jié)構(gòu)
OBJECT_ATTRIBUTES 結(jié)構(gòu)僅描述函數(shù)使用的數(shù)據(jù)的細(xì)節(jié), 列表 2-6 給出的 IO_STATUS_BLOCK 結(jié)構(gòu)則用于記錄對(duì)用戶(hù)所提交的操作的處理結(jié)果。該結(jié)構(gòu)很簡(jiǎn)單 ---Staus 成員存放一個(gè) NTSTATUS 類(lèi)型的代碼,其值可能是 STATUS_SUCCESS 或定義于 ntstatus.h 中的所有可能的錯(cuò)誤代碼。 Information 成員在操作成功的情況下,提供與操作相關(guān)的附加數(shù)據(jù)。比如,如果函數(shù)返回一個(gè)數(shù)據(jù)塊,該成員將被設(shè)置為該數(shù)據(jù)塊的大小。
typedef struct _IO_STRATUS_BLOCK
{
NTSTATUS Status;
ULONG Information;
} IO_STATUS_BLOCK, *PIO_STATUS_BLOCK;
列表 2-6. IO_STATUS_BLOCK 結(jié)構(gòu)
另一個(gè)常見(jiàn)的 Windows 2000 數(shù)據(jù)類(lèi)型是 LIST_ENTRY 結(jié)構(gòu),列表 2-7 給出了該結(jié)構(gòu)的定義。內(nèi)核使用該結(jié)構(gòu)將所有對(duì)象維護(hù)在一個(gè)雙向鏈表中。一個(gè)對(duì)象分屬多個(gè)鏈表是很常見(jiàn)的, Flink 成員是一個(gè)向前鏈接,指向下一個(gè) LIST_ENTRY 結(jié)構(gòu), Blink 成員則是一個(gè)向后鏈接,指向前一個(gè) LIST_ENTRY 結(jié)構(gòu)。通常情況下,這些鏈表都成環(huán)形,也就是說(shuō),最后一個(gè) Flink 指向鏈表中的第一個(gè) LIST_ENTRY 結(jié)構(gòu),而第一個(gè) Blink 指向最后一個(gè)。這樣就很容易雙向遍歷該鏈表。如果一個(gè)程序要遍歷整個(gè)鏈表,它需要保存第一個(gè) LIST_ENTRY 結(jié)構(gòu)的地址,以判斷是否已遍歷了整個(gè)鏈表。如果鏈表僅包含一個(gè) LIST_ENTRY 結(jié)構(gòu),那么該 LIST_ENTRY 結(jié)構(gòu)必須引用其自身,也就是說(shuō), Flink 和 Blink 都指向其自己。
typedef struct _LIST_ENTRY
{
struct _LIST_ENTRY *Flink;
struct _LIST_ENTRY *Blink;
} LIST_ENTRY, *PLIST_ENTRY;
列表 2-7. LIST_ENTRY 結(jié)構(gòu)
圖 2-4 展示了對(duì)象鏈表各成員間的關(guān)系。對(duì)象 A1 、 A2 、 A3 屬于同一鏈表。注意, A3 的 Flink 指向 A1 , A1 的 Blink 指向 A3 。最右邊的對(duì)象 B1 僅有一個(gè)成員,因此,其 Flink 和 Blink 都指向相同的地址 --- 即對(duì)象 B1 的地址。典型的雙向鏈表的例子是進(jìn)程和線(xiàn)程鏈表。內(nèi)部變量 PsActiveProcessHead 就是一個(gè) LIST_ENTRY 結(jié)構(gòu),位于 ntoskrnl.exe 的 .data 節(jié)中。該變量指向系統(tǒng)進(jìn)程列表的首部(通過(guò)其 Blink 指針)。你可以在內(nèi)核調(diào)試器中使用 dd PsActiveProcessHead 來(lái)獲取該鏈表的首部,然后通過(guò)其 Flink 和 Blink 指針遍歷整個(gè)鏈表(仍使用 dd 命令)。當(dāng)然,這種探測(cè) Windows 進(jìn)程的方法非常繁瑣,但這可使你深入的觀察基本的系統(tǒng)結(jié)構(gòu)。 Windows 2000 Native API 提供了更便利的方法來(lái)枚舉進(jìn)程,如 NtQuerySystemInformation() 函數(shù)。
typedef struct _CLIENT_ID
{
HANDLE UniqueProcess;
HANDLE UniqueThread;
} CLIENT_ID, *PCLIENT_ID;
列表 2-8. CLIENT_ID 結(jié)構(gòu)
處理進(jìn)程和線(xiàn)程的 API 函數(shù),如: NtOpenProcess() 和 NtOpenThread() ,使用 列表 2-8 給出的 CLIENT_ID 結(jié)構(gòu)來(lái)和特定的進(jìn)程、線(xiàn)程相關(guān)聯(lián)。盡管其類(lèi)型為 HANDLE ,實(shí)際上,從嚴(yán)格的意義上來(lái)講 UniqueProcess 和 UniqueThread 成員并不是句柄( Handle ),它們都是整數(shù)型的進(jìn)程 ID 和線(xiàn)程 ID 。即標(biāo)準(zhǔn) Win32 函數(shù) GetCurrentProcessId() 和 GetCurrentThreadId() 返回的 DWORD 類(lèi)型的數(shù)值。
Windows 2000 執(zhí)行體( Executive )還使用 CLIENT_ID 結(jié)構(gòu)在全局范圍內(nèi)標(biāo)識(shí)唯一的線(xiàn)程。例如,如果你使用內(nèi)核調(diào)試器的 !thread 命令來(lái)顯示當(dāng)前線(xiàn)程參數(shù),就會(huì)在輸出的第一行看到類(lèi)似“ Cid ppp.ttt ”的顯示,其中“ ppp ”就是 CLIENT_ID 的 UniqueProcess 成員,而“ ttt ”則代表 UniqueThread ,如下所示。注意,我用黑體標(biāo)出的地方。
kd> !thread
THREAD 83a51ba8 Cid 0a5c.0e64 Teb: 7ffdd000 Win32Thread: e14f4eb0 RUNNING on processor 0
Not impersonating
DeviceMap e20fb208
Owning Process 83a14708
Wait Start TickCount 906512 Elapsed Ticks: 68570
Context Switch Count 266 LargeStack
UserTime 00:00:00.0312
KernelTime 00:00:00.0015
。。。。。。。。。。。。。。。。。。。
Native API 的接口
對(duì)于內(nèi)核模式的驅(qū)動(dòng)程序,使用 Native API 的接口非常平常,就像在用戶(hù)模式下的程序中調(diào)用 Win32 API 一樣。 Windows 2000 DDK 提供的頭文件和庫(kù)包含了所有在調(diào)用 ntoskrnl.exe 導(dǎo)出的 Native API 時(shí)所需的信息。而另一方面, Win32 SDK 幾乎不支持在程序中調(diào)用 ntdll.dll 導(dǎo)出的 Native API 。我說(shuō)“幾乎不”是因?yàn)?Win32 SDK 實(shí)際上提供了一個(gè)重要的東西:導(dǎo)入庫(kù) ntdll.lib ,該文件位于 Program FilesMicrosoft Platfrom SDKLib 目錄中。如果沒(méi)有這個(gè)庫(kù),將很難調(diào)用 ntdll.dll 導(dǎo)出的函數(shù)。
譯注:
你需要安裝 Windows 2000 DDK 才能獲得 ntdll.lib
可以到 http://www.microsoft.com/msdownload/platformsdk/sdkupdate/ 下載最新的 SDK
將 NTDLL.DLL 導(dǎo)入庫(kù)添加到工程中
在你能成功的編譯和鏈接在用戶(hù)模式下使用 ntdll.dll 導(dǎo)出函數(shù)的代碼之前,你必須考慮如下的四個(gè)重點(diǎn):
1. SDK 的頭文件中,沒(méi)有包含這些函數(shù)的原型。
2. SDK 文件中缺少這些函數(shù)使用的幾個(gè)基本的數(shù)據(jù)類(lèi)型。
3. SDK 和 DDK 頭文件并不兼容,你不能將 #include <ntddk.h> 加入你的 Win32 C 源代碼文件中。
4. ntdll.lib 并沒(méi)有加入 Visual C/C++ 默認(rèn)的導(dǎo)入庫(kù)列表中
最后一個(gè)問(wèn)題很容易解決,只需要編輯工程的設(shè)置屬性,或者將如下內(nèi)容加入你的源代碼中, #pragma comment(linker,”defaultlib:ntdll.lib”) ,像在前面的 Windows 2000 運(yùn)行時(shí)庫(kù)一節(jié)解釋的那樣,這會(huì)在編譯時(shí),將 ntdll.dll 加入鏈接器的 /defaultlib 設(shè)置中。解決缺失的定義比較困難。因?yàn)椴豢赡軐?SDK 和 DDK 頭文件整合到 C 程序中,最簡(jiǎn)易的解決方法是寫(xiě)一格自定義的頭文件,在該頭文件中包含所有調(diào)用 ntdll.dll 導(dǎo)出函數(shù)必須的定義。幸運(yùn)的是,你不需要開(kāi)始這項(xiàng)工作了,在本書(shū)光盤(pán)的 srccommoninclude 目錄下的 w2k_def.h 文件包含了你所需要的所有基本信息。該頭文件將在第六、七兩章中扮演重要角色。因?yàn)樗辉O(shè)計(jì)為可同時(shí)兼容用戶(hù)模式和內(nèi)核模式的工程,在用戶(hù)模式代碼中,你必須在 #include <w2k_def.h> 之前插入 #define _USER_MODE_ ,以加入僅出現(xiàn)在 DDK 中的一些定義。
有關(guān) Native API 編程的很多詳細(xì)信息都已經(jīng)出版,目前看來(lái),針對(duì) Windows 2000 平臺(tái)的好書(shū)是 Gary Nebbett's 的《 Windows NT/2000 Native API Reference 》。該書(shū)提供的示例程序較少,但它覆蓋了 Windows NT/2000 平臺(tái)上的所有 Native API ,還包括這些函數(shù)需要的數(shù)據(jù)結(jié)構(gòu)定義以及其他必須的一些結(jié)構(gòu)定義。
將在第六章介紹的 w2k_call.dll 示例庫(kù),演示了 w2k_def.h 的典型用法。第六章還將討論另一種在用戶(hù)模式進(jìn)入 Windows 2000 內(nèi)核的方法,此種方法不受限于 Native API 。事實(shí)上,這種技巧也可用于 ntoskrnl.exe ,對(duì)于所有加載到內(nèi)核空間的模塊,只要它們導(dǎo)出了函數(shù)或者可以和 .dbg 或 .pdb 符號(hào)文件相匹配都可以使用此方法。如你所見(jiàn),在本書(shū)剩余章節(jié)中還有很多有趣的信息。但是,在我們到達(dá)那兒之前,我們會(huì)繼續(xù)討論一些基本的概念和技術(shù)。
