《Windows PE》2.2 加载PE文件一

在上一节中,我们静态分析了磁盘上的PE文件。而本节我们将动态的分析加载到内存中的PE文件。

本节必须掌握的知识点:

        加载PE文件的过程

        加载PE文件

        PE头各个部分的结构定义

2.2.1 加载PE文件的过程

■在Windows系统中,加载PE(Portable Executable)文件是通过以下步骤完成的:

●加载器的启动: 当一个可执行文件(PE文件)被执行时,操作系统的加载器(Loader)负责启动加载过程。加载器是操作系统的一部分,负责加载和执行可执行文件。

●DOS Stub的执行: 加载器首先会执行PE文件中的DOS Stub部分(如果存在)。DOS Stub是为了兼容DOS环境而保留的一段代码,它在现代的Windows系统中一般没有实际的功能,所以加载器会忽略它并继续执行下一步。

●加载PE文件头:加载器读取PE文件的文件头(PE Header)。文件头包含了PE文件的基本信息,如文件类型、目标平台、节表的位置和大小等。

●校验PE文件的合法性:加载器会对PE文件进行校验,以确保文件的完整性和合法性。它会检查PE文件的签名、文件格式、大小等,并验证文件的完整性,以防止恶意或损坏的文件被加载执行。

●分配内存空间:加载器根据PE文件头中的信息,为PE文件分配内存空间。它会根据文件头中的ImageBase字段确定PE文件在内存中的基址,并为PE文件的各个节(Sections)分配对应的内存空间。

●加载节表和节数据:加载器读取PE文件的节表(Section Table),该表描述了PE文件中各个节的位置、大小和属性等。加载器根据节表的信息,将各个节的数据从文件中加载到相应的内存空间中。

●重定位(可选):如果PE文件包含了重定位表(Relocation Table),加载器会对其中的重定位项进行处理。重定位表记录了在将PE文件装入内存时需要修正的地址,以适应实际加载地址的差异。

●解析导入表:加载器会解析PE文件的导入表(Import Table),找到PE文件所依赖的其他模块(DLL)并加载它们。加载器会根据导入表中的信息,定位并加载所需的外部函数,并将函数的地址填充到IAT(Import Address Table)中。

●执行入口点:加载器最后会跳转到PE文件的入口点(Entry Point),该入口点是PE文件中的一个特定函数,标识程序的起始执行位置。加载器会传递一些参数给入口点函数,并开始执行PE文件的代码。

通过以上步骤,Windows系统能够成功加载和执行PE文件。加载器负责将PE文件的代码、数据和资源等加载到内存中,并完成相关的初始化工作,使得程序能够正常运行。

当然,加载PE文件只是创建一个进程的一部分。从磁盘PE文件到内存中PE文件的执行还需要很多复杂的环节和流程。本书不再继续深入讨论,有兴趣的读者可以查阅操作系统内核有关进程创建方面的资料。

2.2.2 加载PE文件

       ■4GB虚拟地址空间

       在32位Windows系统中,每创建一个32位进程,都会分配4GB的虚拟内存空间。其中低2GB空间为用户空间,具有用户态R3访问权限,用户空间中的虚拟地址范围通常是从0x00000000到0x7FFFFFFF。高2GB空间为内核空间,具有内核态R0访问权限,内核空间的虚拟地址范围是从0x80000000到0xFFFFFFFF。

如图2-4所示,在 32 位 Windows 中,可用的虚拟地址空间共计为 232 字节(4 GB)。 通常,较

低的 2 GB 用于用户空间,而上 2 GB 用于系统空间。      

图2-4 32位Windows 4GB虚拟地址空间

      

●使用虚拟地址访问内存有几个好处:

1.程序可以使用连续的虚拟地址范围来访问物理内存中的大型非连续内存缓冲区。

2.程序可以使用一系列虚拟地址来访问大于可用物理内存的内存缓冲区。当物理内存不足时,内存管理器将物理内存页(通常)4 KB保存到磁盘文件。 系统根据需要在物理内存和磁盘之间移动数据或代码页。

3.不同进程使用的虚拟地址是隔离的。一个进程中的代码无法更改另一个进程或操作系统正在使用的物理内存。

进程可用的虚拟地址范围称为进程的虚拟地址空间。 每个用户模式进程都有其各自的专用虚拟地址空间。

