本次的了解主要讲解 PE的基本概念、MS-DOS文件头、PE文件头、区块、输入表、输出表等。

这里我将会结合一个简单的小程序来加深我对PE文件结构的了解。

使用学习工具:有StudyPE、LordPE、PEID。

学习PE建议看书。。和自己动手。。。

PE文件:

  在WIN上,32位的可执行文件是PE文件,64位的是PE32+文件 ,DLL文件的格式和PE格式差不多,唯一的区别是PE和DLL的有一个字段标识这个文件是EXE还是DLL。

  

如上就是一个PE文件的结构图,PE文件使用的是一个平面地址空间,所有的数据都融合在一起,文件的内容又被分割为不同的区块(Section),

各个区块按页的边界来对齐。每个块都有自己的属性(是否可读,是否可写,是否可执行等等)。

基地址:

当PE文件被装载器装载了之后,内存中的板块被称为模块。映射文件的起始地址被称为模块句柄---内存中的模块代表这进程从这个可执行文件中所需要的代码、数据、资源、输入表、输出表及其他东西所使用的东西放在一个连续的内存块中。在装载中,PE文件的一个字段会告诉系统把文件映射到内存需要多少内存,不能被映射的数据被放置在文件的尾部。

在WIN32中,可以使用HMODULE GetModuleHandle(LPCTSTR lpModuleName)来获得一个模块的名称。当传递一个可执行文件或者DLL作为参数,

如果系统成功找到这个文件,就会返回该可执行文件或者DLL文件映像加载到的基地址。

在PE文件中,有一个字符设置了基地址,VC++建立的exe文件的基地址是0x00400000h,DLL文件的基地址是0x10000000h。

相对虚拟地址:

为了让程序的载入更加的灵活-也为了在PE文件中出现有确定的内存地址,出现了相对虚拟地址(Relative Vritual Address, RVA),当你的程序加载后,假设你的text块的RVA = 0x00001000h,映射到程序中时,VA(虚拟地址) = ImagineBase(基地址)+RVA(相对虚拟地址),你的代码区块在内存中就开始与0x00401000h。

文件偏移地址:

因为我们的文件是存储在磁盘上的,某个数据相对于文件头的偏移量就是这个数据的偏移地址,称为文件偏移地址(File Offset)或者物理地址(RAW Offset),偏移地址的起始值是0。

MS-DOS头部(IMAGE_DOS_HEADER):

每个PE文件是以一个DOS程序开始的,还有MZ header之后的DOS stub(DOS块)。如果这个可执行文件不能被这个系统支持,会打印一串提示符

"This program cannot be run is MS-DOS mode",DOS头部中主要是WORD e_magic和 LONG e_lfanew这个字段比较重要。这些数据结构可以在winnt.h中找到。

#define IMAGE_DOS_SIGNATURE 0x5A4D
#define IMAGE_OS2_SIGNATURE 0x454E
#define IMAGE_OS2_SIGNATURE_LE 0x454C
#define IMAGE_VXD_SIGNATURE 0x454C
#define IMAGE_NT_SIGNATURE 0x00004550 #include "pshpack2.h"
//这里就是IMAGE_DOS_HEADER的结构了。
typedef struct _IMAGE_DOS_HEADER {
WORD e_magic; // DOS可执行文件标记“MZ”
WORD e_cblp;
WORD e_cp;
WORD e_crlc;
WORD e_cparhdr;
WORD e_minalloc;
WORD e_maxalloc;
WORD e_ss;
WORD e_sp;
WORD e_csum;
WORD e_ip; //DOS代码入口IP
WORD e_cs; //DOS代码的入口CS
WORD e_lfarlc;
WORD e_ovno;
WORD e_res[];
WORD e_oemid;
WORD e_oeminfo;
WORD e_res2[];
LONG e_lfanew; // 指向PE文件头, “PE”,0,0
} IMAGE_DOS_HEADER,*PIMAGE_DOS_HEADER;

LONG e_lfanew是指向PE文件头的一个地址,它的文件偏移地址是0x3C--也就是60字节开始,然后占据了4个字节,指向PE文件头,具体看下图

我们可以看到0x000000F8在偏移60字节的地方,看到PE文件头在0x000000F8,具体可以自己找一个文件来测试(加深印象)

DOS文件头就到这里了,接下来继续介绍PE头,也就是IMAGE_NT_HEADER,下面的代码是对于定义32还是64的PE头,我们可以看到相应的宏语句

