PE文件格式之MS-DOS头,PE文件头,区块
可执行文件的格式是操作系统本身执行机制的反映。
1.在Win16平台上(如Windows 3.x),可执行文件是NE格式。
2.在Win32平台上(包括Windows 9x/NT/2000/XP/2003/Vista/CE),可执行文件是PE格式。
PE是Portable Executable File Format(可移植的执行体)简写,它是目前Windows平台上的主流可执行文件格式。
PE文件衍生于早期建立在VAX/VMS上的COFF文件格式(Common Object File Format),描述PE格式及COFF文件的主要地方是在winnt.h,其中有一节叫“Image Format”。该节给出了DOSMZ格式和Windows 3.1的NE格式文件头,之后就是PE文件的内容。在这个头文件中,几乎能找到关于PE文件的每一个数据结构定义、枚举类型、常量定义。winnt.h是PE文件定义的最终决定者。
EXE和DLL之间的区别完全是语义上的,他们使用完全相同的PE格式。唯一的区别就是用一个字段标识出这个文件是EXE还是DLL。还有许多DLL的扩展,如OCX控件和控制面板程序(.CPL文件)等都是DLL,它们有一样的实体。
64位的Windows只是对PE格式做了一些简单的修饰,新格式叫PE32+
在学习PE格式细节的时候,可以参照下图和Stud_PE工具配合(直观显示PE各部分数据)
图1.PE文件的框架结构
1.PE的基本概念
PE文件使用的是一个平面地址空间,所有代码和数据都被合并在一起,组成一个很大的结构。文件的内容被分割为不同的区块(Section,又称区段、节等),区块中包含代码或数据,各个区块按页边界来对齐,区块没有大小限制,是一个连续结构。每个块都有它自己在内存中的一套属性,比如:这个块是否包含代码、是否只读或可读/写等。
PE文件不是作为单一内存映射文件被装入内存
Windows加载器(又称PE装载器)遍历PE文件并决定文件的哪一部分被映射,这种映射方式是将文件较高的偏移位置映射到较高的内存地址中。当磁盘文件一旦被装入内存中,磁盘上的数据结构布局和内存中的数据结构布局是一致的。这样如果知道在磁盘的数据结构中寻找一些内容,那么几乎都能在被装入到内存映射文件中找到相同的信息。但数据之间的相对位置可能改变,其某项的偏移地址可能区别于原始的偏移位置,不管怎样,所有表现出来的信息都允许从磁盘文件偏移到内存偏移的转换
:angry: 是不是一脸懵逼= = 慢慢来!
图2.PE文件磁盘与内存映像结构图
1.基地址
1.当PE文件通过Windows加载器被装入内存后,内存中的版本被称作模块(Module)。
2.映射文件的起始地址被称为模块句柄(hModule),可以通过模块句柄访问内存中其他的数据结构。这个初始内存地址也称为基地址(ImageBase)。
3.内存中的模块代表着进程从这个可执行文件中所需要的代码、数据、资源、输入表、输出表及其他有用的数据结构所使用的内存都放在一个连续的内存块中,编程人员只要知道装载程序文件映像到内存后的基地址即可。PE文件剩下的其他部分可以被读入,但是可能不映射。
4.Windows NT或Windows 95将Module的基地址作为Module的实例句柄(Instance Handle,即Hinstance)。
5.在32位Windows系统中可以直接调用GetModuleHandle以取得指向DLL的指针,通过指针访问该DLL Module的内容。
HMODULE GetModuleHandle(LPCTSTR lpModuleName);
6.当调用该函数时,传递一个可执行文件或DLL文件名字符串,如果系统找到文件,则返回该可执行文件或DLL文件映像加载到的基地址。也可调用GetModuleHandle,传递NULL参数,则返回调用的可执行文件的基地址。
7.基地址的值是由PE文件本身设定的。按照默认设置,用Visual C++建立的EXE文件基地址是00400000h,DLL文件基地址是10000000h。但是,可以在创建应用程序的EXE文件时改变这个地址,方法是在链接应用时使用链接程序的/BASE选项,或者链接后通过REBASE应用程序进行设置。
上面的简单了解下就好~ :joy:
2.相对虚拟地址
1.在可执行文件中,有许多地方需要指定内存中的地址。例如,引用全局变量时,需要指定它的地址。
2.PE文件尽管有一个首选的载入地址(基地址),但是它们可以载入到进程空间的任何地方,所以不能依赖于 PE的载入点。由于这个原因,必须有一个方法来指定地址而不依赖于PE载入点的地址。
3.为了在PE文件中避免有确定的内存地址,出现了相对虚拟地址(Relative Virtual Address,简称RVA)概念。
4.RVA只是内存中的一个简单的相对于PE文件装入地址的偏移位置,它是一个“相对”地址,或称为“偏移量”。
eg: 一个exe文件从地址400000h处装入,并且它的代码区块开始于401000h,代码块的RVA就是
目标地址 401000h-载入地址 400000h =RVA 1000h
5.将实际的装入地址加上RVA即可得到实际的内存地址
实际的内存地址被称作虚拟地址(Virtual Address,简称VA)
虚拟地址(VA)=基地址(ImageBase)+相对虚拟地址(RVA)
3.文件偏移地址
当PE文件储存在磁盘上时,某个数据的位置相对于文件头的偏移量,称为文件偏移地址(File Offset)或物理地址(RAW Offset)。
我们常用的winhex或者Hex Workshop 打开的文件所显示地址就是文件偏移地址
2.MS-DOS头部
1.每个PE文件是以一个DOS程序开始的,有了它,一旦程序在DOS下执行,DOS就能识别出这是有效的执行体
2.然后运行紧随MZ header之后的DOS stub(DOS块)。DOS stub实际上是一个有效的EXE,在不支持PE文件格式的操作系统中,它将简单显示一个错误提示,类似于字符串“This program cannot be run in MS-DOS mode”。
3.我们平常把DOS MZ头和DOS stub合称为DOS文件头
PE文件的第一个字节起始于一个传统的MS-DOS头部,被称作IMAGE_DOS_HEADER。
IMAGE_DOS_HEADER { // DOS .EXE header
WORD e_magic; // Magic number
WORD e_cblp; // Bytes on last page of file
WORD e_cp; // Pages in file
WORD e_crlc; // Relocations
WORD e_cparhdr; // Size of header in paragraphs
WORD e_minalloc; // Minimum extra paragraphs needed
WORD e_maxalloc; // Maximum extra paragraphs needed
WORD e_ss; // Initial (relative) SS value
WORD e_sp; // Initial SP value
WORD e_csum; // Checksum
WORD e_ip; // Initial IP value
WORD e_cs; // Initial (relative) CS value
WORD e_lfarlc; // File address of relocation table
WORD e_ovno; // Overlay number
WORD e_res[4]; // Reserved words
WORD e_oemid; // OEM identifier (for e_oeminfo)
WORD e_oeminfo; // OEM information; e_oemid specific
WORD e_res2[10]; //Reserved words
LONG e_lfanew; // File address of new exe header
}IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;
其IMAGE_DOS_HEADER结构如下所示(左边的数字是到文件头的偏移量):
重要字段:
1.e_magic字段(一个字大小)需要被设置为值5A4Dh,这个值有个#define,名为**IMAGE_DOS_SIGNATURE,**ASCII表示法里,它的ASCII值为“MZ”,是MS-DOS的最初创建者之一Mark Zbikowski字母的缩写。:+1:
2.e_lfanew字段是真正PE文件头的相对偏移(RVA),其指出真正PE头的文件偏移位置,它占用4个字节,位于