1 《Undocumented Windows 2000 Secrets》翻译 --- 第二章( 三 )


表 B-1 (位于附录 B 中)中有两件事需要特别注意 。首先,NtCurrentTeb() 函数没有对应的 Zw* 函数 。这不是什么大问题,因为 ntdll.dll 以相似的方式导出 Nt* 和 Zw* 函数 。其次,ntoskrnl.exe 不再一贯的成对的导出 Nt/Zw 函数 。其中的一些仅以 Nt* 或 Zw* 的形式出现 。我不知道为什么会这样,我猜测 ntoskrnl.exe 仅导出了在 Windows 2000 DDK 中有文档记录的函数以及其它系统模块必须的那些函数 。注意,保留的 Native API 函数仍然实现于 ntoskrnl.exe 的内部 。这些函数并没有公开的进入点,但可通过 INT 2eh 到达他们 。
服务描述符表( The Service Descriptor Tables )
从 示例 2-1 给出的反编译代码可看出,INT 2eh 随同传入 CPU 寄存器 EAX 和 EDX 的两个参数一起被调用 。我已经提到过 EAX 中的“魔术”数字是一个分派 ID。除 NtCurrentTeb() 之外的所有 Native API 都采用此种方式,处理 INT 2eh 的代码必须确定每个调用将被分配到那个函数 。这就是提供分派 ID 的原因 。位于 ntoskrnl.exe 中的中断处理例程将 EAX 中的数值作为一个索引来查询一个特定的表 。这个表被称作系统服务表( System Service Table, SST )该表对应的 C 结构体 ---SYSTEM_SERVICE_TABLE 的定义在 列表 2-1 中给出 。在该列表中还包含 SERVICE_DESCRIPTOR_TABLE 结构的定义,该结构共有四个 SST 类型的数组,其中的前两个用于特定目的 。
尽管上述的两个表是系统基本的数据类型,但他们在 Windows 2000 DDK 中 并没有相应的文档记载,本书中出现的许多代码片断都包含未文档化的数据类型和函数 。因此,不能保证这些信息是完全真实可信的 。所有符号化的信息,如结构名 称、结构成员和参数都是如此 。在创建这些符号时,我试图使用适当的名称,这些名称基于从已知符号的一个很小的子集(包括从符号文件中得到的那些)中得出的 命名方案 。然而,在很多场合这种启发式方法并不成功 。只有在原始的代码中包含所有的信息,但我无法得到它们 。实际上,我并不打算阅读这些源代码,因为这需 要和微软签订一个 NDA ( Non-Disclosure Agreement,,不可泄漏协议),由于该 NDA 的限制,将很难写出一本有关非文档化信息的书 。
typedef NTSTATUS (NTAPI*NTPROC)();
typedef NTPROC* PNTPROC;
#define NTPROC_ sizeof(NTPROC)
typedef struct _SYSTEM_SERVICE_TABLE
{
PNTPROC ServiceTable; // array of entry points
PDOWRD CounterTable; // array of usage counters
DWord ServiceLimit; // number of table entries
PBYTE ArgumentTable; // array of byte counts
}
SYSTEM_SERVICE_TABLE,
*PSYSTEM_SERVICE_TABLE,
**PPSYSTEM_SERVICE_TABLE;
//-----------------------------------------------------------------------------------------------------------
typedef struct _SERVICE_DESCRIPTOR_TABLE
{
SYSTEM_SERVICE_TABLE ntoskrnl; // ntoskrnl.exe ( native api )
SYSTEM_SERVICE_TABLE win32k; // win32k.sys (gdi/user support)
SYSTEM_SERVICE_TABLE Table3; // not used
SYSTEM_SERVICE_TABLE Table4; // not used
}
SYSTEM_DESCRIPTOR_TABLE,
*PSYSTEM_DESCRIPTOR_TABLE,
**PPSYSTEM_DESCRIPTOR_TABLE;
列表 2-1 系统服务描述符表的结构定义
现在,回到 SDT ( Service Descriptor Table )的秘密上来 。从 列表 2-1 给出的该结构的定义可看出该结构的头两个数组保留给了 ntoskrnl.exe 和 Win32 子系统(位于 win32k.sys )中的内核模式( kernel-mode )部分 。来自 gdi32.dll 和 user32.dll 的调用都通过 Win32k 的系统服务表( SST )进行分派 。Ntolkrnl.exe 导出了一个指针(符号为 KeServiceDescriptorTable )指向其主服务描述符表( Main SDT ) 。内核还维护了一个替代的 SDT,其名称为: KeServiceDescriptorTableShadow,但这个 SDT 并没有被导出 。从处于内核模式的模块中访问主服务描述符表( SDT )非常容易,你只需要两个 C 指令,如 列表 2-2 所示 。首先是由 extern 关键字修饰的变量说明,这告诉链接器该变量并不包含在此模块中,而且不需要在链接时解析相应的符号名称 。当该模块被加载到进程的地址空间后,针对该符号的引用才会动态连接到相应的模块中 。列表 2-2 中第二个 C 指令就是这样的一个引用 。将类型为 PSERVER_DESCRIPTOR_TABLE 的变量赋值为 KeServiceDescriptorTable 时,就会和 ntoskrnl.exe 建立一个动态连接 。这很像调用一个 DLL 中的 API 函数 。

推荐阅读