和对应的数据结构定义。

#ifdef _WIN64             //如果采用64的架构
typedef IMAGE_OPTIONAL_HEADER64 IMAGE_OPTIONAL_HEADER;
typedef PIMAGE_OPTIONAL_HEADER64 PIMAGE_OPTIONAL_HEADER;
#define IMAGE_SIZEOF_NT_OPTIONAL_HEADER IMAGE_SIZEOF_NT_OPTIONAL64_HEADER
#define IMAGE_NT_OPTIONAL_HDR_MAGIC IMAGE_NT_OPTIONAL_HDR64_MAGIC
#else /* _WIN64 */ //如果不是采用64而是32位的架构
typedef IMAGE_OPTIONAL_HEADER32 IMAGE_OPTIONAL_HEADER;
typedef PIMAGE_OPTIONAL_HEADER32 PIMAGE_OPTIONAL_HEADER;
#define IMAGE_SIZEOF_NT_OPTIONAL_HEADER IMAGE_SIZEOF_NT_OPTIONAL32_HEADER
#define IMAGE_NT_OPTIONAL_HDR_MAGIC IMAGE_NT_OPTIONAL_HDR32_MAGIC
#endif /* _WIN64 */ //上面的typedef都是改变结构体的名称 typedef struct _IMAGE_NT_HEADERS64 {//这里是64位的PE头的结构体的定义
DWORD Signature;
IMAGE_FILE_HEADER FileHeader;
IMAGE_OPTIONAL_HEADER64 OptionalHeader;
} IMAGE_NT_HEADERS64,*PIMAGE_NT_HEADERS64; typedef struct _IMAGE_NT_HEADERS {//这里是32位的PE头的结构体的定义
DWORD Signature;
IMAGE_FILE_HEADER FileHeader;
IMAGE_OPTIONAL_HEADER32 OptionalHeader;
} IMAGE_NT_HEADERS32,*PIMAGE_NT_HEADERS32;

在PE的文件头中,第一个DWORD Signature, 被定义为了0x00004550h,也就是"PE\0\0"这四个字符。这个标志没有什么作用。(DOS中指向的地方)。

主要是IMAGE_FILE_HEADER和IMAGE_OPTIONAL_HEADER这两个结构体中的几个字段重要。

我么接下来观看PE文件头中的IMAGE FILE_HEADER FileHeader这个结构体

typedef struct _IMAGE_FILE_HEADER {
WORD Machine; //这里定义的是运行平台,i386= 0x014Ch这个值,还有其他平台,看书吧。。
WORD NumberOfSections;       //这个是标识区块的数目,紧跟在PE头的后面,也就是IMAGE_NT_HEADERS的后面
DWORD TimeDateStamp;
DWORD PointerToSymbolTable;
DWORD NumberOfSymbols;
WORD SizeOfOptionalHeader;     //这里表明了IMAGE_NT_HEADERS中的大小(RAW SIZE),32位一般是0x00E0, 64位PE+一般是0x00F0
WORD Characteristics;       //普通的EXE是0x010fh, DLL文件是0x210Eh
} IMAGE_FILE_HEADER,*PIMAGE_FILE_HEADER;

接下来我们看一下IMAGE_OPTIONAL_HEADER这个结构体,一样下面的是这个结构体在winnt.h中的定义,下面这个是32位的,还有64位的,但是差不多的,可以看winnt.htypedef struct _IMAGE_OPTIONAL_HEADER