32位Windows分段管理机制

●32位地址转换

32位保护模式是一个更加强大的处理器模式。处理器可以同时运行多个程序,并为每个进程分配4GB的内存,寻址范围0~FFFFFFFFH。

Microsoft汇编器中的平坦内存模式适用于保护模式编程。

.386                      ;支持8086及以上处理器

.model flat,stdcall  ;flat内存模型,stdcall调用约定

平坦内存模式非常易于使用,只需要使用一个32位整数就可以存放任何指令和变量地址。处理器在后台进行地址的计算和转换,所有这一切对应用程序员都是透明的。段寄存器(CS,DS,SS,ES,FS,GS)指向段描述符表,操作系统使用段描述符表定位程序使用的段的位置。

32位保护模式下,源代码中使用的逻辑地址为32位偏移地址,R3特权级的普通应用程序段基址由操作系统指定。

逻辑地址到物理地址的转换分为两个步骤:

1.32位逻辑地址转换为32位线性地址。

转换方法:32位线性地址=32位段基址+32位偏移。

2.32位线性地址转换为32位物理地址。

转换方法:通过分页机制,查找地址映射表将32位线性地址转换为32位物理地址。

我们先了解分段管理机制,稍后我们将讲解分页机制。

●分段机制

1.一段模式

在平坦分段模式下,所有段都被映射到计算机的32位物理地址空间中。一个程序至少需要一个代码段。每个段都由一个段描述符定义,段描述符通常是一个存放在段描述符表中的一个64位的值(32位基址+20位界限+12位属性)。图2-5给出了一个全局段描述符,其地址域指向4GB虚拟内存空间从零开始的一段内存。“段界限域”用于表示段的大小。图中的段界限域为00020H,段的大小为00020H*4KB(页大小=4KB),“访问类型域”用于表示段的属性。

 

举例

设段描述符的段界限域为十六进制值1000H,段界限以页为单位,即4KB(1000H),则该段描述符所表示的段的大小为1000H(段界限域212)*1000H(4KB=212B)=1000000H(16MB=224B)。

图2-5 flat分段模式

2.多段模式

在多段模式下,每个任务都有自己的段描述符表,可以是全局段描述符表(GDT),也可以是局部段描述符表(LDT)。每个段描述符都指向一个与其他段都不相同的段,并且每个段都位于独立的地址空间中。LDT局部段描述符表本身即是一段,其段描述符在全局段描述符表中。LDT局部描述符表的结构和GDT全局段描述符表的结构完全相同,可以看作是全局段描述符表的子表。LDT表的每个表项(段描述符)都指向内存中的一个不相同的段,每个段描述符都指定了段的大小。

如图2-6所示,局部段描述符表中有三个段描述符,所表示的段大小分别为8KB、32KB和128KB。

图2-6 多段模式

●全局段描述符表(GDT)

在32位Windows系统中,有两个全局段描述符表(Global Descriptor Table,GDT)。

1.用户模式GDT(User-mode GDT): 用户模式GDT包含了用户空间的段描述符。它定义了用户模式代码段、数据段、堆栈段等用户空间的各种段属性和内存布局。

2.内核模式GDT(Kernel-mode GDT): 内核模式GDT包含了内核空间的段描述符。它定义了内核模式代码段、数据段、堆栈段等内核空间的各种段属性和内存布局。

这两个GDT分别用于用户模式和内核模式的地址转换和内存访问控制。用户模式GDT只能访问用户空间的段,而内核模式GDT可以访问用户空间和内核空间的段。

每个GDT包含多个段描述符,每个段描述符定义了一个内存段的属性和访问权限。段描述符包括段基址、段界限、访问权限位、段类型和段特权级等信息。

需要注意的是,随着64位Windows系统的普及,现代的操作系统和处理器通常使用了更为复杂的内存模型和保护机制,如分页机制和分段机制的结合,而不再仅仅依赖于GDT。因此,在64位Windows系统中,GDT的作用相对较小,而页表(Page Table)成为更为重要的内存管理结构。

全局段描述符表在处理器切换到保护模式时创建。因此全局段描述符表的地址肯定是一个虚拟地址。GDT表的32位基址存放在GDTR寄存器中。

提示

