您的位置:首页技术文章
文章详情页

《Undocumented Windows 2000 Secrets》翻译 --- 第四章(3)

【字号: 日期:2023-08-27 15:07:50浏览:28作者:猪猪

第四章 探索 Windows 2000 的内存管理机制

翻译: Kendiv ( fcczj@263.net )

更新: Sunday, February 17, 2005

声明:转载请注明出处,并保证文章的完整性,本人保留译文的所有权利。

Memory Spy Device 示例

微软对 Windows NT 和 2000 说的最多的就是它们是 安全的操作系统 。它们不但在网络环境中加入了用户验证系统,同时还加强了系统的稳健性( robustness ),以进一步降低错误应用程序危及系统完整性的概率,这些错误的程序可能使用了非法的指针或者在其内存数据结构以外的地方进行了写入操作。这些在 Windows 3.x 上都是十分让人头疼得问题,因为 Windows 3.x 系统和所有的应用程序共享单一的内存空间。 Windows NT 为系统和应用程序内存以及并发的进程提供了完全独立的内存空间。每个进程都有其独立的 4GB 地址空间,如 4-2 所示。无论何时发生任务切换,当前的地址空间都会被换出( switch out ),同时另一个被映射进来,它们各自使用不同的段寄存器、页表和其他内存管理结构。这种设计避免了应用程序无意中修改另一个程序所使用的内存。由于每个进程必然会要求访问系统资源,所以在 4GB 空间中总是包含一些系统数据和代码,并采用了一个不同的技巧来保护这些内存区域不被恶意程序代码所覆写( overwritten )。

Windows 2000 的内存分段

Windows 2000 继承了 Windows NT 4.0 的基本内存分段模型,默认情况下,该模型将 4GB 地址空间划分为相等的两块。低一半的地址范围是: 0x00000000 ---- 0x7FFFFFFF ,其中包含运行于用户模式(用 Intel 的术语来说是,是特权级 3 或 Ring 3 )的应用程序的数据和代码。高一半的地址范围是: 0x80000000 --- 0xFFFFFFFF ,默认全部保留给系统使用,位于这一范围的代码运行于内核模式(即特权级为 0 或 Ring 0 )。特权级决定了代码可以执行什么操作以及可以访问那一个块内存。这意味着对于低特权级的代码来说,会被禁止执行某些 CPU 指令或访问某些内存区域。例如,如果一个用户模式下的程序触及了任何 0x80000000 (即 4GB 地址空间中的高一半)以上的地址,系统会抛出一个异常并同时终止该程序的运行,不会给其任何机会。

图 4-5. 用户模式下不能访问 0x80000000 以上的地址

图 4-5 展示了程序试图读取 0x80000000 地址时的情况。这种严格的访问限制对于系统的完整性来说是好事,但对于调试工具就不是什么好消息了,因为调试工具需要访问所有可用内存。幸运的是,存在着一个简单的方法:采用内核驱动程序,和系统本身类似,它也运行于高特权级(即 Ring 3 ),因此它们可以执行所有的 CPU 指令,可访问所有的内存区域。这其中的诀窍就是将一个 Spy 驱动程序注入系统,用它来访问需要的内存,并将读到的内容发送到它的搭档程序,该搭档程序会在用户模式下等待。当然,内核驱动程序不能读取虚拟内存地址,而且得不到分页机制的支持。因此,这样的驱动程序必须在访问一个地址之前小心的检查它,以避免出现蓝屏死机( Blue Screen Of Death , BSOD )。相对于应用程序引发的异常(仅会终止出现问题的程序),驱动程序引发的异常会停止整个系统,并强迫进行重启。

