分享
 
 
 

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

王朝system·作者佚名  2006-11-24
窄屏简体版  字體: |||超大  

本章对于 Windows 2000 Native API 的讨论,主要集中在这些 API 和系统模块之间的关系,将重点介绍 Windows 2000 采用的中断机制。 Windows 2000 利用此机制将对内核服务的请求从用户模式向内核模式传递。另外, Win32K 接口和一些与 Native API 相关的主要运行时库也会被提及,同时还将介绍一些经常使用的数据类型。

有关 Windows 2000 架构的详细讨论已经很多。许多有关 Windows NT 的讨论同样适用于 Windows 2000 。《 Inside Windows NT 》( Custer 1993, Solomon 1998 )的第一、二版都是有关此方面的好书,同样的还有《 Inside Windows 2000 》( Solomon and Russinovich 2000 )。

NT*() 和 Zw*() 函数集

有关 Windows 2000 架构的一个有趣的事实是:它模拟了多个操作系统。 Windows 2000 内置三个子系统来支持 Win32 、 POSIX 和 OS/2 应用程序。 Win32 子系统是最流行的一个,因此它更多的被开发人员和操作系统所关照。在 Windows 9x 中, Win32 接口实际上是作为整个系统的基础结构来实现的。但是, Windows 2000 的设计却有很大不同。尽管 Win32 子系统包含一个名为 kernel32.dll 的系统模块,但这并不是实际的操作系统内核。它仅仅是 Win32 子系统的一个基本组件。在很多编程书籍中, Windows NT/2000 的软件开发被简化为与 Win32 API 打交道的工作, NT 平台暴露出的一个隐藏的事实是存在另一个更为基础的调用接口: Native API 。相信编写 kernel-mode driver 或 file system driver 的开发人员已经对 Native API 非常熟悉了,因为 kernel-mode 模块位于更低的系统层,在那里子系统是不可见的。然而,你并不需要到驱动程序一层才能访问此接口 ---- 即使一个普通的 Win32 应用程序也可在任何时候调用 Native API 。这没什么技术上的限制 ---- 仅仅是微软不支持此种应用程序开发模式而已。因此,有关此话题的信息并不是很多, neither SDK nor the DDK make the Native API available to Win32 Application.

未文档化的级别

本书中的多数东西都来自被称为未文档化的信息。这通常意味着微软没有公开发布这些信息。然而,未文档化也存在几个级别,这是因为可能被公布的有关庞大的操作系统(如 Windows 2000 )的信息非常的多。我个人的系统分类如下:

l 正式文档 :这些信息来自微软出版的书、文件或者开发工具。大多数重要信息来自 SDK 、 DDK 和 MSDN 。

l 半文档化的( Semidocumented ) :尽管不是正式文档,但这些信息还是可以从微软正式发布的文件中挖掘出来的。例如, Windows 2000 的很多函数和结构体并没有在 SDK 或 DDK 文档中提到,但出现在一些头文件或示列程序中。以 Windows 2000 为例,很多重要的半文档化信息都源自头文件 ntddk.h 和 ntdef.h ,这两个文件都是 DDK 的一部分。

l 未文档化,但并没有隐藏 :这些信息不能在任何官方文档和开发文档中找到,但其中的一部分对调试工具是可用的。可执行文件或符号文件中的所有符号化信息都属于这一部分。最好的例子是内核调试器的 !processfields 和 !threadfields 命令,这两个命令会给出两个未文档化的结构: EPROCESS 和 ETHREAD 的成员名称及其偏移量。

l 完全未文档化的 :微软很好的隐藏了某些信息,要获得它们只能通过逆向工程和推理。此类信息包含很多实现细节的信息,没有人认为 Windows 2000 开发人员需要关注它们,但是这些信息对于系统开发人员和开发调试软件的人来说却非常宝贵。挖掘系统内部的信息是非常困难的,但同样是非常有趣的。

本书讨论的 Windows 2000 的内部细节覆盖了上述系统分类的后三个。

系统服务分配器( System Service Dispatcher )