GDTR是x86架构中的一个特殊寄存器,全称为Global Descriptor Table Register(全局描述符表寄存器)。GDTR寄存器存储了GDT(全局描述符表)的基地址和限长。

在32位保护模式下,GDTR寄存器的结构如下:

1.低16位(0-15位)存储GDT的限长(Limit),表示GDT表的大小(以字节为单位),限制了GDT的访问范围。

2.高32位(16-47位)存储GDT的基地址(Base Address),指向GDT表的起始位置。

GDTR寄存器的值通过LGDT(Load Global Descriptor Table)指令进行加载和更新。当加载GDTR寄存器后,处理器将使用存储在GDTR中的GDT描述符来进行段选择和段访问控制。

GDTR寄存器在操作系统和系统软件中扮演重要的角色,用于管理内存段的访问权限和属性,以及提供安全的内存隔离和保护。通过修改GDT并更新GDTR寄存器,操作系统可以控制应用程序和内核的内存访问权限,确保系统的稳定性和安全性。

全局段描述符表中的每个表项都是一个全局段描述符。全局段描述符表中的描述符可以是以下几种类型:

“LDT局部段描述符表”段描述符。

“TSS任务段”描述符。

“IDT中断描述符表”段描述符。

操作系统全局代码段、数据段和堆栈段描述符。

某个任务的代码段、数据段和堆栈段描述符。

实验三:使用windbg查看Windows XP系统的GDT表。

第一步:命令行输入Ctrl+Break断下后,输入!pcr,找到GDT表的地址。

kd> !pcr

KPCR for Processor 0 at ffdff000:

    Major 1 Minor 1

       NtTib.ExceptionList: 8054a6b0

           NtTib.StackBase: 8054aef0

          NtTib.StackLimit: 80548100

        NtTib.SubSystemTib: 00000000

             NtTib.Version: 00000000

         NtTib.UserPointer: 00000000

             NtTib.SelfTib: 00000000

                   SelfPcr: ffdff000

                      Prcb: ffdff120

                      Irql: 00000000

                       IRR: 00000000

                       IDR: ffffffff

             InterruptMode: 00000000

                       IDT: 8003f400

                       GDT: 8003f000    ;GDT表地址

                       TSS: 80042000

             CurrentThread: 80553940

                NextThread: 00000000

                IdleThread: 80553940

                 DpcQueue:  0x80553fa0 0x80500e24 [Normal] nt!KiTimerExpiration

第二步:命令行输入dq 0x8003f00 L20(指令解释:display qword 0x20 line8个字节一组,共显示32组)查看GDT表内容,如下所示:

kd> dq 0x8003f000 L20

ReadVirtual: 8003f000 not properly sign extended

8003f000  00000000`00000000              00cf9b00`0000ffff

8003f010  00cf9300`0000ffff                   00cffb00`0000ffff

8003f020  00cff300`0000ffff                    80008b04`200020ab

8003f030  ffc093df`f0000001                  0040f300`00000fff

8003f040  0000f200`0400ffff                  00000000`00000000

8003f050  80008954`b1000068                80008954`b1680068

8003f060  00009302`2f40ffff                  0000920b`80003fff

8003f070  ff0092ff`700003ff                   80009a40`0000ffff

8003f080  80009240`0000ffff                  00009200`00000000

8003f090  00000000`00000000              00000000`00000000

8003f0a0  8a008952`52e80068               00000000`00000000

8003f0b0  00000000`00000000              00000000`00000000

8003f0c0  00000000`00000000               00000000`00000000

8003f0d0  00000000`00000000              00000000`00000000

8003f0e0  ba009f11`8000ffff                  00009200`0000ffff

8003f0f0  8000984f`b6b003b7                00009200`0000ffff

Windbg调试器的使用方法可以参考编程达人网站(www.bcdaren.com)工具软件视频教程www.bcdaren.com,此处不再赘述。                

       ●局部段描述符表

       在x86架构中,并没有直接支持局部段描述符表(Local Descriptor Table,LDT)。x86架构中的段描述符表主要是全局描述符表(Global Descriptor Table,GDT)和任务状态段(Task State Segment,TSS)。

全局描述符表(GDT)是一个全局的描述符表,包含了全局范围内的段描述符。GDT由操作系统定义和管理,并通过GDTR寄存器加载到处理器中,用于实现内存段的访问控制和管理。