设备 I/O 控制 Dispatcher ( Device I/O Control Dispatcher

本书光盘上有一个通用 Spy Device 的源代码,该 Spy Device 作为内核驱动程序实现。可以在 srcw2k_spy 目录下找到它的源代码。这个设备基于第三章的驱动向导所产生的驱动程序骨架。其用户模式下的接口为 w2k_spy.sys , w2k_spy.sys 采用 Win32 的设备 I/O 控制( IOCTL ),在第三章中曾简要的谈过 IOCTL 。 Spy Device 定义了一个名为 Devicew2k_spy 的设备和一个符号链接 DosDevicesw2k_spy ,定义符号链接是为了能在用户模式下访问该设备。非常可笑的是符号链接的名字空间居然是 DosDevice ,而在这儿,我们使用的可不是一个 DOS 设备驱动。这就像历史上有名的 root ,原本是叫做石头的 J 。安装好符号链接后,驱动程序就可以被用户模式下的任何模块打开了,方法是:使用 Win32 API 函数 CreateFile() ,路径为 .w2k_spy 。字符串 . 是通用转义符,表示本地设备。例如, .C : 指向本地硬盘上的 C :分区。从 SDK 的文档中可了解 CreateFile() 的更多细节。

该驱动程序的头文件有一部分已经由 列表 4-2 列表 4-5 给出。这个文件有些像 DLL 的头文件:它包含在编译过程中,模块所需的定义,而且还为客户端程序提供了足够的接口信息。 DLL 和驱动程序以及客户端程序都包含相同的头文件,但每个模块会取出各自所需的定义以完成正确的操作。不过,头文件的这种两面性给内核驱动程序带来的麻烦要远多于给 DLL 带来的,这都是因为微软给驱动程序提供的特殊开发环境所致。不幸的是, DDK 中的头文件并不能和 SDK 中的 Win32 文件兼容。至少在 C 工程,二者的头文件是不能混合使用的。这样的结果就是陷入了僵局,此种情况下,内核驱动可以访问的常量、宏和数据类型对于客户端程序来说是却是无法使用的。因此, w2k_spy.c 定义了一个名为 _W2K_SPY_SYS_ 的标志常量, w2k_spy.h 通过 #ifdef…..#else…..#endif 来检查该常量是否出现,以决定需要补充哪些缺少的定义。这意味着,所有出现在 #ifdef _W2K_SPY_SYS_ 之后的定义仅可被驱动代码看到,位于 #else 之后的则专用于客户端程序。 w2k_spy.h 中条件语句之外的所有部分被这两个模块同时使用。

在第三章中,在讨论我的驱动向导时,我给出了向导生成的驱动程序骨架,如 列表 3-3 所示。由该驱动向导生成的新的驱动工程均开始于 DeviceDispatcher() 函数。该函数接受一个设备上下文指针,以及一个指向 IRP ( I/O 请求包)的指针,该 IRP 随后将会被分派。向导的样板代码已经处理了基本的 I/O 请求: IRP_MJ_CREATE 、 IRP_MJ_CLEANUP 和 IRP_MJ_CLSE ,当客户要关闭一个设备时,会给该设备发送这些 I/O 请求。 DeviceDispatcher() 针对这些请求只是简单的返回 STATUS_SUCCESS ,因此设备可以被正确的打开和关闭。对于某些设备,这种动作已经足够,但有些设备还需要初始化和清理代码,这些代码多少都有些复杂。对于其他的请求,第三章中的驱动程序骨架总是返回 STATUS_NOT_IMPLEMENTED 。扩展该骨架代码的第一步是修改默认的动作,以便处理更多的 I/O 请求。就像 w2k_spy.sys 的主要任务之一:通过 IOCTL 调用将在用户模式下无法访问的数据发送给 Win32 应用程序,因此首先需要在 DeviceDispatcher() 中添加处理 IRP_MJ_DEVICE_CONTROL 的函数。 列表 4-6 给出了更新后的代码。

NTSTATUS DeviceDispatcher (PDEVICE_CONTEXT pDeviceContext,

PIRP pIrp)

{

PIO_STACK_LOCATION pisl;

DWord dInfo = 0;

NTSTATUS ns = STATUS_NOT_IMPLEMENTED;

pisl = IoGetCurrentIrpStackLocation (pIrp);

switch (pisl->MajorFunction)

{

case IRP_MJ_CREATE:

case IRP_MJ_CLEANUP:

case IRP_MJ_CLOSE:

{

ns = STATUS_SUCCESS;

break;

}

case IRP_MJ_DEVICE_CONTROL:

{

ns = SpyDispatcher (pDeviceContext,

pisl->Parameters.DeviceIoControl.IoControlCode,

pIrp->AssociatedIrp.SystemBuffer,

pisl->Parameters.DeviceIoControl.InputBufferLength,

pIrp->AssociatedIrp.SystemBuffer,

pisl->Parameters.DeviceIoControl.OutputBufferLength,

&dInfo);

break;

}

}

pIrp->IoStatus.Status = ns;

pIrp->IoStatus.Information = dInfo;

IoCompleteRequest (pIrp, IO_NO_INCREMENT);

return ns;

}

列表 4-6. 为 Dispatcher 增加处理的 IRP_MJ_DEVICE_CONTROL 函数

列表 4-6 中的 IOCTL 处理代码非常简单,它仅调用了 SpyDispatcher() ,并将一个扩展后的 IRP 结构和当前 I/O 堆栈位置作为参数传递给 SpyDispatcher() 。 SpyDispatcher() 在 列表 4-7 中给出,该函数需要如下的参数:

l pDeviceContext 一个驱动程序的设备上下文指针。驱动程序向导提供了的基本 Device_Context 结构,该结构中包含驱动程序和设备对象指针(参见 列表 3-4 )。不过, Spy 驱动程序在该结构中增加了一对私有成员。

l dCode 指定了 IOCTL 编码,以确定 Spy 设备需要执行的命令。一个 IOCTL 编码是一个 32 位整数,它包含 4 个位域,如 4-6 所示。

l pInput 指向一个输入缓冲区,用于给 IOCTL 提供输入数据。

l dInput 输入缓冲区的大小。

l pOutput 指向用来接收 IOCTL 输出数据的输出缓冲区。

l dOutput 输出缓冲区的大小

l pdInfo 指向一个 DWORD 变量,该变量保存写入输出缓冲区中的字节数。

图 4-6. 设备 I/O 控制编码的结构

根据所用的 IOCTL 使用的传输模式,输入 / 输出缓冲区会以不同的方式从系统传递给驱动程序。 Spy 设备使用已缓存的 I/O ( buffered I/O ),系统将输入数据复制到一个安全的缓冲区(此缓冲区由系统自动分配)中,在返回时,将指定数目的数据从同样的系统缓冲区中复制到调用者提供的输出缓冲区中。一定要牢记:在这种情况下,输入和输出缓冲区是重叠的,因此 IOCTL 的处理代码必须在向输出缓冲区中写入任何数据之前,保存所有它稍后可能需要使用的输入数据。系统 I/O 缓冲区的指针保存在 IRP 结构中的 SystemBuffer 成员中(参见 ntddk.h )。输入 / 输出缓冲区的大小保存在一个不同的地方,它们是 IRP 的参数成员 DeviceIoControl 的一部分,分别为 InputBufferLength 和 OutputBufferLength 。 DeviceIoControl 子结构还通过其 IoControlCode 成员提供了 IOCTL 编码。有关 Windows NT/2000 的 IOCTL 的传输模式的信息以及它们如何传入 / 传出数据,请参考我在 Windows Developer's Journal(Schreiber 1997) 发表的文章“ A Spy Filter Driver for Windows NT ”。

NTSTATUS SpyDispatcher (PDEVICE_CONTEXT pDeviceContext,

DWORD dCode,

PVOID pInput,

DWORD dInput,

PVOID pOutput,

DWORD dOutput,

PDWORD pdInfo)

{

SPY_MEMORY_BLOCK smb;

SPY_PAGE_ENTRY spe;

SPY_CALL_INPUT sci;

PHYSICAL_ADDRESS pa;

DWORD dValue, dCount;

BOOL fReset, fPause, fFilter, fLine;

PVOID pAddress;

PBYTE pbName;

HANDLE hObject;

NTSTATUS ns = STATUS_INVALID_PARAMETER;

MUTEX_WAIT (pDeviceContext->kmDispatch);

*pdInfo = 0;

switch (dCode)

{

case SPY_IO_VERSION_INFO:

{

ns = SpyOutputVersionInfo (pOutput, dOutput, pdInfo);

break;

}

case SPY_IO_OS_INFO:

{

ns = SpyOutputOsInfo (pOutput, dOutput, pdInfo);

break;

}

case SPY_IO_SEGMENT:

{

if ((ns = SpyInputDword (&dValue,

pInput, dInput))

== STATUS_SUCCESS)

{

ns = SpyOutputSegment (dValue,

pOutput, dOutput, pdInfo);

}

break;

}

case SPY_IO_INTERRUPT:

{

if ((ns = SpyInputDword (&dValue,

pInput, dInput))

== STATUS_SUCCESS)

{

ns = SpyOutputInterrupt (dValue,

pOutput, dOutput, pdInfo);

}

break;

}

case SPY_IO_PHYSICAL:

{

if ((ns = SpyInputPointer (&pAddress,

pInput, dInput))

== STATUS_SUCCESS)

{

pa = MmGetPhysicalAddress (pAddress);

ns = SpyOutputBinary (&pa, PHYSICAL_ADDRESS_,

pOutput, dOutput, pdInfo);

}

break;

}

case SPY_IO_CPU_INFO:

{

ns = SpyOutputCpuInfo (pOutput, dOutput, pdInfo);

break;

}

case SPY_IO_PDE_ARRAY:

{

ns = SpyOutputBinary (X86_PDE_ARRAY, SPY_PDE_ARRAY_,

pOutput, dOutput, pdInfo);

break;

}

case SPY_IO_PAGE_ENTRY:

{

if ((ns = SpyInputPointer (&pAddress,

pInput, dInput))

== STATUS_SUCCESS)

{

SpyMemoryPageEntry (pAddress, &spe);

ns = SpyOutputBinary (&spe, SPY_PAGE_ENTRY_,

pOutput, dOutput, pdInfo);

}

break;

}

case SPY_IO_MEMORY_DATA:

{

if ((ns = SpyInputMemory (&smb,

pInput, dInput))

== STATUS_SUCCESS)

{

ns = SpyOutputMemory (&smb,

pOutput, dOutput, pdInfo);

}

break;

}

case SPY_IO_MEMORY_BLOCK:

{

if ((ns = SpyInputMemory (&smb,

pInput, dInput))

== STATUS_SUCCESS)

{

ns = SpyOutputBlock (&smb,

pOutput, dOutput, pdInfo);

}

break;

}

case SPY_IO_HANDLE_INFO:

{

if ((ns = SpyInputHandle (&hObject,

pInput, dInput))

== STATUS_SUCCESS)

{

ns = SpyOutputHandleInfo (hObject,

pOutput, dOutput, pdInfo);

}

break;

}

case SPY_IO_HOOK_INFO:

{

ns = SpyOutputHookInfo (pOutput, dOutput, pdInfo);

break;

}

case SPY_IO_HOOK_INSTALL:

{

if (((ns = SpyInputBool (&fReset,

pInput, dInput))

== STATUS_SUCCESS)

&&

((ns = SpyHookInstall (fReset, &dCount))

== STATUS_SUCCESS))

{

ns = SpyOutputDword (dCount,

pOutput, dOutput, pdInfo);

}

break;

}

case SPY_IO_HOOK_REMOVE:

{

if (((ns = SpyInputBool (&fReset,

pInput, dInput))

== STATUS_SUCCESS)

&&

((ns = SpyHookRemove (fReset, &dCount))

== STATUS_SUCCESS))

{

ns = SpyOutputDword (dCount,

pOutput, dOutput, pdInfo);

}

break;

}

case SPY_IO_HOOK_PAUSE:

{

if ((ns = SpyInputBool (&fPause,

pInput, dInput))

== STATUS_SUCCESS)

{

fPause = SpyHookPause (fPause);

ns = SpyOutputBool (fPause,

pOutput, dOutput, pdInfo);

}

break;

}

case SPY_IO_HOOK_FILTER:

{

if ((ns = SpyInputBool (&fFilter,

pInput, dInput))

== STATUS_SUCCESS)

{

fFilter = SpyHookFilter (fFilter);

ns = SpyOutputBool (fFilter,

pOutput, dOutput, pdInfo);

}

break;

}

case SPY_IO_HOOK_RESET:

{

SpyHookReset ();

ns = STATUS_SUCCESS;

break;

}

case SPY_IO_HOOK_READ:

{

if ((ns = SpyInputBool (&fLine,

pInput, dInput))

== STATUS_SUCCESS)

{

ns = SpyOutputHookRead (fLine,

pOutput, dOutput, pdInfo);

}

break;

}

case SPY_IO_HOOK_WRITE:

{

SpyHookWrite (pInput, dInput);

ns = STATUS_SUCCESS;

break;

}

case SPY_IO_MODULE_INFO:

{

if ((ns = SpyInputPointer (&pbName,

pInput, dInput))

== STATUS_SUCCESS)

{

ns = SpyOutputModuleInfo (pbName,

pOutput, dOutput, pdInfo);

}

break;

}

case SPY_IO_PE_HEADER:

{

if ((ns = SpyInputPointer (&pAddress,

pInput, dInput))

== STATUS_SUCCESS)

{

ns = SpyOutputPeHeader (pAddress,

pOutput, dOutput, pdInfo);

}

break;

}

case SPY_IO_PE_EXPORT:

{

if ((ns = SpyInputPointer (&pAddress,

pInput, dInput))

== STATUS_SUCCESS)

{

ns = SpyOutputPeExport (pAddress,

pOutput, dOutput, pdInfo);

}

break;

}

case SPY_IO_PE_SYMBOL:

{

if ((ns = SpyInputPointer (&pbName,

pInput, dInput))

== STATUS_SUCCESS)

{

ns = SpyOutputPeSymbol (pbName,

pOutput, dOutput, pdInfo);

}

break;

}

case SPY_IO_CALL:

{

if ((ns = SpyInputBinary (&sci, SPY_CALL_INPUT_,

pInput, dInput))

== STATUS_SUCCESS)

{

ns = SpyOutputCall (&sci,

pOutput, dOutput, pdInfo);

}

break;

}

}

MUTEX_RELEASE (pDeviceContext->kmDispatch);

return ns;

}

列表 4-7. Spy 驱动程序的内部命令 Dispatcher

#define CTL_CODE (DeviceType, Function, Method, Access)

(( (DeviceType) << 16 ) | ( Access << 14 ) | ( (Function) << 2 ) (Method) )

列表 4-8. 用来构建 I/O 控制编码的 CTL_CODE() 宏

DDK 的主要头文件 ntddk.h 和 SDK 中的 Win32 文件 winioctl.h 均定义了一个简单但非常有用的宏 ---- CTL_CLOSE() ,如 列表 4-8 所示。该宏可方便的建立 4-6 所示的 IOCTL 编码。该编码中的四个部分分别服务于以下四个目的:

1. DeviceType 这是一个 16 位的设备类型 ID 。 ntddk.h 列出了一对预定义的类型,由符号常量 FILE_DEVICE_* 表示。 0x0000 到 0x7FFF 保留给微软内部使用,开发人员可使用 0x8000 到 0xFFFF 。 Spy 驱动程序定义了它自己的设备 ID : FILE_DEVICE_SPY ,其值为 0x8000 。

2. 2 位的访问检查值用来确定 IOCTL 操作所需的访问权限。可能的值有: FILE_ANY_ACCESS (0) , FILE_READ_ACCESS (1) , FILE_WRITE_ACCESS (2) 和最后两个的组合: FILE_READ_ACCESS | FILE_WRITE_ACCESS (3) 。详见 ntddk.h 。

3. 12 个位的 ID 表示所选择的操作函数,所选操作将由设备来执行。 0x0000 到 0x7FFF 保留给微软内部使用,开发人员可使用 0x8000 到 0xFFFF 。 Spy 设备采用的 IOCTL 函数 ID 位于 0x8000 到 0xFFFF 。

4. 传输模式占用 2 个位,可在四个可用 I/O 传输模式中选择一个,这四个模式为: METHOD_BUFFERED (0) , METHOD_IN_DIRECT (1) , METHOD_OUT_DIRECT (2) 和 METHOD_NETTHER (3) ,可在 ntddk.h 中找到这些定义。 Spy 设备针对所有请求使用 METHOD_BUFFERED ,这是一个非常安全但有些慢的模式,因为数据需要在客户端和系统缓冲区之间进行复制。因为 Memory Spy 对 I/O 的处理速度并不敏感,所以选择安全是一个不错的注意。如果你希望知道其他模式的细节,请参考我在 Windows Developer's Journal(Schreiber 1997) 发表的文章“ A Spy Filter Driver for Windows NT ”

表 4-2 列出了 w2k_spy.sys 支持的所有 IOCTL 函数。 0 到 10 的函数 ID 为最基本的内存探测函数,绝大部分的任务都会用到它们;本章稍候将讨论它们。剩余的函数 ID 从 11 到 23 分属于不同的 IOCTL 组,在下一章我们将讨论它们,在下一章,我们将讨论 Native API hook 和在用户模式下调用内核。注意某些 IOCTL 编码需要写入权限,由第 15 号位表示(参见 4-6 )。确切的说,所有形如 0x80006nnn 的 IOCTL 命令只需读权限,而形如 0x8000Ennn 的命令需要读 / 写权限。典型的要求读权限的例子是 CreateFile() ,它通过指定 dwDesiredAccess 参数为 GENERIC_READ 和 GENERIC_WRITE 的组合来打开设备。

表 4-2 最左面的函数名称同样出现在 SpyDispatcher() (见 列表 4-7 )中那个庞大的 switch/case 语句中。这些函数首先获取设备的 dispatcher mutex ,这样就能保证,如果一个以上的客户端或一个多线程的程序和设备通讯时,在同一时间只有一个请求被执行。 MUTEX_WAIT() 是 KeWaitForMutexObject() 的外包宏( wrapper marco ), KeWaitForMutexObject() 至少需要 5 个参数。 KeWaitForMutexObject() 本身也是一个宏,它将传入的参数向前传递给 KeWaitForSingleObject() 。 列表 4-9 给出了 MUTEX_WAIT() 以及它的伙伴 MUTEX_RELEASE() 和 MUTEX_INITIALIZE() 。在 mutex 对象变为有信号( signaled )状态后,根据接收到的 IOCTL 编码, SpyDispatcher() 会转向不同的分支,每个分支都包含多种简单的代码序列。

表 4-2. w2k_spy.sys 支持的 IOCTL 函数

函数名称

ID

IOCTL 编码

SPY_IO_VERSION_INFO

0

0x80006000

返回 Spy 的版本信息

SPY_IO_OS_INFO

1

0x80006004

返回操作系统的版本信息

SPY_IO_SEGMENT

2

0x80006008

返回一个段的属性

SPY_IO_INTERRUPT

3

0x8000600C

返回一个中断门的属性

SPY_IO_PHYSICAL

4

0x80006010

线性地址转换为物理地址

SPY_IO_CPU_INFO

5

0x80006014

返回特殊 CPU 寄存器的值

SPY_IO_PDE_ARRAY

6

0x80006018

返回位于 0xC0300000 的 PDE 数组

SPY_IO_PAGE_ENTRY

7

0x8000601C

Return the PDE or PTE of a linear address

SPY_IO_MEMORY_DATA

8

0x80006020

返回内存块中的内容

SPY_IO_MEMORY_BLOCK

9

0x80006024

返回内存块中的内容

SPY_IO_HANDLE_INFO

10

0x80006028

从句柄中查找对象属性

SPY_IO_HOOK_INFO

11

0x8000602C

返回有关 Native API Hook 的信息

SPY_IO_HOOK_INSTALL

12

0x8000E030

安装 Native API Hook

SPY_IO_HOOK_REMOVE

13

0x8000E034

移除一个 Native API Hook

SPY_IO_HOOK_PAUSE

14

0x8000E038

暂停 / 恢复 Hook 协议

SPY_IO_HOOK_FILTER

15

0x8000E03C

允许 / 禁止 Hook 协议过滤器

SPY_IO_HOOK_RESET

16

0x8000E040

清除 Hook 协议

SPY_IO_HOOK_READ

17

0x8000E044

从 Hook 协议中读取数据

SPY_IO_HOOK_WRITE

18

0x8000E048

向 Hook 协议中写入输入

SPY_IO_MODULE_INFO

19

0x8000E04C

返回已加载模块的信息

SPY_IO_PE_HEADER

20

0x8000E050

返回 IMAGE_NT_HEADERS 数据

SPY_IO_PE_EXPORT

21

0x8000E054

返回 IMAGE_EXPORT_DirectorY 数据

SPY_IO_PE_SYMBOL

22

0x8000E058

返回导出的系统符号的地址

SPY_IO_CALL

23

0x8000E05C

调用已加载模块中的一个函数

#define MUTEX_INITIALIZE(_mutex)

KeInitializeMutex

(&(_mutex), 0)

#define MUTEX_WAIT(_mutex)

KeWaitForMutexObject

(&(_mutex), Executive, KernelMode, FALSE, NULL)

#define MUTEX_RELEASE(_mutex)

KeReleaseMutex

(&(_mutex), FALSE)

列表 4-9. 管理 Kernel-Mutex 的宏

SpyDispatcher() 使 用一对帮助函数来读取输入参数,以获取被请求的数据,并将产生的数据写入调用者提供的输出缓冲区中。就像前面提到的,内核模式的驱动程序总是过分挑剔的对 待它接受到的来自用户模式的参数。以驱动程序的观点来看,所有用户模式下的代码都是有害的,它们除了让系统崩溃就什么都不知道了。这种多少有些多疑症的观 点并不是荒谬的 ---- 仅有很小的比率会导致整个系统立即终止,同时出现蓝屏。因此,如果一个客户端程序说:“这是我的缓冲区 ----- 它最多可容纳 4,096 个字节”,驱动程序不会接受这个缓冲区 ---- 即使该缓冲区指向有效的内存,并且其大小也是正确的。在 IOCTL 的可缓冲的 I/O 模式( Buffered I/O )下(如果 IOCTL 编码的模式部分为 METHOD_BUFFERED ),系统会很小心的检查并分配一个足够容纳所有输入 / 输出数据的缓冲区。然而,其他的 I/O 传输模式,尤其是 METHOD_NETTHER ,驱动程序会接受原始的用户模式的缓冲区指针。

标签: Windows系统