在上一节中,我们静态分析了磁盘上的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