任务状态段(TSS)是一种特殊的段描述符,用于存储任务的上下文信息,如堆栈指针、程序计数器等。每个任务(或进程)都有一个独立的TSS,通过任务切换(Task Switch)机制进行切换。TSS通常包含了一个局部描述符表(LDT)的指针,用于指向任务特定的局部描述符表。

局部段描述符表(LDT)是一种与任务关联的描述符表,存储特定任务的段描述符。LDT与任务状态段(TSS)配合使用,用于实现任务级别的内存管理和隔离。每个任务可以有自己的LDT,用于存储任务的私有段描述符。

然而,随着现代操作系统的发展和内存管理机制的演进,LDT的使用已经相对较少。大多数操作系统(如Windows和Linux)采用了分页机制和虚拟内存管理,而不是依赖于LDT来实现内存隔离和管理。因此,在常见的操作系统和应用程序中,LDT的使用并不常见。

总结起来,x86架构中的段描述符表主要包括全局描述符表(GDT)和任务状态段(TSS),而局部段描述符表(LDT)的使用已经相对较少,并且在现代操作系统中并不常见。

Windows32位系统中存在多个局部段描述符表(LDT),可以视为是全局段描述表的子表。通常每个进程都有自己的局部段描述符表,而且多任务进程可以拥有不止一个局部段描述符表。局部段描述符表含有当前任务的代码段、数据段、堆栈段和任务段的描述符,也可以包含该任务所使用的一些门描述符,如任务门和调用门描述符等。随着任务的切换,系统当前的局部段描述符表也随之切换。切换的方法为:将另一个“局部段描述符表”段的16位选择子装载到LDTR寄存器中。

MOV AX,DemoLDT_SEL      ;“局部段描述符表”段选择子

LLDT AX                              ;装载LDTR寄存器

通过LDT局部段描述符表可以使各任务私有的各个段与其他任务段相隔离,从而达到受保护的目的。通过GDT表可以使各任务都需要使用的段能够被共享。图2-7给出了任务A和任务B所涉及的有关段既隔离受保护,又合用共享的情况。通过任务A的局部描述符表LDTA和任务B的局部描述符表LDTB,把任务A所私有的代码段CodeA及数据段DataA与任务B所私有的代码段CodeB和数据段DataB及DataC隔离,但任务A和任务B通过全局描述符表GDT共享代码段CodeK及CodeOS和数据段DataE及DataOS。

一个任务可使用的整个虚拟地址空间分为高2GB和低2GB,高2GB空间的描述符在全局描述符表中,低2GB空间的描述符在局部描述符表或全局描述符表中。由于全局和局部描述符表都可以包含多达8192个描述符,而每个描述符所描述的段最大可达4GB,因此最大的虚拟地址空间可为:4GB *8192 *2 = 64MMB = 64T(字节)。

图2-7 全局和局部描述符表的关系

  

图2-8 装载LDT表

可以这样理解GDT表和LDT表:GDT为一级描述符表,LDT为二级描述符表。

       如图2-8所示:我们可以通过GDTR寄存器找到GDT全局段描述符表,在GDT全局段描述符表中保存LDT1、LDT2、LDT3三个局部段描述符表的段描述符,对应的局部段描述符表的段选择子分别为Selector1、Selector2、Selector3。假如我们现在要切换到LDT2中的段,只需要使用指令LLDT将GDT表中的LDT段描述符2对应的选择子Selector2装入LDTR寄存器即可。

段描述符

用于表示定义段的基址、界限和属性三个参数的数据称为描述符。每个描述符长8个字节64位值(32位基址+20位界限+12位属性)。在保护方式下,每一个段都有一个相应的段描述符来描述。

按段描述符所描述的对象来划分,段描述符可分为如下三类:存储段描述符、系统段描述符、门描述符(控制描述符)。下面以存储段描述符为例。 

存储段描述符

存储段是存放可由程序直接进行访问的代码段和数据段。存储段描述符用于描述存储段, 所以存储段描述符也被称为代码段和数据段描述符。

存储段描述符的格式如图2-9所示,图中上面一排是对描述符8个字节的使用的说明,最低地址字节(假设地址为m+0)在最右边,其余字节依次向左,直到最高字节,地址为m + 7。下一排是对属性域各位的说明。

图2-9存储段描述符格式

