PE格式详解.docx
- 文档编号:2397486
- 上传时间:2022-10-29
- 格式:DOCX
- 页数:20
- 大小:34.44KB
PE格式详解.docx
《PE格式详解.docx》由会员分享,可在线阅读,更多相关《PE格式详解.docx(20页珍藏版)》请在冰豆网上搜索。
PE格式详解
PE格式详解
本文来自CSDN博客,转载请标明出处:
本系列主要是参考英文原本ARTeam的PEFileFormatTutorial并加以注解,以最简洁的语言来阐述PE格式,帮助大家快速入门。
在开始之前,请确定您懂得C语言,至少是基本数据类型、数组和结构体,以及会WinHEX的基本使用方法。
任何错误都欢迎指出,感激不尽!
(一)介绍PE格式
PE格式(PortableExecutable,可移植可执行文件)是原生的Win32文件格式,任何的32位应用程序(Executable)包括.NET、动态链接库(DLL)、控件(OCX)以及控制面板程序(CPL)均为PE格式,其实NT内核ntoskrnl.exe也是PE格式。
我们研究PE格式的目的有:
一是给应用程序添加代码功能,比如注册机代码注入(KeygenInjecton);二是手动给EXE脱壳。
现在很多程序都加“壳”,一来是减少映像文件尺寸,二来可以保护程序文件不被轻易修改。
被加壳的EXE一般它的输入表(importtable)与数据区段(.data)都被加密过,并且被插入了一段脱壳的代码,当程序执行时会先执行这段代码给内存中的数据解密,接着会修复输入表和各个区段,然后再跳到原始入口点执行。
原文中使用的范例程序是delphi写的进制转换程序,本文中我使用的是VC9编写的一个很简单的数组求和程序,在debug模式下编译。
这并不影响我们学习PE格式。
首先两个概念,PE文件被加载器加载到内存后被称为一个模块(module),存储模块首地址的称为模块句柄(handleofmodule),简写为HMODULE。
然后我们看一下PE格式的总体结构(这张图MS已经遍布全球了):
PICTUREMISSING
前4块我们后面会细讲,就区段来说(Section),一个PE文件至少要有2个区段,代码区段用来存储程序代码,以及数据区段用来存储各种数据。
NT为PE预定义了9个区段:
.text,.bss,.rdata,.data,.rsrc,.edata,.idata,.pdata,.debug。
一个PE既可以选择其中的几个区段,也可以自己定义额外的区段满足特别需要。
一般来说,.text是代码区段,.data、.rdata、.bss是数据区段,.rsrc是资源区段,.edata与.idata分别是输出输入表区段,.debug是调试信息区段。
这些名字(.text,.data…)实际上是给程序员看着方便的,执行程序时,系统会完全忽略它们。
最后一点,就是PE文件在硬盘中的结构顺序和当它们被加载到内存中后的顺序并非一样,记录硬盘上排列的是FileAlignment域(域是含有特定信息的二进制片段),记录内存中排列的是SectionAlignment域,这既是PE加载器调整的结果,也是虚拟内存机制的作用。
关于Windows虚拟内存机制,请参考相关资料,或者我的另一篇文章。
就是有一点要注意的是,PE文件中的每一个区段总是在一个新的页面中存放的。
一个PE程序总是以一个64字节的DOSheader结构开头,目的就是为了如果程序在DOS中运行,DOS会识别它为正确的EXE并进而运行DOSstub,它的作用就是输出字符串”ThisprogramcannotruninDOSmode.”然后退出。
下面就是DOS Header在C语言中的结构表示(来自MinGW的winnt.h)
typedefstruct_IMAGE_DOS_HEADER{
WORDe_magic;//MagicDOS签名MZ(4Dh5Ah)
WORDe_cblp;//文件最后一页的字节数
WORDe_cp;//文件中的页数
WORDe_crlc;//重定位的个数
WORDe_cparhdr;//段落中头的长度
WORDe_minalloc;//额外段落需要的最小分配值
WORDe_maxalloc;// 额外段落需要的最大分配值
WORDe_ss;//初始SS寄存器值
WORDe_sp;//初始SP寄存器值
WORDe_csum;//校验和
WORDe_ip;//初始IP值
WORDe_cs;//初始CS值
WORDe_lfarlc;//重定位表在文件中的地址
WORDe_ovno;//覆盖数量
WORDe_res[4];//保留字
WORDe_oemid;//OEM标识符
WORDe_oeminfo;//OEM信息
WORDe_res2[10];// 保留字
LONGe_lfanew;//PEheader的起始偏移量
}IMAGE_DOS_HEADER,*PIMAGE_DOS_HEADER;
首先声明, WORD表示unsignedshortint(2bytes),而LONG和后面会出现的DWORD表示unsignedint(4bytes),其定义符合C99标准。
我们只关心其中两个成员,e_magic是MagicNumber,它总是等于0x4d5a,也就是MZ这两个字符,这是为了几纪念MarkZbikowsky,MS-DOS设计者之一。
e_lfanew指向的是PE header,这才是PE文件真正的开始,Win32EXE加载器会读取其中的地址并找到PEheader,DOSstub因此被跳过。
我们可以来实践一下下,用WinHEX打开我们的范例程序testPE.exe。
PICTUREMISSING
可以看到前两个byte,确实是MZ表示一个合法的EXE文件,图中的前四行(0h~3Fh)共64字节即为IMAGE_DOS_HEADER结构,e_lfanew是最后四个字节,从图中我们可以读出,位于3Ch~3Fh的依次是D8,00,00,00,因为机器中整型存放遵循高位在高地址处的原则,所以实际的值是00,00,00,D8,我们顺势找到D8h,马上就发现了PE两个字,从这里开始便是真正的PEheader。
那么位于40h到D7h的东西从PE结构图就可以看出是DOS stub,其实就是一段汇编代码,我们就不管了。
PEheader对应C中的IMAGE_NT_HEADER32结构,如下:
typedefstruct_IMAGE_NT_HEADERS{
DWORDSignature;// PESignaturePE..(50h45h00h00h)
IMAGE_FILE_HEADERFileHeader;// PE的文件头信息
IMAGE_OPTIONAL_HEADEROptionalHeader;// 可选头
}IMAGE_NT_HEADERS32,*PIMAGE_NT_HEADERS32;
再是真实PE文件的截图:
PICTUREMISSING
(一)中我们已经得到PEheader的起始地址D8h,直到DBh,共4个字节,就是结构中的Signature成员,数据为50h45h00h00h,对应值为00004550h(注,以后凡说到值,均指按高位在高地址的原则倒过来后的实际值,不再说明),对应winnt.h中的常数IMAGE_NT_SIGNATURE,如果Signature等于454Ch表示是IMAGE_VXD_SIGNATURE,即Win3.X中的VirtualDeviceDriver;等于454Eh表示IMAGE_OS2_SIGNATURE,即OS2的程序等等,请自行查阅winnt.h。
IMAGE_NT_HEADERS32结构只有三个成员,比较简单,但是展开后两个成员的结构体IMAGE_FILE_HEADER,IMAGE_OPTIONAL_HEADER,就会让人绝倒了。
。
。
我们首先看一下IMAGE_FILE_HEADER结构:
typedefstruct_IMAGE_FILE_HEADER{
WORDMachine;// 支持机器的类型
WORDNumberOfSections;// 区段的个数
DWORDTimeDateStamp;// 连接器创建EXE时的日期时间
DWORDPointerToSymbolTable;//旧文件中,COFF符号表地址,没有是0
DWORDNumberOfSymbols;// COFF符号表中符号的个数
WORDSizeOfOptionalHeader;//Optionalheader的长度(32位EXE中是224字节)
WORDCharacteristics;// 见下面的说明
}IMAGE_FILE_HEADER,*PIMAGE_FILE_HEADER;
结合图,分析如下:
Machine值为014Ch,对应IMAGE_FILE_32BIT_MACHINE常数,即32位机器,其他值请查阅winnt.h。
接着两个字节表示NumberOfSections区段的个数,值为0007h,也就是我们的testPE.exe有7个区段。
SizeOfOptionalHeader位于ECh,EDh,值为00E0h,即后面的OptionalHeader结构总长为224字节。
再看一个Characteristics,表示这个PE文件的类型,图中位于EEh和EFh,值为0102h,是使用二进制位来标记的,二进制为100000010 =IMAGE_FILE_32BIT_MACHINE|IMAGE_FILE_EXECUTABLE_IMAGE,表示该文件是32位的EXE文件,如果它的值是2XXXh,则表示是一个DLL,具体还请查阅winnt.h。
(唉。
。
。
这句话打的好累啊,以后就说自行查阅了)
特地把Optionalheader放一篇文章,是因为它比较复杂与庞大,也因为它比较重要。
老样子,先是IMAGE_OPTIONAL_HEADER结构:
typedefstruct_IMAGE_OPTIONAL_HEADER{
WORDMagic;// 又是Magic
BYTEMajorLinkerVersion;// 链接器主要版本
BYTEMinorLinkerVersion;// 链接器次要版本
DWORDSizeOfCode;// 所有代码区段的长度之和
DWORDSizeOfInitializedData;// 同上
DWORDSizeOfUninitializedData;// 同上
//代码起始执行处,对DLL可选,没有是0
DWORDAddressOfEntryPoint;
DWORDBaseOfCode;// 载入内存后代码的起始地址,相对ImageBase而言
DWORDBaseOfData;// 载入内存后代码的起始地址,相对ImageBase而言
DWORDImageBase;// 期望的内存中映像载入地址
DWORDSectionAlignment;// 内存中区段排列基数
DWORDFileAlignment;// 磁盘上区段排列基数
//OS最低主要版本号
WORDMajorOperatingSystemVersion;
//OS最低次要版本号
WORDMinorOperatingSystemVersion;
WORDMajorImageVersion;// 映像文件的主要版本号
WORD
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- PE 格式 详解
![提示](https://static.bdocx.com/images/bang_tan.gif)