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

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

翻译:Kendiv( fcczj@263.net )

更新: Tuesday, February 22, 2005

 

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

 

内存Dump工具----本书示例程序

现在你已经学完了复杂和让人困惑的内存Spy设备的IOCTL函数的代码,你可能想看这些函数运行起来是什么样子。因此,我创建了一个控制台模式的工具,名为:“SBS Windows 2000 Memory Spy”,该工具会加载Spy驱动程序,根据命令行出入的参数,它会调用多个IOCTL函数。该程序的可执行文件为:w2k_mem.exe,其源代码位于本书光盘的/src/w2k_mem目录下。

 

命令行格式

你可以从光盘中运行内存Spy工具:d:/bin/w2k_mem.exe,这里d:应该由你的CD-ROM盘符代替。如果无参数启动w2k_mem.exe,将会列出冗长的命令信息,如示列4-1所示。W2k_mem.exe基本的命令体系是:一个命令包含一个或多个数据请求,每个命令都至少提供一个线性基址,内存Dump将从该地址开始。如果你愿意还可指定内存块的大小,不过这是可选的,内存块的默认大小是256。命令中的内存大小必须以“#”开始。可通过增加多个选项来改变命令的默认行为。一个选项包括一个单字符的选项ID和一个“+”或“-”前缀。“+”或“-”表示允许或禁止该选项。默认情况下,所有选项都是允许的。

 

// w2k_mem.exe

// SBS Windows 2000 Memory Spy V1.00

// 08-27-2000 Sven B. Schreiber

// sbs@orgon.com

 