提示

注:286的时候,段描述符为48位,8位(属性)+ 24位(基址)+ 16位(界限)。

386开始段描述符拓展为64位:拓展8位(基址)+ 拓展4位(属性)+  拓展4位(界限)+  8位(属性)+ 24位(基址)+ 16位(界限)。

80386段描述符中的段属性也被安排在两个域中。下面对其定义及意义作说明: 

1.P位称为存在(Present)位。P = 1表示描述符对地址转换是有效的,或者表示该描述符所描述的段存在;P = 0表示段描述符对地址转换无效,并且使用该描述符会引起异常。 

2. DPL表示段描述符特权级(Descriptor Privilege Level),共2位。它规定了所描述段的特权级,用于特权检查,以决定对该段能否进行访问。

3. DT位说明描述符的类型。对于存储段描述符而言,DT=1,以区别于系统段描述符和门描述符(DT=0)。

4. TYPE域说明存储段描述符所描述的存储段的具体属性。

位0:指示段描述符是否已被访问(Accessed),用符号A标记。A=0表示尚未被访问,A=1表示段已被访问。当把描述符的相应选择子装入到段寄存器时,80386把该位置1,表明描述符已被访何。操作系统可测试访问位,以确定描述符是否被访问过。

位1:在数据段描述符中,TYPE域中的位1指示所描述的数据段是否可写,用W标记。W = 0表示对应的数据段不可写,只能读,W = 1表示对应的数据段可读可写。

位2:就是ED位,指示所描述的数据段的扩展方向。ED=0表示数据段向高地址扩展,也即段内偏移必须小于等于段界限。ED=1表示数据段向低扩展,也即段内偏移必须大于段界限。

 位3:指示所描述的段是代码段还是数据段,用符号E标记,E=0表示段是不可执行段,也就是数据段,相应的描述符也就是数据段(包括堆栈段)描述符;E=1表示段是可执行段,也就是代码段,相应的描述符也就是代码段描述符。

在代码段描述符中,TYPE域中的位1指示所描述的代码段是否可读,用符号R标记,R = 0表示对应的代码段不可读,只能执行,R = 1表示对应的代码段可读可执行。TYPE域中的位2指示所描述的代码段是否是一致码段,用C代表。C = 0表示对应的代码段不是一致代码段(普通代码段),C= 1表示对应的代码段是一致代码段。一致代码段是指位于高2GB空间内的代码段,R具有R3权限的应用程序不需要切换特权级R0就可以直接访问。

存储段描述符中的TYPE域所说明的存储段的属性可归纳为表2-1。

类型域

描述

十进制

数据段

E

W

A

0

0

0

0

0

只读

1

0

0

0

1

只读,已访问

2

0

0

1

0

可读,可写

3

0

0

1

1

可读,可写,已访问

4

0

1

0

0

只读,向低扩展

5

0

1

0

1

只读,向低扩展,已访问

6

0

1

1

0

可读,可写,向低扩展

7

0

1

1

1

可读,可写,向低扩展,已访问

代码段

C

R

A

8

1

0

0

0

只执行

9

1

0

0

1

只执行,已访问

10

1

0

1

0

可执行,可读

11

1

0

1

1

可执行,可读,已访问

12

1

1

0

0

只执行,一致代码段

13

1

1

0

1

只执行,一致代码段,已访问

14

1

1

1

0

可执行,只读,一致代码段

15

1

1

1

1

可执行,只读,一致代码段,已访问

表2-1存储段描述符类型

在80286的存储段描述符中,位于段描述符内第5字节的段属性各位的意义与上述说明相同,确切地说是80386为了与80286兼容而保持了原有定义,下面说明的属性位是 80386在80286基础上的扩充的属性位。 

5.G位就是段界限粒度(Granularity)位。G=0表示界限粒度为字节(实模式下内存地址空间以字节为单位,20位段界限域可访问的地址空间就是220个字节,1MB大小),G = 1表示界限粒度是4KB(保护模式下,内存地址空间以页为单位,20位段界限域可访问的地址空间就是220*212个字节,4GB大小)。注意,界限粒度只对段界限有效,对段基地址无效,段基地址总是以字节为单位。

6. D位是一个很特殊的位,在描述可执行段、向低地址扩展数据段或者堆栈段的三种段描述符中的意义各不相同。

