本文链接:http://blog.csdn.net/u012763794/article/details/51469477
1.介绍
什么是PE文件?
PE文件是windows操作系统下使用的可执行文件格式。32位就直接叫PE或PE32,64位的就PE+或PE32+,注意不是PE64哦!!!!
学习PE文件其实就是学习结构体,里面储存了如何加载到内存,从何处开始运行,运行需要那些dll,需要多大的栈和内存等
初识PE文件
2.PE头
DOS头
我们关注第一个成员和最后一个
e_magic:所有PE开头都有DOS签名 “MZ”,这是以一个名叫Mark Zbikowski的DOS可执行文件的设计者首字母命名的,看图上的前两个字节
e_lfanew:指向NT头的位置,long类型,占4个字节,看上图就是0x0000000E,注意是小端模式哦,不同程序有所不同的
下图箭头位置就是NT头了
DOS存根
NT头
签名结构体
NT头之文件头---IMAGE_FILE_HEADER
WORD Machine; 每个CPU都有唯一的机器码,兼容32位的Intel x86的机器码为14C,具体看下面
#define IMAGE_FILE_MACHINE_UNKNOWN 0
#define IMAGE_FILE_MACHINE_I386 0x014c // Intel 386.
#define IMAGE_FILE_MACHINE_R3000 0x0162 // MIPS little-endian, 0x160 big-endian
#define IMAGE_FILE_MACHINE_R4000 0x0166 // MIPS little-endian
#define IMAGE_FILE_MACHINE_R10000 0x0168 // MIPS little-endian
#define IMAGE_FILE_MACHINE_WCEMIPSV2 0x0169 // MIPS little-endian WCE v2
#define IMAGE_FILE_MACHINE_ALPHA 0x0184 // Alpha_AXP
#define IMAGE_FILE_MACHINE_SH3 0x01a2 // SH3 little-endian
#define IMAGE_FILE_MACHINE_SH3DSP 0x01a3
#define IMAGE_FILE_MACHINE_SH3E 0x01a4 // SH3E little-endian
#define IMAGE_FILE_MACHINE_SH4 0x01a6 // SH4 little-endian
#define IMAGE_FILE_MACHINE_SH5 0x01a8 // SH5
#define IMAGE_FILE_MACHINE_ARM 0x01c0 // ARM Little-Endian
#define IMAGE_FILE_MACHINE_THUMB 0x01c2 // ARM Thumb/Thumb-2 Little-Endian
#define IMAGE_FILE_MACHINE_ARMNT 0x01c4 // ARM Thumb-2 Little-Endian
#define IMAGE_FILE_MACHINE_AM33 0x01d3
#define IMAGE_FILE_MACHINE_POWERPC 0x01F0 // IBM PowerPC Little-Endian
#define IMAGE_FILE_MACHINE_POWERPCFP 0x01f1
#define IMAGE_FILE_MACHINE_IA64 0x0200 // Intel 64
#define IMAGE_FILE_MACHINE_MIPS16 0x0266 // MIPS
#define IMAGE_FILE_MACHINE_ALPHA64 0x0284 // ALPHA64
#define IMAGE_FILE_MACHINE_MIPSFPU 0x0366 // MIPS
#define IMAGE_FILE_MACHINE_MIPSFPU16 0x0466 // MIPS
#define IMAGE_FILE_MACHINE_AXP64 IMAGE_FILE_MACHINE_ALPHA64
#define IMAGE_FILE_MACHINE_TRICORE 0x0520 // Infineon
#define IMAGE_FILE_MACHINE_CEF 0x0CEF
#define IMAGE_FILE_MACHINE_EBC 0x0EBC // EFI Byte Code
#define IMAGE_FILE_MACHINE_AMD64 0x8664 // AMD64 (K8)
#define IMAGE_FILE_MACHINE_M32R 0x9041 // M32R little-endian
#define IMAGE_FILE_MACHINE_CEE 0xC0EE
WORD NumberOfSections;
有多少个节区,一定大于0 ,比如基本的,什么代码区(.text),数据(.data),资源(.rsrc)区,若跟实际的不符则出错
DWORD TimeDateStamp;
文件时何时创建的
DWORD PointerToSymbolTable;
这里指向一个表,很少使用,网上也没怎么介绍
DWORD NumberOfSymbols;
上面表的数量
WORD SizeOfOptionalHeader;
说明IMAGE_OPTIONAL_HEADER32(可选头)的大小
WORD Characteristics;
用于标识文件的属性(0x0002,2000h一定记住,一个是可执行文件,一个是DLL),具体看下
#define IMAGE_FILE_RELOCS_STRIPPED 0x0001 // Relocation info stripped from file.
#define IMAGE_FILE_EXECUTABLE_IMAGE 0x0002 // File is executable (i.e. no unresolved externel references).
#define IMAGE_FILE_LINE_NUMS_STRIPPED 0x0004 // Line nunbers stripped from file.
#define IMAGE_FILE_LOCAL_SYMS_STRIPPED 0x0008 // Local symbols stripped from file.
#define IMAGE_FILE_AGGRESIVE_WS_TRIM 0x0010 // Agressively trim working set
#define IMAGE_FILE_LARGE_ADDRESS_AWARE 0x0020 // App can handle >2gb addresses
#define IMAGE_FILE_BYTES_REVERSED_LO 0x0080 // Bytes of machine word are reversed.
#define IMAGE_FILE_32BIT_MACHINE 0x0100 // 32 bit word machine.
#define IMAGE_FILE_DEBUG_STRIPPED 0x0200 // Debugging info stripped from file in .DBG file
#define IMAGE_FILE_REMOVABLE_RUN_FROM_SWAP 0x0400 // If Image is on removable media, copy and run from the swap file.
#define IMAGE_FILE_NET_RUN_FROM_SWAP 0x0800 // If Image is on Net, copy and run from the swap file.
#define IMAGE_FILE_SYSTEM 0x1000 // System File.
#define IMAGE_FILE_DLL 0x2000 // File is a DLL.
#define IMAGE_FILE_UP_SYSTEM_ONLY 0x4000 // File should only be run on a UP machine
#define IMAGE_FILE_BYTES_REVERSED_HI 0x8000 // Bytes of machine word are reversed.
NT头之可选头------- IMAGE_OPTIONAL_HEADER32
typedef struct _IMAGE_DATA_DIRECTORY {
DWORD VirtualAddress;
DWORD Size;
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
#define IMAGE_NUMBEROF_DIRECTORY_ENTRIES 16
真正的定义
typedef struct _IMAGE_OPTIONAL_HEADER {
//
// Standard fields.
//
WORD Magic;
BYTE MajorLinkerVersion;
BYTE MinorLinkerVersion;
DWORD SizeOfCode;
DWORD SizeOfInitializedData;
DWORD SizeOfUninitializedData;
DWORD AddressOfEntryPoint;
DWORD BaseOfCode;
DWORD BaseOfData;
//
// NT additional fields.
//
DWORD 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;
IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;
//
// Standard fields.
//
WORD Magic; 32位为10B,64位20B
BYTE MajorLinkerVersion; 主链接版本
BYTE MinorLinkerVersion; 次链接版本
DWORD SizeOfCode; 代码节的大小
DWORD SizeOfInitializedData; 已初始化数据大小
DWORD SizeOfUninitializedData; 未初始化数据大小
DWORD AddressOfEntryPoint; 入口点的RVA,非常重要,指明哪里最先执行代码
DWORD BaseOfCode; 代码段的RVA(相对虚拟地址)
DWORD BaseOfData; 数据点的RVA
//
// NT additional fields.
//
DWORD ImageBase; 文件首选建议装入的地址,运行时,EIP就设置为ImageBase+AddressOfEntryPoint了
DWORD SectionAlignment; 节区对齐,就是节区的最小占用多少
DWORD FileAlignment; 文件对齐,文件最小是多少,不够应该补0 吧
WORD MajorOperatingSystemVersion; 操作系统最低主版本
WORD MinorOperatingSystemVersion; 最低次版本
WORD MajorImageVersion; 可执行文件主版本
WORD MinorImageVersion; 次版本
WORD MajorSubsystemVersion; 最低子操作系统主版本
WORD MinorSubsystemVersion; 次版本
DWORD Win32VersionValue; 保留字段
DWORD SizeOfImage; 装入内存后的总大小
DWORD SizeOfHeaders; PE头大小(dos头,一直到节表头)
DWORD CheckSum; 校验和
WORD Subsystem; 可执行文件的子系统类型(是系统驱动文件还是普通可执行文件)
WORD DllCharacteristics; dll文件类型
DWORD SizeOfStackReserve; 为线程保留的栈大小
DWORD SizeOfStackCommit; 已提交的栈大小
DWORD SizeOfHeapReserve; 保留的堆大小
DWORD SizeOfHeapCommit; 已提交的堆大小
DWORD LoaderFlags; 被废弃了
DWORD NumberOfRvaAndSizes; 数据目录项个数,即下面DataDirectory的个数
IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];数据目录表,包括输入输出表,资源等
看看具体是那些,刚好是.text前面
0xe0字节,224字节,16*14字节,那就是14行咯
(下图的左边的地址为10进制)
重点是最后一个成员IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
虽然常量定义其为16个,但最终还是由倒数第二个变量决定NumberOfRvaAndSizes
winnt.h中定义了15项,最后一项保留
#define IMAGE_DIRECTORY_ENTRY_EXPORT 0 // Export Directory
#define IMAGE_DIRECTORY_ENTRY_IMPORT 1 // Import Directory
#define IMAGE_DIRECTORY_ENTRY_RESOURCE 2 // Resource Directory
#define IMAGE_DIRECTORY_ENTRY_EXCEPTION 3 // Exception Directory
#define IMAGE_DIRECTORY_ENTRY_SECURITY 4 // Security Directory
#define IMAGE_DIRECTORY_ENTRY_BASERELOC 5 // Base Relocation Table
#define IMAGE_DIRECTORY_ENTRY_DEBUG 6 // Debug Directory
// IMAGE_DIRECTORY_ENTRY_COPYRIGHT 7 // (X86 usage)
#define IMAGE_DIRECTORY_ENTRY_ARCHITECTURE 7 // Architecture Specific Data
#define IMAGE_DIRECTORY_ENTRY_GLOBALPTR 8 // RVA of GP
#define IMAGE_DIRECTORY_ENTRY_TLS 9 //
#define IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG 10 // Load Configuration Directory
#define IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT 11 // Bound Import Directory in headers
#define IMAGE_DIRECTORY_ENTRY_IAT 12 // Import Address Table
#define IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT 13 // Delay Load Import Descriptors
#define IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR 14 // COM Runtime descriptor
索引为 7 那个不同cpu的架构就选的不同吧
因为DataDirectory数组里保存了导入表(用了哪些dll),导出表,TLS(Thread Local Storage) Directory等RVA和大小的信息
关于导出表:
下面是某个博客原文
英文:When the PE loader runs a program, it loads the associated DLLs into the process address space. It then extracts information about the import functions from the main program. It uses the information to search the DLLs for the addresses of the functions to be patched into the main program. The place in the DLLs where the PE loader looks for the addresses of the functions is the export table.
我可能不是很对的翻译
中文:当PE装载器运行一个程序,它装入导入表的dll到进程的对应的地址空间,跟着从main函数展开导入函数的信息,接着用这些信息搜索dll中导入函数的地址,跟着放在main函数的调用该函数的位置上。那么导出表就是dll中查找某个函数地址的地方
导出表,导出嘛,就是给别人用,你要告诉别人函数的地址在哪啊
那最后一个成员在哪呢,因为一个数组占8个字节,共16个,从后面数上来就可以了
(下图的左边的地址为10进制)
可以看到索引为0的导出表是全0,因为是记事本,索引没有导出表吧,一般dll才会有
索引1的就是导入表了,可以看到RVA为0x7604,但在文件中查看要转化为文件偏移,因为文件中存的是内存中的相对地址
这里就涉及节区头的概念,看下一小节的节区头的截图,(以四字节为单位,每个节区头第四个四字节,不懂的可以看看节区头结构体中VirtualAddress的位置)VirtualAddress就是节区的RVA
可看到.text的RVA为0x1000,.data的RVA为0x9000,.rsrc的RVA为0xB000
很明显0x7604在.text节区,那么它距离.text的起始位置就是0x7604-0x1000=0x6604,
因为无论文件还是内存中,距离.text的起始位置都是一样的,所以0x6604也是文件中距离.text的起始位置的距离
最后加上.text的起始位置的文件偏移(PointerToRawData)即可,PointerToRawData还是在节区头中,看看下面,VirtualAddress再数两个4字节,还是看图吧
(下图的左边的地址为10进制)
所以最终求出0x7604的文件偏移为0x0400 + 0x6604 = 0x6A04
我们看看0x6A04是不是有导入表的信息,即导入表结构体的信息
可以看到刚好5个双字,即20字节
typedef struct _IMAGE_IMPORT_DESCRIPTOR {
union {
DWORD Characteristics; // 0 for terminating null import descriptor
DWORD OriginalFirstThunk; // RVA to original unbound IAT (PIMAGE_THUNK_DATA)
} DUMMYUNIONNAME;
DWORD TimeDateStamp; // 0 if not bound,
// -1 if bound, and real date\time stamp
// in IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT (new BIND)
// O.W. date/time stamp of DLL bound to (Old BIND)
DWORD ForwarderChain; // -1 if no forwarders
DWORD Name;
DWORD FirstThunk; // RVA to IAT (if bound this IAT has actual addresses)
} IMAGE_IMPORT_DESCRIPTOR;
typedef IMAGE_IMPORT_DESCRIPTOR UNALIGNED *PIMAGE_IMPORT_DESCRIPTOR;
我们来验证一下吧,第4个Name成员比较直观,RVA :0x7AAC,那么文件偏移(RAW):0x7AAC-0x1000+0x400=0x6EAC
可以看到没错的,其他的成员自己找吧
节区头
#define IMAGE_SIZEOF_SHORT_NAME 8
typedef struct _IMAGE_SECTION_HEADER {
BYTE Name[IMAGE_SIZEOF_SHORT_NAME];
union {
DWORD PhysicalAddress;
DWORD VirtualSize;
} Misc;
DWORD VirtualAddress;
DWORD SizeOfRawData;
DWORD PointerToRawData;
DWORD PointerToRelocations;
DWORD PointerToLinenumbers;
WORD NumberOfRelocations;
WORD NumberOfLinenumbers;
DWORD Characteristics;
} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;
union {
DWORD PhysicalAddress;
DWORD VirtualSize; 内存中节区所占大小,不一定是内存对齐后的值
} Misc;
DWORD VirtualAddress; 内存中节区的起始RVA
DWORD SizeOfRawData; 磁盘中节区所占大小
DWORD PointerToRawData; 磁盘中节区的起始RVA
DWORD PointerToRelocations;
DWORD PointerToLinenumbers;
WORD NumberOfRelocations;
WORD NumberOfLinenumbers;
DWORD Characteristics; 节区属性,由下面的组合而成(按位或,bit OR)
#define IMAGE_SCN_CNT_CODE 0x00000020 // Section contains code.
#define IMAGE_SCN_CNT_INITIALIZED_DATA 0x00000040 // Section contains initialized data.
#define IMAGE_SCN_CNT_UNINITIALIZED_DATA 0x00000080 // Section contains uninitialized data.
#define IMAGE_SCN_MEM_SHARED 0x10000000 // Section is shareable.
#define IMAGE_SCN_MEM_EXECUTE 0x20000000 // Section is executable.
#define IMAGE_SCN_MEM_READ 0x40000000 // Section is readable.
#define IMAGE_SCN_MEM_WRITE 0x80000000 // Section is writeable.
比如
代码区: 具有执行,读取权限
数据区: 非执行,读写权限
资源区: 非执行,读权限
具体看看在记事本的哪里,首先大小0x28=40字节
通过显示的名字我们看到有3个节区头,第一个字段是节区的名字也是符合的
.text节区头
.data节区头
.rsrc节区头
好了就先这样,有什么再补充