Usage: w2k_mem { { [+option|-option] [/<path>] } [#[[0]x]<size>] [[0]x]<base> }

 

<path> specifies a module to be loaded into memory.

Use the +x/-x switch to enable/disable its startup code.

If <size> is missing, the default size is 256 bytes.

 

Display address options (mutually exclusive):

 

   +z -z   zero-based display         on / OFF

   +r -r   physical RAM addresses     on / OFF

 

Display mode options (mutually exclusive):

 

   +w -w   WORD  data formatting      on / OFF

   +d -d   DWORD data formatting      on / OFF

   +q -q   QWORD data formatting      on / OFF

 

Addressing options (mutually exclusive):

 

   +t -t   TEB-relative addressing    on / OFF

   +f -f   FS-relative  addressing    on / OFF

   +u -u   user-mode   FS:[<base>]    on / OFF

   +k -k   kernel-mode FS:[<base>]    on / OFF

   +h -h   handle/object resolution   on / OFF

   +a -a   add bias  to  last base    on / OFF

   +s -s   sub bias from last base    on / OFF

   +p -p   pointer  from last block   on / OFF

 

System status options (cumulative):

 

   +o -o   display OS  information    on / OFF

   +c -c   display CPU information    on / OFF

   +g -g   display GDT information    on / OFF

   +i -i   display IDT information    on / OFF

   +b -b   display contiguous blocks  on / OFF

 

Other options (cumulative):

 

   +x -x   execute DLL startup code   on / OFF

 

Example: The following command displays the first 64

bytes of the current Process Environment Block (PEB)

in zero-based DWORD format, assuming that a pointer to

the PEB is located at offset 0x30 inside the current

Thread Environment Block (TEB):

 

   w2k_mem +t #0 0 +pzd #64 0x30

 

Note: Specifying #0 after +t causes the TEB to be

addressed without displaying its contents.

示列4-1.   内存Spy工具的帮助信息

 

每个命令行所执行的数据请求不等同于选项,数据大小的说明,路径或任何其他的命令修饰成分。命令中的每个无格式的数字都被假定是一个线性地址,并且将从该地址开始,按16进制显示其内容。数字默认按10进制格式解释,如果有前缀“0x”或“x.”则按照16进制格式解释。

 

如果提供一些简单的示例,很容易掌握w2k_mem.exe采用的复杂命令行选项,下面就给出一些:

l         w2k_mem 0x80400000 显示从线性地址0x80400000开始的256个字节,产生的内容可能会类似于示列4-2。顺便说一下,这是ntoskrnl.exeDOS stub(注意开始的“MZID)。

 

l         w2k_mem #0x40 0x80400000显示从线性地址0x80400000开始的64个字节,#0x40表示要显示的块大小为64

 

l         w2k_mem +d #0x40 0x80400000 在前一命令的基础上,按照32位的DWORD Chunk来显示,这就是+d选项的作用。在同一个命令中,首先出现的+选项将会一直有效,除非使用相应的-选项或使用其互斥选项。如+d的互斥选项为:+w+q

 

l         w2k_mem +wz #0x40 0x10000 +d –z 0x200000 包含两个数据请求。首先,线性地址范围:0x10000----0x1003F中的内容将按照16WORD格式来显示,随后的0x20000---0x2003F按照32DWORD格式显示(见示列4-3)。第一个请求中还包含一个+z选项,该选项将使“Address”列的数字从0开。在第二个请求中,通过-z选项,禁用了从0开始的显示模型。

 

l         w2k_mem +rd #4096 0xC0300000 DWORD格式显示起始于0xC0300000的系统页目录。+r选项表示在“Address”列中以物理内存地址代替线性地址。

 

现在,你应该基本上明白命令行格式是如何工作的了。在下一小节中,将详细讨论一些比较特别的选项和特性。它们中的大多数会改变对出现在它们之前的地址的解释方式。在默认情况下,指定的地址是一个线性基址,内存Dump将从那里开始。选项:+t+f+u+k+h+a+s+p将以多种方式改变这种默认解释方式。

 

示列4-2.   数据请求示列

 

 

示列4-3.  以指定格式显示数据

 

TEB相关的地址

进程中的每个线程都有其自己的线程环境块(Thread Environment BlockTEB),系统在此TEB中保存频繁使用的线程相关的数据。在用户模式下,当前线程的TEB位于独立的4KB段,可通过CPUFS寄存器来访问该段。而在内核模式下,FS却指向不同的段,下面将解释之。一个进程的所有TEB都以堆栈的方式,存放在从0x7FFDE000开始的线性内存中,每4KB为一个完整的TEB,不过该内存区域是向下扩展的。这意味着,第二个线程的TEB的地址将是0x7FFDC000,这和堆栈类似。在第七章,我们会详细讨论TEB的内容和进程环境块(Process Environment BlockPEB)的地址0x7FFDF000(参见列表7-187-19)。这里知道TEB的存在,而且知道其地址由FS寄存器给出就足够了。

 

如果在一个地址之前出现了+t选项,w2k_mem.exe将自动把FS段的基地址加到该地址上,示列4-4展示了w2k_mem  +dt  #0x38 0命令执行后的输出。这一次我省略了w2k_mem.exe输出的标题和状态信息。

示列4-4.  显示第一个线程环境块(TEB

 

FS相关的地址

我前面已经提到过,在用户和内核模式下,FS将指向不同的段。+t选项将选择用户模式下FS所指向的地址,+f选项则使用在内核模式下FS指向的地址。当然,Win32应用程序没有办法获取该地址,因此,需要再次请求Spy设备。w2k_mem.xe调用IOCTL函数SPY_IO_CPU_INFO,来读去CPU的状态信息,这包括所有段寄存器在内核模式下的值。从此开始,所有的事情和+t选项相同。

 

内核模式的FS指向另一个线程相关的结构,Windows 2000内核回经常使用该结构,其名称为:内核的处理器控制区域(Kernel’s Processor Control RegionKPCR)。该结构在讨论IOCTL函数SPY_IO_OS_INFO时已经提及过,在第七章我们还会再次提到它(见列表7-16)。再次强调,现在你只需要知道该结构存在于线性地址0xFFDFF000处即可,使用+f选项就可访问它。在示列4-5中,我使用命令:w2k_mem  +df  #0x54  0来演示,在实际情况下,使用+f选项的结果。

 

示列4-5.  显示内核的处理器控制区域(KPCR

 

FS:[Base]寻址方式

在察看Windows 2000内核代码时,你会经常遇到像MOV  EAX,  FS:[18h]这样的指令。这些指令用于取出属于TEBKPCR的成员的值,或者是属于其他包含在FS段中的结构体的成员的值。它们中的大多数都指向其他的内部结构。命令行选项+u+k允许你;+u表示使用用户模式下的FS段;+k表示使用内核模式下的FS段。例如,命令:w2k_mem +du #0x1E8 0x30(见示列4-6)将在用户模式下,从位于FS:[30h]处的内存块中转储(dump488个字节。而命令:w2k_mem +dk #0x1C 0x20(见示列4-7)将显示由内核模式下的FS:[20h]指向的内存块的前28个字节,这实际上是指向KPRCB的一个指针。如果你不知道PEBKPRCB是什么,不要着急,读完本书你就会一目了然了。

 

示列4-6.  显示进程环境块(PEB

 

 

示列4-7.  显示内核的处理器控制区域(KPRCB

 

句柄/对象 解析

假设你有一个对象句柄,而且你想要看看该句柄对应的对象在内存中是什么样子。如果你使用+h选项,你就会发现完成这一任务太简单了,该选项将调用Spy设备的SPY_IO_HANDLE_INFO函数(见列表4-26)来查找给定句柄的对象体(Object Body)。Windows 2000对象世界是一个令人惊讶的主题,我将在第七章深入剖析它。所以,现在先把它丢掉一边去。

 

相对寻址

有时使用这种寻址方式可以很容易显示一系列内存块,这些内存块间隔相同大小的字节。这很有可能,比如,一个数组结构,像朵线程程序中的TEB堆栈。+a+s选项通过将给定的地址解释为一个偏移量,来进行对寻址。这两个选项的区别是:+aadd bias)将产生一个正的偏移量,+ssubtract bias)则产生一个负的偏移量。示列4-8展示了命令:w2k_mem +d #32 0xC0000000 +a 4096 4096的输出结果。它将取出三个连续4KB页中的前32个字节,起始地址为:0xC0000000,系统的页表就位于此处。注意,+a选项接近命令的结尾处。它将使随后的“4096”将被解释为偏移量,该偏移量将被加到前面的基地址上。

 

示列4-8.  页表样本

 

示列4-8还展示了如果传入一个无效的线性地址会发生什么。显然,第一对页表涉及的4MB地址范围:0x00000000----0x003F00000x00400000-----0x007F0000是有效的。而第三对页表则是无效的。w2k_mem.exe会通过显示一个空表来反映这一现实。程序知道那个地址范围是有效的,因为Spy设备的SPY_IO_MEMORY_DATA函数将此信息放入作为结果的SPY_MEMORY_DATA结构中(参见列表4-25)。

 

 

间接寻址

我所钟爱的命令选项之一就是:+p,因为在我准备这本书的时候,它为我节省了很多打字的时间。该选项和+u+k的工作方式类似,但不使用FS段,而是使用先前显示过的数据块。这是一个很棒的特性,如果你想向下寻找链表上的对象,例如,读取下一个成员的地址,随该命令一起,键入一个新的命令等等,通过简单在命令中加入+p选项和一系列偏移量,就可以指定下一个对象的链接在前一个16进制Dump表中的位置。

 

示列4-9中,我使用该选项来向下遍历当前活动进程的链表。首先,我告诉通过内核调试器获取系统内部变量PsActiveProcessHead的地址,该地址是一个LIST_ENTRY结构,用于标识进程链表的开始。LIST_ENTRY结构中包含一个Flink(向前指针)成员和一个Blink(向后)成员。Flink成员位于偏移量0处,Blink成员位于偏移量4处(参见列表2-7)。命令:w2k_mem  #8  +d 0x8046A180 +p 0 0 0 0首先转储PsActiveProcessHead(这是一个LIST_ENTRY结构),然后从+p选项出开始转为间接寻址。选项后的四个0是用来告诉w2k_mem.exe提取前一个数据块中偏移量为0的值,这正是Flink所在的位置。注意,示列4-9中的Blink成员在偏移量为4的位置上,它指向前一个LSIT_ENTRY之后,就像我们期望的那样。

 

译注:

对于w2k_mem  #8  +d  0x8046A180  +p  0 0 0 0 命令

0x8046A180需要由你自己系统中的PsActiveProcessHead的地址来替代。

 

可通过内核调试器来查找PsActiveProcessHead的地址,我在这里使用的是livekd,命令为:ln PsActiveProcessHead

 

如果命令中加入了足够的值为0的参数,16进制转储最终会回到PsActiveProcessHead,它用来标识进程链表的开始和结束。就像第二章里解释的那样,Windows 2000维护的双向链表实际上是一个环;也就是说,链表中最后一个成员的Flink将指向链表中的第一个成员,而链表中第一个成员的Blink指向最后一个成员。

 

示列4-9.  向下遍历活动进程链表

 

 

加载模块

有时你可能会想dump一个模块在内存中的映像,但是该模块还没有映射到w2k_mem.exe进程的线性地址空间。通过使用/<path>+x选项来显示的加载一个指定模块就可解决这一问题。每个前缀为斜线(“/”)的命令项将被解释为模块的全路径名,w2k_mem.exe将尝试使用Win32 API函数LoadLibraryEx()从该路径出加载模块。默认情况下,将使用加载选项DON’T_RESOLVE_DLL_REFERENCES,这会使模块被加载到内存中,但不会被初始化。对于一个DLL,这意味着它的DllMian()入口点将不会被调用。同样,在该DLL的导入节中指定的依赖模块也都不会被加载。然而,如果你在路径参数之前,指定了+x选项,那么模块将在加载后进行完整的初始化。注意,有些模块可能会拒绝在w2k_mem.exe进程的上下文环境中被初始化。例如,内核模式的设备驱动程序就不能在使用+x选项的情况下,被加载到内存中。

 

加载和显示一个模块一般需要经过两个操作步骤,如示列4-10所示。首先,你应该加载模块,而不显示任何数据,以找出系统分配给该模块的基地址。幸运的是,只要在此期间,没有其他的模块加入到进程中,模块的加载地址就将是唯一的,因此,接下来尝试通过相同的基地址来加载模块。在示列4-10中,我加载了内核模式的设备驱动程序nwrdr.sys,它是微软的NetWare重定向器。在我的系统里没有使用IPX/SPX,因此,默认没有加载该驱动程序。

 

列表4-10.  加载和显示一个模块映像(Module Image

 

 

         特别的是,你可以使用/<path>选项将另一个程序的.exe文件读入内存。不过,该模块可能会被加载到一个不常见的地址,因为它的首选加载地址已经被w2k_mem.exe占用了。此外,你不能执行加载的应用程序,+x选项只能用于DLL,不会对其他类型的模块起作用。


……………
待续………….

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值