在描述可执行段的描述符中,D位决定了指令使用的地址及操作数所默认的大小。D =1表示默认情况下指令使用32位地址及32位或16位、8位操作数,这样的代码段也称为32 位代码段。D=0表示默认情况下使用16位地址及16位或8位的操作数,这样的代码段,也称为16位代码段,它与80286兼容。

在向低地址扩展数据段的段描述符中,D位决定段的上部边界。D=1表示段的上部界限为 4GB,D=0表示段的上部界限为64KB,这是为了与80286兼容。

在描述由SS寄存器寻址的段描述符中,D位决定隐式的堆栈访问指令(如PUSH和 POP指令)使用何种堆栈指针寄存器。D=1表示使用32位堆栈指针寄存器ESP;D=0 表示使用16位堆栈指针寄存器SP,这与80286兼容。

7.AVL位是软件可利用位,80386对该位的使用未做规定,Intel公司也保证今后开发生产的处理器只要与80386兼容,就不会对该位的使用做任何定义或规定。

此外,描述符内第六字节中的位5需置成0,可理解成为以后的处理器保留的。

 注意

       本书仅介绍了存储段描述符,关于系统段描述符的详细介绍请参考编程达人系列教材的另外两本书《X86汇编语言基础教程》和《Windows 32位内核分析》,本书不再赘述。

段选择子

在实地址模式下,逻辑地址空间中存储单元的地址由段值和段内偏移两部分组成。在保护方式下,虚拟地址空间(相当于逻辑地址空间)中存储单元的地址由段选择子和段内偏移两部分组成。与实地址模式相比,段选择子替代了段值。

一个典型的保护模式程序有三个段:代码段,数据段和堆栈段。

CS包含描述符表中的代码段描述符选择子。

DS包含描述符表中的数据段描述符选择子。

SS包含描述符表中的堆栈段描述符选择子。

以此类推,其他段寄存器均包含相应段描述符选择子。

32位保护模式段寄存器结构内容如表2-2所示:

基址

界限

属性

选择子

ES

0

0xffffFFFF

可读可写

0x23

CS

0

0xffffFFFF

可读可执行

0x1B

SS

0

0xffffFFFF

可读可写

0x23

DS

0

0xffffFFFF

可读可写

0x23

FS

不确定

0xFFF

0x38

GS

-

-

0

表2-2 32位保护模式段寄存器结构内容

段选择子长16位,其格式如图2-10所示。从图中可见,段选择子的高13位是描述符索引(Index),所谓描述符索引是指描述符在描述符表中的序号。段选择子的第2位是引用描述符表指示位,标记为TI(Table Indicator),TI = 0指示从全局描述符表GDT中读取描述符,TI = 1指示从局部描述符表ILDT中读取描述符。

图2-10 段选择子格式

 

总结

TI=0,查GDT表;TI=1,查LDT表;

RPL:请求特权级R0~R3。

选择子确定描述符,描述符确定段基地址,段基地址与偏移之和就是线性地址。虚拟地址空间由段选择子和偏移两部分构成的二维虚拟地址,确定了线性地址空间中的一维线性地址。 

选择子的最低两位是请求特权级RPL(Requested Privilege Level),用于特权检査。当请求特权级RPL小于或等于段描述符DPL(最低访问权限)时,允许访问该段。 例如,RPL=3,DPL=0,最低访问权限为R0,而请求访问权限为R3,因而非法,不能访问。

举例

假设某个选择子的内容是0030H(0000 0000 0011 0000B),根据图31-10所示选择子的格式可知Index = 6,TI = 0,RPL = 0,所以它指定全局描述符表中的第6个描述符,请求特权级是0。

再如,假设某个选择子的Index=4,TI=1,RPL=3,那么该选择子的内容是27H(0000 0000 0010 0111B)。

将段选择子的低3位清零,右移3位得到GDT表的索引,再左移3位,得到在GDT表中的偏移。

由于选择子中的描述符索引字段用13位表示,所以可区分8192(213)个描述符。这也就是描述符表最多含有8192个描述符的原因。由于每个描述符长8字节,按照图30-2所示选择子格式,屏蔽选择子低3位后所得的值就是选择子所指定的描述符在描述符表中的偏 移,这可以认为是安排选择子高13位为描述符索引的原因。