Win32 子系统和 Native API 之间的关系可以由 Win32 核心模块与 Windows 2000 内核模块之间的依赖关系很好的解释。 图 2-1 展示了模块间的依赖关系,方框表示系统模块,箭头表示模块间的依赖关系。如果一个箭头从模块 A 指向模块 B ,这表示 A 依赖于 B ,即,模块 A 调用 B 中的函数。模块由双向箭头连接,表示二者之间相互依赖。在 图 2-1 中,模块: user32.dll 、 advapi32.dll 、 gdi32.dll 、 rpcrt4.dll 以及 kernel32.dll 实现了基本的 Win32 API 。当然,还有其他的 DLL (如 version.dll 、 shell32.dll 和 comctl32.dll )也为 Win32 API 提供支持,为了更清晰些,我省略了它们。 图 2-1 表现出的一个特性非常有趣,所有的 Win32 API 调用最后都转移到了 ntdll.dll ,而 ntdll.dll 又将其转移到了 ntoskrnl.exe 。

Ntdll.dll 是一个操作系统组件,它为 Native API 准确地提供服务, ntdll.dll 是 Native API 在用户模式下的前端。 Native API 真正的接口在 ntoskrnl.exe 中实现。从其文件名可以猜出它就是 NT 操作系统内核。事实上,内核模式驱动程序对系统服务的请求多数时候都会进入该模块。 Ntdll.dll 的主要任务就是为运行于用户模式的程序提供一个确定的内核函数的子集,这其中就包括 Win32 子系统 DLLs 。在 图 2-1 中,从 ntdll.dll 指向 ntoskrnl.exe 的箭头旁标注的 INT 2eh 表示 Windows 2000 使用此中断将 CPU 特权级从用户模式切换到内核模式。开发内核( kernel-mode )模式程序的人员认为用户模式的代码是具有攻击性的、充满错误的和危险的。因此,必须让这些代码远离内核函数。而通过在调用 API 的过程中将特权级别从用户模式切换到内核模式是一种可控制这些问题的方式。调用程序从来不可能触及内核,它只能察看它们。

例如,由 kernel32.dll 导出的 Win32 API 函数 DeviceIoControl() 最终会调用由 ntdll.dll 导出的 NtDeviceIoControlFile() 。通过反编译该函数会发现此函数令人惊讶的实现方式 — 它是如此的简单! 示列 2-1 展示了这些。首先, CPU 寄存器 EAX 被装入了一个“魔术”数字 0x38 ,这是一个分派 ID 。接下来,寄存器 EDX 被设置指向堆栈中的某处,其地址为堆栈指针 ESP 加上 4 ,因此, EDX 将指向堆栈中返回地址的后面,该返回地址在进入 NtDeviceIoControlFile() 时将被立即保存下来。显而易见, EDX 指向的位置是用来临时存放传递进来的参数的。接下来的指令是一个简单的 INT 2eh ,该指令将跳转到中断描述符表( Interrupt Descriptor Table,IDT )的 0x2e 位置上存放的中断处理例程( interrupt handler )中。这看上去是不是很熟悉?事实上,这有些像 DOS 下的 INT 21h 调用。然而, Windows 2000 的 INT 2eh 接口要远比一个简单的 API 调用有用,分配器( dispatcher )利用它从用户模式进入内核模式。请注意,这种模式切换方式是 x86 处理器特有的。在 Alpha 平台上,有不同的方式来实现此种功能。

NtDeviceIoControlFile:

mov eax, 38h

lea edx, [esp+4]

int 2Eh

ret 28h

示列 2-1. ntdll.NtDeviceIoControlFile() 的实现方式

