PE文件结构整理

本博客主要参考了以下两篇博客中的代码并加以整理:

52pj_zmblix的讲解
csdn某大佬的讲解

PE

PE即Portable Executable,是MS发明的32位操作系统的主要程序文件格式。理论上说,PE文件向下兼容了dos16位操作系统的程序文件。

PE文件结构关键不在于了解,在于熟谙。只有对PE文件头到了不用刻意去计算也能凭直觉找到所需要信息的地步才算入门了。然后才能进行手工打补丁等一系列的实验。仅仅了解了PE的结构,和没了解过并没有本质的区别。

本文只讲PE,不讲操作系统。

按主流的说法,PE文件可以分解成下面几个部分:

PE文件
Dos头可能是出于安全目的,实际上对大多数人是多余的
1.PE签名
NT头2.PE文件头
3.PE可选头
节表常见的有.idata节,.text节,.data节等九大节

Dos头结构很简单,如下所示,一般32位PE文件的Dos头只会输出一句话,如果你使用dos虚拟机去运行一个PE程序,你会在终端看到:

C:\>hello.exe
This program can not run in dos mode
Dos头													
typedef struct _IMAGE_DOS_HEADER {  // DOS .EXE header			
      WORD   e_magic;               // 00 Magic number			
      WORD   e_cblp;                // 02 最后一页的字节数		
      WORD   e_cp;                  // 04 页数		
      WORD   e_crlc;                // 06 重定位项数量			
      WORD   e_cparhdr;             // 08 文件头以节计算的长度	
      WORD   e_minalloc;            // 0a 需要的最小段数,1段=10h  
      WORD   e_maxalloc;            // 0c 需要的最大段数
      WORD   e_ss;                  // 0e SS	
      WORD   e_sp;                  // 10 SP			
      WORD   e_csum;                // 12 Check num				
      WORD   e_ip;                  // 14 IP			
      WORD   e_cs;                  // 16 CS	
      WORD   e_lfarlc;              // 18 重定位表地址	
      WORD   e_ovno;                // 1a 覆盖号,必须为0			
      WORD   e_res[4];              // 1c 保留字			
      WORD   e_oemid;               // 24 OEM identifier (for e_oeminfo)	
      WORD   e_oeminfo;             // 26 OEM information; e_oemid specific 
      WORD   e_res2[10];            // 28 保留字			
      LONG   e_lfanew;              // 3c NT头入口地址	
    } IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;				

接着我们看NT头:

typedef struct _IMAGE_NT_HEADERS {
	DWORD Signature;						//00 PE签名50 45 00 00
	IMAGE_FILE_HEADER FileHeader;			//映像文件头
	IMAGE_OPTIONAL_HEADER32 OptionalHeader;	//映像可选头
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;

PE文件加载到内存中称为映像(image)

typedef struct _IMAGE_FILE_HEADER {
 	WORD    Machine;					//04 运行平台,如386是014c,64是0200
  	WORD    NumberOfSections;			//06 节数
  	DWORD    TimeDateStamp;				//08 创建时间
  	DWORD    PointerToSymbolTable;		//0c COFF符号表偏移
  	DWORD    NumberOfSymbols;			//10 符号表数量
  	WORD    SizeOfOptionalHeader;		//14 可选头大小
  	WORD    Characteristics;			//16 文件属性,如.dll为2000
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;

可选头只是名字这么叫,实际上关键部分一项都不能少,可选头类型是由运行平台决定的

以下是一个386平台的可选头

typedef struct _IMAGE_OPTIONAL_HEADER {
      WORD    Magic;						//18 可选头类型,386是010b
      BYTE    MajorLinkerVersion; 			//1a 最高链接库版本号
      BYTE    MinorLinkerVersion; 			//1b 最低链接库版本号
      DWORD   SizeOfCode;					//1c 代码段总大小
      DWORD   SizeOfInitializedData;		//20 .idata大小
      DWORD   SizeOfUninitializedData;		//24 .data大小
      DWORD   AddressOfEntryPoint;			//28 程序入口RVA
      DWORD   BaseOfCode;					//2c 代码起始RVA
      DWORD   BaseOfData;                 	//30 数据起始RVA
      DWORD   ImageBase;					//34 内存映像基地址,一般是400000
      DWORD   SectionAlignment;				//38 节对齐
      DWORD   FileAlignment;				//3c 文件内对齐
      WORD    MajorOperatingSystemVersion;	//40 最高操作系统版本号
      WORD    MinorOperatingSystemVersion;	//42 最低操作系统版本号
      WORD    MajorImageVersion;			//44 最高映像版本号
      WORD    MinorImageVersion;			//46 最低映像版本号
      WORD    MajorSubsystemVersion;		//48 最高子系统版本号
      WORD    MinorSubsystemVersion;		//4a 最低子系统版本号
      DWORD   Win32VersionValue;			//4c 保留,必须为0
      DWORD   SizeOfImage;					//50 内存映像总大小
      DWORD   SizeOfHeaders;				//54 所有文件头的总大小
      DWORD   CheckSum;						//58 映像文件校验和
      WORD    Subsystem;					//5c 子系统,0为Unknown,1为不需要
      WORD    DllCharacteristics;			//5e Dll文件属性
      DWORD   SizeOfStackReserve;			//60 线程栈保留内存
      DWORD   SizeOfStackCommit;			//64 线程栈初始内存
      DWORD   SizeOfHeapReserve;			//68 进程堆保留内存
      DWORD   SizeOfHeapCommit;				//6c 进程堆初始内存
      DWORD   LoaderFlags;					//70 保留,必须为0
      DWORD   NumberOfRvaAndSizes;			//74 数据目录的项数
	IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
											//78 从78开始为数据目录的具体项
  } IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;


数据目录表项的定义如下:

typedefstruct _IMAGE_DATA_DIRECTORY {
    DWORD   VirtualAddress;				//RVA
    DWORD   Size;						//大小
} IMAGE_DATA_DIRECTORY,*PIMAGE_DATA_DIRECTORY;

VirtualAddress是一个RVA,Size是其大小。数据目录一共有16个,分别是:

  #define IMAGE_DIRECTORY_ENTRY_EXPORT          	0   //78 输出表
  #define IMAGE_DIRECTORY_ENTRY_IMPORT          	1   //80 输入表
  #define IMAGE_DIRECTORY_ENTRY_RESOURCE        	2   //88 资源表
  #define IMAGE_DIRECTORY_ENTRY_EXCEPTION       	3   //90 异常表
  #define IMAGE_DIRECTORY_ENTRY_SECURITY        	4   //98 安全表
  #define IMAGE_DIRECTORY_ENTRY_BASERELOC       	5   //a0 重定位表
  #define IMAGE_DIRECTORY_ENTRY_DEBUG           	6   //a8 调试表
  //      IMAGE_DIRECTORY_ENTRY_COPYRIGHT       	7   //   (X86 usage) 版权
  #define IMAGE_DIRECTORY_ENTRY_ARCHITECTURE    	7   //b0 架构数据
  #define IMAGE_DIRECTORY_ENTRY_GLOBALPTR       	8   //b8 全局指针的RVA 
  #define IMAGE_DIRECTORY_ENTRY_TLS             	9   //c0 TLS表
  #define IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG 	    10  //c8 载入配置表
  #define IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT    	11  //d0 绑定导入表
  #define IMAGE_DIRECTORY_ENTRY_IAT         	    12  //d8 导入地址表
  #define IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT    	13  //e0 延迟加载表 
  #define IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR  	14  //e8 COM运行时描述符目录
//		  IMAGE_DIRECTORY_ENTRY_RESERVE				15  //f0 保留

节表(Section table)又叫块表,中存放了文件各个节的描述信息,每个节的描述大小为28h,节表开始于PE+f8的位置,只要求节表中每个节的名称是唯一的,只是为了便于分辨,这些名字并没有实际意义,.code节也可以叫.data,反之亦然。其结构定义如下:

typedef struct_IMAGE_SECTION_HEADER {
	  BYTE  Name[IMAGE_SIZEOF_SHORT_NAME]; 	//00节表名称,一般以.开头
	  union {   
	       DWORD PhysicalAddress;  		//物理地址
	       DWORD VirtualSize;			//节在内存中的大小,是alignment的整数倍
	  } Misc; 								//08 Misc 一般是VisualSize
	  DWORD VirtualAddress; 				//0c RVA,一般第一个节的RVA总是1000
	  DWORD SizeOfRawData; 					//10 节在文件中的大小,  
	  DWORD PointerToRawData;				//14 节在文件中的偏移地址
	  DWORD	PointerToRelocations; 			//18 重定位表地址,只在obj中有意义
	  DWORD	PointerToLinenumbers; 			//1c 行号表(含调试信息)地址
	  WORD  NumberOfRelocations; 			//20 重定位项数,只在obj中有意义
	  WORD  NumberOfLinenumbers; 			//22 行号数目
	  DWORD   Characteristics;				//24 节的属性
} IMAGE_SECTION_HEADER,*PIMAGE_SECTION_HEADER;

一般一个节的名称不会超过8字节,如果超过8字节,最后就没有0字符了,这个节会被与它前面的一个带有$的节被链接器在载入时合并。

/*一些常见的节属性:*/
0x00000020 	//代码段 (.text)
0x00000040 	//已初始化数据段 (.data)
0x00000080 	//未初始化数据段 (.bss)
0x04000000 	//该段数据不能被缓存 
0x08000000 	//该段不能被分页 
0x10000000 	//共享段 
0x20000000 	//可执行段(.text) 
0x40000000 	//可读段 
0x80000000 	//可写段

输入表

输入表的结构:

typedef struct_IMAGE_IMPORT_DESCRIPTOR {     
	_ANONYMOUS_UNION union{                     
	   DWORD	Characteristics;         
	   DWORD	OriginalFirstThunk;     
	} DUMMYUNIONNAME;   		//00  指向API名字表
	DWORD TimeDateStamp;   		//04h 如果有绑定的话,这里会存有一个时间戳
								//一般设成0xFFFFFFFF表示绑定 
	DWORD ForwarderChain;   	//08h 传递器链表
	DWORD Name;         		//0Ch 指向dll名字表
	DWORD FirstThunk;     		//10h  指向API地址表
}IMAGE_IMPORT_DESCRIPTOR,*PIMAGE_IMPORT_DESCRIPTOR;

输入地址表项的结构

typedef struct_IMAGE_THUNK_DATA32 {  
	union {         		   
	  	DWORD ForwarderString;        
	  	DWORD Function;        
	  	DWORD Ordinal;         
	  	DWORD AddressOfData;    
	} u1;
}IMAGE_THUNK_DATA32,*PIMAGE_THUNK_DATA32;

如上所示,输入表项是函数名的RVA地址。如果IMAGE_THUNK_DATA32的最高位为1时,表示函数以序号方式输入,若最高位为零,那么函数以函数名方式输入。
API名字表和地址表都以一个全为0的IMAGE_THUNK_DATA32项作为结尾标志。

typedef struct_IMAGE_IMPORT_BY_NAME {     
	WORD Hint;    			//如果最高位为1,则为API在库内的序号
	BYTE Name[1];			//长度不固定,以'\0'结尾  
} IMAGE_IMPORT_BY_NAME,*PIMAGE_IMPORT_BY_NAME;

输出表

typedef struct _IMAGE_EXPORT_DIRECTORY {
	DWORD  Characteristics;  			//00 一般为0
    DWORD  TimeDateStamp;    			//04 如果有库绑定,这里会有一个时间戳
    WORD   MajorVersion;     			//08 最高**版本(实际上没用,一般设为0)
    WORD   MinorVersion;    			//0a 最低**版本(同上)
    DWORD  Name;    					//0c dll名
    DWORD  Base;   						//10 API序号的基数
    DWORD  NumberOfFunctions;      		//14 总的API个数
    DWORD  NumberOfNames;  	 	 		//18 按名字导出的API个数
    DWORD  AddressOfFunctions;     	 	//1c API地址表  
    DWORD  AddressOfNames;       		//20 API名字表   
    DWORD  AddressOfNameOrdinals;  		//24 API序号表
}IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;

比起输入表,多了一个地址表,这三个表都是RVA地址,实际上地址表的内容是对应序号的入口指针,查地址表到最后还是要查序号表(所以为什么有这个地址表呢?)

重定位表

重定位表的结构非常简单,它包含一堆重定位项的RVA,而第一项是重定位基址。:

struct_IMAGE_BASE_RELOCATION
{
	DWORD  VirtualAddress;    	//00 重定位数据开始的RVA地址
    DWORD  SizeOfBlock;     	//04 重定位块的长度
    WORD   TypeOffset;     		//08 重定位项位数组
}IMAGE_BASE_RELOCATION;

重定位数组的每项有4个字节,第一个字节是重定位类型,后三个字节是重定位的RVA。32位系统的的重定位过程和16位其实差别不大,主要区别在于前者使用相对虚拟地址(RVA)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值