有一个特殊的选择子称为空(NULL)选择子,它的Index==0,TI = 0,而RPL字段可以为任意值。空选择子有特定的用途,当用空选择子进行存储器访问时会引起异常。空选择子是特别定义的,它不对应于全局描述符表GDT中的第0个描述符,因此GDT中的第0 个描述符总是不会被处理器访问,一般把它置成全0。但当TI=1时,Index为0的选择子不是空选择子,它指定了当前任务局部段描述符表LDT中的第0个描述符。

32位Windows分页管理机制

处理器在读取或写入内存位置时使用虚拟地址。在这些操作期间,真正发生读写内存时,处理器需要将虚拟地址转换为物理地址。具体转换方法为查表,即通过查找地址映射表找到虚拟地址所对应的物理地址。

我们以10-10-12分页机制为例,如图2-11所示。在初始化一个进程时,在高2GB虚拟空间内创建一个地址映射表。地址映射表包含1024个页表,每个页表内有1024个表项,每个表项的长度为32位,即4个字节。那么每个页表的大小为4*1024=4KB,1024个页表的大小为1024*4KB=4MB。所以在创建地址映射表时,需要在高2GB的虚拟空间内分配4MB大小的内存空间用于创建地址映射表。分配地址映射表存储空间的起始地址为0xC0000000,从这个地址开始分配1024个页,每一页为一个页表。其中地址映射表中的第0x300页为页目录表,用于存储页表的索引(32位值)。那么页目录表的起始地址就是0xC0300000。表的物理地址存储在CR3寄存器中(CR3寄存器是唯一存储物理地址的寄存器)。

图2-11  10-10-12分页机制

 

总结

1.通过0xC0300000找到的物理页就是页目录表。

2.这个物理页即是页目录表本身也是页表。

3.页目录表是一张特殊的页表,每一项PTE指向的不是普通的物理页,而是指向其他的页表。

页表的每个表项都对应一个物理页的32位起始地址,每个物理页的大小通常为4KB。那么10-10-12分页机制最大可以映射的物理内存为1024个页表*1024个表项*4KB=4GB。

如果一个程序需要访问或修改地址映射表,如何实现呢?我们只需要记得以下两个公式就可以了。

1.访问页目录表的公式:

0xC0300000 + PDI(页目录表项)*4

2.访问页表的公式:

0xC0000000 + PDI*4096 + PTI(页表项)*4

如果物理内存超过4GB了怎么办呢?那就需要修改3级映射表,改为2-9-9-12分页机制。4MB的地址映射表大小不变,页表的数量仍然是1024个,将页表项数量由1024个改为512个,页表项32位值(最多存储32位物理地址)改为64位值(最多存储64位物理地址)。【注】4*1024*1024=8*512*1024=4MB。

不同的操作系统可能会有不同的分页机制,分页机制是人为定义的,只要地址映射表可以满足地址转换的需要就可以了。

接下来我们做一个实验,验证虚拟地址到物理地址的映射过程,并证明0xC0300000虚拟地址处存放的就是页目录表。

实验四:动态分析32位PE文件

实验环境:

  1. 安装Vmware虚拟键,并安装Windows XP操作系统。

2.安装windbg双机调试器,将XP系统虚拟机作为主机,并建立连接。

【注】虚拟机的安装和windbg双机调试的搭建过程请读者独立完成,可以参阅编程达人官网(www.bcdaren.com)相关的视频教程或者查阅其他资料,此处不再赘述。

●步骤一:在XP虚拟机中创建一个记事本test20.txt,并输入字符串”www.bcdaren.com”。

●步骤二:实验CE内存搜索工具,点击左上角加载按钮,加载notepad.exe进程(双机进程或者点击下方的“Open”按钮),如图2-12所示。

图2-12 使用CE内存搜索工具加载记事本进程

       ●步骤三:选择搜索类型为“Text”,并勾选“Unicode”选项,在搜索框输入字符串”www”,然后点击“新的搜索”按钮,注意左侧地址栏内会显示找到的字符串地址,可以选中第一个地址0x000B27C8,然后点击下方的“查看内存”按钮,验证该地址处是否为字符串www.bcdaren.com,如果不是则点击“再次搜索”按钮继续搜索,直到找到为止,如图2-13所示。

图2-13 使用CE搜索字符串地址