Windows 2000 Native API 由 248 个函数组成,这些函数都采用上述方式进入内核。与 Windows NT 4.0 相比多出了 37 个。你很容易在 ntdll.dll 的导出列表中通过 Nt 前缀来认出它们。 Ntdll.dll 总共导出了 249 个这样的符号。多出的那个函数是 NtCurrentTeb() ,该函数是一个纯粹的用户模式函数,它无需进入内核。 附录 B 中的 表 B-1 列出了所有可用的 Native API 。该表同时还指出那个函数是由 ntoskrnl.exe 导出的。令人奇怪的是,在处于内核模式的模块中,只能调用 Native API 的一个子集。另一方面, ntoskrnl.exe 导出了两个 ntdll.dll 没有提供的 Nt* 符号(指以 Nt 开头的符号): NtBuildNumber 和 NtGlobalFlag 。这两个符号都没有指向函数的入口地址,而是指向 ntoskrnl.exe 中的变量。驱动模块( driver module )可以使用 C 编译器的 extern 关键字来导入这些变量。 Window 2000 采用此种方式导出了很多变量,稍后我将给出一个示例代码来使用其中的几个。

你可能会奇怪为什么 表 B-1 (位于附录 B 中)分别为 ntdll.dll 和 ntoskrnl.exe 提供了两列,其名称分别为: ntdll.Nt* 、 ntdll.Zw* 和 ntoskrnl.Nt* 、 ntoskrnl.Zw* 。原因是,这两个模块导出了两组相互关联的 Native API 符号。在 表 B-1 (位于附录 B 中)的最左列给出了所有名字中包含 Nt 前缀的符号。另一个集合包含相似的名字,不过由 Zw 前缀代替了 Nt 。反编译 ndll.dll 可看出每对符号都指向相同的代码。这看起来似乎是浪费内存。然而,如果你反编译 ntoskrnl.exe ,你就会发现 Nt* 符号指向实际的代码而 Zw* 指向 INT 2eh stubs (如 示列 2-1 列出的)。这意味着 Zw* 函数集合将从用户模式转入内核模式,而 Nt* 符号直接指向的代码会在模式切换后被执行。

表 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 函数。

// Import SDT pointer

extern PSERVICE_DESCRIPTOR_TABLE KeServiceDescriptorTable;

// Create SDT reference

PSERVICE_DESCRIPTOR_TABLE psdt = KeServiceDescriptorTable;

列表 2-2 访问系统服务描述符表

SDT 中的每个 SST 的 ServiceTable 成员都是一个指针,指向一个由函数指针构成的数组,此函数指针的类型为: NTPROC ,这为 Native API 提供了占位符,这种方式和在 Win32 编程中使用的 PROC 类型很相似。 NTPROC 的定义在前面的 列表 2-1 中给出。 Native API 函数通常返回一个 NTSTATUS 类型的代码并且使用 NTAPI 调用方式, NTAPI 实际上就是 _stdcall 。 ServiceLimit 成员保存在 ServieTable 数组中发现的入口地址的个数。在 Windows 2000 中,其默认值为 248 。 ArgumentTable 成员是一个 BTYE 类型的数组,它和 ServiceTable 所指的数组一一对应,并给出其中每个函数指针所需的参数在调用者的堆栈中的字节数。此信息随 EDX 寄存器提供的指针一起使用。当内核从调用者的堆栈中复制参数到自己的堆栈时就需要这些信息。 CounterTable 成员在 Windows 2000 的 Free Build 版中不被使用。在 Debug Build 版中,该成员指向一个 DWORD 类型的数组,作为每个函数的使用计数器( usage counters )。 This information can be used for profiling purposes.

使用 Windows 2000 的内核调试器可方便的显示 SDT 中的内容。如果你还没有设置好这个有用的程序,那请参考第一章。在 示列 2-2 中,我首次使用了 dd KeServiceDescriptorTable 命令。调试器会将此公开符号解析为 0x8046AB80 ,同时显示该地址之后的 32 个 DWORD 的 16 进制转储。不过仅有前面的四行才是有意义的,它们分别对应 列表 2-1 中的四个 SDT 成员。为了更清晰些,它们都将以黑体显示。如果你仔细观察,你会发现第五行与第一行十分相像,这是另一个 SDT 吗?这是测试内核调试器的 ln 命令的好机会。在示列 2-2 中,在显示完 KeServ

[1] [2] 下一页

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有