typedef struct _IMAGE_OPTIONAL_HEADER {

      WORD Magic;
BYTE MajorLinkerVersion;
BYTE MinorLinkerVersion;
DWORD SizeOfCode;           //这里定义了包含代码区块的大小
DWORD SizeOfInitializedData;    //这里定义了已经初始化的变量的区块的大小
DWORD SizeOfUninitializedData;   //这里是未初始化的变量的区块的大小
DWORD AddressOfEntryPoint;     //这里是程序入口的RVA(相对虚拟地址)
DWORD BaseOfCode;          //这里是程序代码块的起始RVA
DWORD BaseOfData;          //这里是数据块起始RVA
DWORD ImageBase;           //这里是程序默认装入的基地址(ImageBase)
DWORD SectionAlignment;       //内存中区块的对齐值,非常重要
DWORD FileAlignment;        //文件中区块的对齐值,非常重要
WORD MajorOperatingSystemVersion;
WORD MinorOperatingSystemVersion;
WORD MajorImageVersion;
WORD MinorImageVersion;
WORD MajorSubsystemVersion;
WORD MinorSubsystemVersion;
DWORD Win32VersionValue;
DWORD SizeOfImage;
DWORD SizeOfHeaders;
DWORD CheckSum;
WORD Subsystem;          //这里定义了文件的子系统,图形接口子系统,字符子系统,具体可以看具体的定义
WORD DllCharacteristics;
DWORD SizeOfStackReserve;
DWORD SizeOfStackCommit;
DWORD SizeOfHeapReserve;
DWORD SizeOfHeapCommit;
DWORD LoaderFlags;
DWORD NumberOfRvaAndSizes;   //这里定义了数据目录表的项数,一直保持为16
IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]; //这个是数据目录表,指向输入、输出表、资源块等数据,很重要
} IMAGE_OPTIONAL_HEADER32,*PIMAGE_OPTIONAL_HEADER32;

这个IMAGE_OPTIONAL_HEADER只需要关注一些关键字段就行了,记住。。

接下来我么就看一看这个数据目录表,数据目录表简单点说就是一个长度为16的IMAGE_DATA_DIRECTORY结构体数组而已

typedef struct _IMAGE_DATA_DIRECTORY {
DWORD VirtualAddress; //数据块的其实RVA,很重要
DWORD Size;             //数据块的长度
} IMAGE_DATA_DIRECTORY,*PIMAGE_DATA_DIRECTORY;

这是一个16位的数组,最有一个数组元素作为保留,全部为0,其他的从开头一直到倒数第二个数据都是已经规定好了的,我们看一下这个数据目录表成员

#define IMAGE_DIRECTORY_ENTRY_EXPORT 0              //Export Table
#define IMAGE_DIRECTORY_ENTRY_IMPORT 1        //Import Table 输入表这里比较重要
#define IMAGE_DIRECTORY_ENTRY_RESOURCE 2
#define IMAGE_DIRECTORY_ENTRY_EXCEPTION 3
#define IMAGE_DIRECTORY_ENTRY_SECURITY 4
#define IMAGE_DIRECTORY_ENTRY_BASERELOC 5
#define IMAGE_DIRECTORY_ENTRY_DEBUG 6
#define IMAGE_DIRECTORY_ENTRY_ARCHITECTURE 7
#define IMAGE_DIRECTORY_ENTRY_GLOBALPTR 8
#define IMAGE_DIRECTORY_ENTRY_TLS 9
#define IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG 10
#define IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT 11
#define IMAGE_DIRECTORY_ENTRY_IAT 12          //IAT (import address table), 这里也很重要
#define IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT 13
#define IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR 14

还有也可以使用LordPE的PE editor来查看这个目录,现在的查看目录表的工具很多。。。。。

PE的第一阶段到这里,接下来会继续学习,增加熟悉度。。。。。。。。。。-----------好好学习,天天向上!

最新文章

  1. Impossible to load an image in xcassets on bundle
  2. AMap地图加载完成事件
  3. iOS Block详细介绍(block实现)
  4. mysql列类型
  5. Js图片切换
  6. Qt Style Sheets Examples——定制前景色和背景色
  7. Android manifest之系统自带的permission
  8. mysql 获取设置环境变量
  9. 定制属于自己的自动化安装的linux系统镜像
  10. XAMPP配置虚拟主机
  11. HDU 1997 汉诺塔VII
  12. [Nhibernate]二级缓存
  13. php下载文件的一种方式
  14. [PCB设计] 4、BAT脚本处理AD生成的GERBER文件为生产文件
  15. 常用的Python代码段
  16. Day062--django--模板,母版和继承
  17. [ACM-ICPC 2018 徐州赛区网络预赛][D. Easy Math]
  18. 构建NTP时间服务器
  19. 火狐下,td 的 bug;
  20. Mysql_临时表

热门文章

  1. MySQL练习-employees数据库(二)
  2. Google地图路线规划
  3. Android 项目结构图
  4. Java与各种数据库连接代码
  5. 【Android】Android属性allowBackup安全风险
  6. 轻松搞定laravel的curd操作搞定简易留言版(四)
  7. win7下IIS的安装和配置 图文教程
  8. BluetoothChat用于蓝牙串口通信的修改方法
  9. BULK操作减少redo实验
  10. php多线程操作同一文件-待续