●步骤四:按照10-10-12分页机制分解32位线性地址0x000B27C8。

10-10-12分页:将0x000B27C8转换为32位二进制数

0000 0000 00       ------0(高10位为页目录表项索引)

00 1011 0010       ------B2(中间10位为页表项索引)

7c8                ------7c8(低12位为物理页表索引)

●步骤五:点击windbg调试器工具栏Break按钮连接XP虚拟机,连接后就可以在底栏命令行窗口输入调试命令了,如图2-14所示。

图2-14 使用windbg调试器建立双机连接

●步骤六:命令行输入!process 0 0命令,查看记事本进程信息

kd> !process 0 0 notepad.exe

PROCESS 89f3fb48 (EPROCESS进程地址) SessionId: 0(用户ID)  Cid: 07f4  (进程ID)  Peb: 7ffda000 (进程环境块) ParentCid: 05c4(父进程ID)

    DirBase: 1c56d000 (CR3物理地址) ObjectTable: e29ee4d0(句柄表)  HandleCount:  52. (句柄数)

Image: notepad.exe(进程名)

●步骤七:查看页目录表第0项,页表索引为1c5e6067

kd> !dd 1c56d000+0*4(注:每个表项为4个字节)

#1c56d000 1c5e6067 1c414067 1c6ab067 00000000

#1c56d010 1c3e5067 00000000 00000000 00000000

#1c56d020 00000000 00000000 00000000 00000000

#1c56d030 00000000 00000000 00000000 00000000

#1c56d040 00000000 00000000 00000000 00000000

#1c56d050 00000000 00000000 00000000 00000000

#1c56d060 00000000 00000000 00000000 00000000

#1c56d070 00000000 00000000 00000000 00000000

●步骤八:查看页表第b2项,物理页索引为1c6ee067

kd> !dd 1c5e6000+b2*4(注:1c5e6067低12位属性位清零)

#1c5e62c8 1c6ee067 1c531067 1c732067 1c433067

#1c5e62d8 1c4f4067 1c536067 1c635067 00000000

#1c5e62e8 00000000 00000000 00000000 00000000

#1c5e62f8 00000000 00000000 00000000 00000000

#1c5e6308 00000000 00000000 00000000 00000000

#1c5e6318 00000000 00000000 00000000 00000000

#1c5e6328 00000000 00000000 00000000 00000000

#1c5e6338 00000000 00000000 00000000 00000000

●步骤九:查看物理页第7c8项

kd> !db 1c6ee000+7c8(注:1c6ee067低12位属性位清零)

#1c6ee7c8 77 00 77 00 77 00 2e 00-62 00 63 00 64 00 61 00 w.w.w...b.c.d.a.

#1c6ee7d8 72 00 65 00 6e 00 2e 00-63 00 6f 00 6d 00 00 00 r.e.n...c.o.m...

#1c6ee7e8 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................

#1c6ee7f8 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................

#1c6ee808 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................

#1c6ee818 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................

#1c6ee828 00 00 00 00 14 00 89 00-13 00 0e 00 69 01 0c 00 ............i...

#1c6ee838 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................

●步骤十:查看记事本进程4GB虚拟空间内的页目录表

kd> .process /i 89f3fb48 (进入notepad.exe进程空间)

You need to continue execution (press 'g' <enter>) for the context

to be switched. When the debugger breaks in again, you will be in

the new process context.

kd> g

Break instruction exception - code 80000003 (first chance)

nt!RtlpBreakWithStatusInstruction:

804e450a cc              int     3

kd> dd c0300000 (查看虚拟地址c0300000处的页目录表)

c0300000  1c5e6067 1c414067 1c6ab067 00000000

c0300010  1c3e5067 00000000 00000000 00000000

c0300020  00000000 00000000 00000000 00000000

c0300030  00000000 00000000 00000000 00000000

c0300040  00000000 00000000 00000000 00000000

c0300050  00000000 00000000 00000000 00000000

c0300060  00000000 00000000 00000000 00000000

c0300070  00000000 00000000 00000000 00000000

 

结论

1.10-10-12分页机制通过页目录表-页表-页三级地址映射表,将32位线性地址转换为32位物理地址。

2.通过CR3物理地址访问的页目录表与通过0xC0300000虚拟地址访问的页目录表完全相同。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值