PE文件格式

I

import

Unregistered / Unconfirmed
GUEST, unregistred user!
PE6.0格式研究 (1)
作者:陆麟
转载请征得作者同意.
1999.8.2
PE6.0首先在一个可执行文件的偏移3ch处有一个指向'PE00'标记的指针.如果没有这个标记.该文件就不是一个PE的文件.下面演示了一个检测PE文件的函数.
/* DetectPe(char *) Written by Lu Lin. 1999.8.2
Only tested on MS complier and linker.
Entery :
parameter *p: point to the file name. eg. "c:/command.com"
return :
true if the file is in PE format.
false if the file is not a PE file.
*/
BOOL DetectPe(char *p){
HANDLE hf; //handle for the file detecting
short ppe; //point to pe signature
long sig; //signature value retrieved
DWORD res; //store ReadFile()'s actual read bytes
hf=CreateFile(p,GENERIC_READ,FILE_SHARE_READ,0,OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,0);
if (hf==INVALID_HANDLE_VALUE){
GetLastError();
return 0;
}
if (!SetFilePointer(hf,0x3c,0,FILE_BEGIN)){
CloseHandle(hf);
return 0;
}
if (!ReadFile(hf,&psig,2,&res,0)){
CloseHandle(hf);
return 0;
}
if (res!=2){
CloseHandle(hf);
return 0;
}
if (!SetFilePointer(hf,(long)psig,0,FILE_BEGIN)){
CloseHandle(hf);
return 0;
}
if (!ReadFile(hf,&sig,4,&res,0)){
CloseHandle(hf);
return 0;
}
if (res!=4){
CloseHandle(hf);
return 0;
}
if (sig!=0x4550) return 0;
return 1;
}
PE6.0格式研究 (2) SP1
作者:陆麟
转载请征得作者同意.
1999.8.11
PATCHED 8.16
 
 
--------------------------------------------------------------------------------
今天给出PE SIGNATURE后的数据结构.那就是COFF文件头.这里有一些关键的东西.有些很简单.我加了英文注解,有些简单得连注解也不需要.
这个数据结构是我写的PE分析器的一部分,现在先拿来大家看:)我的PE分析器写到哪里,本文就写到哪里:)
typedef struct {
WORD Machine; //Target Machine Type
WORD NumberOfSections; //Number of Sections
DWORD TimeDateStamp; //Creation Time
DWORD PointerToSymbolTable; // Point to Symbol Table
DWORD NumberOfSymbols; //Number of Symbol Table Entry
WORD SizeOfOpitionalHeader; //
WORD Characteristics; //
}CoffHead,*pCoffHead;
MACHINE如果是14ch,就是可以运行在INTEL386以上CPU的两进制文件.其他的似乎没什么用.如184h for alpha, 284h for alpha64,268h for motorola,1f0h for power pc...,有关该处的定义如果有人需要,问我要吧,或者自己到微软去下载.
Characteristics
IMAGE_FILE_RELOCS_STRIPPED 0x0001
Image only, Windows CE, Windows NT and above.表示本PE文件不可以重定位.
IMAGE_FILE_EXECUTABLE_IMAGE 0x0002 Image only. 表示文件可用.如果为0,一般是LINK时出问题了.
IMAGE_FILE_LINE_NUMS_STRIPPED 0x0004 COFF 行号被删除标志.
IMAGE_FILE_LOCAL_SYMS_STRIPPED 0x0008 没有符号表.
IMAGE_FILE_AGGRESSIVE_WS_TRIM 0x0010 不懂.
IMAGE_FILE_LARGE_ADDRESS_AWARE 0x0020 应用程序能处理 > 2gb 地址.该功能是从NTSP3开始被支持.因为大部分的数据库服务器需要很大的内存,而NT仅提供2G给应用程序.从NTSP3开始,通过加载/3GB参数,可以使应用程序分配到2G->3G区域的地址.而该处原先是属于系统内存区.该功能在NTSP3的MSDN版公开,但是好象其他文档里尚没有公开.
IMAGE_FILE_16BIT_MACHINE 0x0040 reserved.
IMAGE_FILE_BYTES_REVERSED_LO 0x0080 Little endian: LSB precedes MSB in memory.
IMAGE_FILE_32BIT_MACHINE 0x0100 目标平台为32位机器.
IMAGE_FILE_DEBUG_STRIPPED 0x0200 没有DEBUG信息.
IMAGE_FILE_REMOVABLE_RUN_FROM_SWAP 0x0400 如果程序在软盘上,就从SWAP区运行.
IMAGE_FILE_SYSTEM 0x1000 表明本程序是系统文件,非应用程序.
IMAGE_FILE_DLL 0x2000 映象是个DLL.
IMAGE_FILE_UP_SYSTEM_ONLY 0x4000 仅可运行在单处理器的机器上.
IMAGE_FILE_BYTES_REVERSED_HI 0x8000 Big endian: MSB precedes LSB in memory.
(此次SP1补充了上次忘记了的特征值.)
PE6.0格式研究 (3)
作者:陆麟
转载请征得作者同意.
1999.8.17
 
 
--------------------------------------------------------------------------------
今天我的PE分析器写完了COFFHEAD部分.接下来也该写点东西给主页了:)
在COFFHEAD下面有一个至关重要的数字.这个数字乃6.0和以前PE格式的根本去别的识别字.
先说说MS可执行文件的限制.MS对一个WIN32可执行文件的大小限制为4G.当然是根据该文件所需分配内存为计算条件.包括代码,数据,栈,线程局部存储...但是目前的WIN32平台包括9X和NT4,都给了应用程序不到2G的可用空间.(NT4SP3开始,如果应用程序标志位IMAGE_FILE_LARGE_ADDRESS_AWARE为1的话,系统将给应用程序近3G的可用空间.)那么如果一个应用程序映象真的要4G,岂非大是糟糕.这样的程序尽管符合规范,但是却永远运行不起来.现在,MS为了新的64位OS已经打好准备.应用程序映象可以真正达到4G,而且可以用64位地址了.
这个标志就在于紧接于COFFHEAD后的2字节里.当年如果该处值为10BH的话,就说明字节的确是个PE32文件.而现在MS扩展了该处的含义.如果该处值为20BH,就说明本文件是个PE32增强文件.
也就是说:
本程序可能用了64位的地址指针,而且标志位后面的数据格式不同于老的PE32文件了.
在该标志位后乃是OPTIONAL HEADER.所有的可执行文件都必须有一个这样的头,将对可执行映象定位起到决定性的作用.而OBJ文件可以有,但是通常情况下,该OPTIONAL HEADER没有功能.
在规范里,老PE32可选头的大小为224字节.而增强PE32的可选头的大小为240字节.该处大小有COFFHEAD偏移16字节处的WORD决定.
关于可选头的详细细节且听下回分解
PE6.0格式研究 (4)
作者:陆麟
转载请征得作者同意.
1999.8.28
PATCHED 1999.9.29
 
 
--------------------------------------------------------------------------------
太忙,太忙,实在没空写程序,但是还是要搞点东西出来.
PE文件在COFF头之后就是可选头.在可选头里的第一个项,上次已经讲过了.在整个可选头里,尽管PE32和PE32+的结构是不同的,但还是有少量字段是相同的,写个结构,可以在写程序的时候省点力气.下面给出PE32和PE32+相同的部分.
typedef struct {
WORD Magic; //0x10b if pe32,0x20b if pe64,0x107 if rom image
BYTE MajorLinkerVersion; //
BYTE MinorLinkerVersion; //
DWORD SizeOfCode; //
DWORD SizeOfInitializedData; //
DWORD SizeOfUninitializedData; //BSS section's size
DWORD AddressOfEntryPoint; //Entrypoint relative to image base
DWORD BaseOfCode; //
__int64 ImageBase; //If pe32, ImageBase>>32 is BaseOfData,
//Imagebase&0xffff is ImageBase.
DWORD SectionAlign; //When load into memory, each section align to
DWORD FileAlign; //File alignment in byte.
WORD MajorOSV; //Major Target OS Version
WORD MinorOSV; //Minor Target OS Version
WORD MajorImageVer; //
WORD MinorImageVer; //
WORD MajorSV; //Major subsystem version
WORD MinorSV; //Minor subsystem version
DWORD UNKNOW;
DWORD SizeOfImage; //
DWORD SizeOfHeards; //Size of dos-stub+PE header+section headers
DWORD CheckSum; //
WORD Subsystem;
WORD DLL_Characteristics;
}_CommOptHead,*pCommOptHead;
结果应该没什么问题,因为我已经加了英文注解.其中唯一需要强调的是ImageBase字段.这个数据在PE32+里是个64位数据(__int64是MS VC编译器提供的便捷方法,标注一个64位值),在PE32里是2个值:DWORD BASEOFDATA, DWORD IMAGEBASE. 由于BASEOFDATA数据并没有什么用,所以在规范里被删除了.其实老实说,连BASEOFCODE也没什么用的.因为PE LOADER会自动识别所有的SECTION.因为在SECTION HEADER里含有所有有关程序运作的必须描述.关于如何使用IMAGEBASE的方法,我在结构里的说明里已经写了,请自己看.结构中AddressOfEntryPoint是真正的程序入口点.通常情况下,它指向CRTL的启动代码库.当然,可以手工修改.具体方法请参照所用的编译器的说明.CIH病毒就是更改了这里,指向病毒代码,使自己被加载.当自己被加载后,才跳转到原代码开始处.
这回就说到这里,且听下会分解.
PE6.0格式研究 (5)
作者:陆麟
转载请征得作者同意.
1999.9.12
 
 
--------------------------------------------------------------------------------
PE分析被拖延了一段时间(给女朋友闹的:)).今天又开始了.
完整的PE32或PE32+可选头结构如下.
typedef struct {
_CommOptHead CommOptHead;
unsigned __int64 SizeOfStackReserve;
unsigned __int64 SizeOfStackCommit;
unsigned __int64 SizeOfHeapReserve;
unsigned __int64 SizeOfHeapCommit;
DWORD USELESS;
DWORD NumberOfDataEntry;
_IMG_DATA_DIR ImgDataDir[0]; //Do remember ImgDataDir
//probably doesn't contents
//16 entries. The number of
//entery depends on field
//NumberOfDataEntry
}OptHead64,*pOptHead64;
typedef struct {
_CommOptHead CommOptHead;
DWORD SizeOfStackReserve;
DWORD SizeOfStackCommit;
DWORD SizeOfHeapReserve;
DWORD SizeOfHeapCommit;
DWORD USELESS;
DWORD NumberOfDataEntry;
_IMG_DATA_DIR ImgDataDir[0];
}OptHead32,*pOptHead32;
OptHead64是PE32+的可选头结构.另一个是PE32的.里面也没什么可以多解释的了.
_IMG_DATA_DIR结构如下.
typedef struct {
PEIMAGE_DATA_DIRECTORY ExpTbl; //Export Table RVA and size
PEIMAGE_DATA_DIRECTORY ImpTbl; //Import ..................
PEIMAGE_DATA_DIRECTORY ResTbl; //Resources ...............
PEIMAGE_DATA_DIRECTORY ExcptTbl; //Execption ...........
PEIMAGE_DATA_DIRECTORY CrtfTbl; //Certificate .........
PEIMAGE_DATA_DIRECTORY BaseRelocTbl;
PEIMAGE_DATA_DIRECTORY DbgTbl;
PEIMAGE_DATA_DIRECTORY Architecture;
PEIMAGE_DATA_DIRECTORY GlobalPtr;
PEIMAGE_DATA_DIRECTORY TlsTbl;
PEIMAGE_DATA_DIRECTORY LoadConfigTbl;
PEIMAGE_DATA_DIRECTORY BoundImpTbl;
PEIMAGE_DATA_DIRECTORY IAT; //Import Address ..........
PEIMAGE_DATA_DIRECTORY DelayImpDes;
PEIMAGE_DATA_DIRECTORY COMRT_H; //COM+ runtime header..
PEIMAGE_DATA_DIRECTORY RESERVED;
}_IMG_DATA_DIR,*pIMG_DATA_DIR;
由于没有硬性规定_IMG_DATA_DIR一定要含有16个PEIMAGE_DATA_DIRECTORY结构.所以真正的PEIMAGE_DATA_DIRECTORY数目一定是按照NumberOfDataEntry来的.如果NumberOfDataEntry显示数字是8,那么8个PEIMAGE_DATA_DIRECTORY之后就是其他数据结构了.但是,目前所有的LINKER都将NumberOfDataEntry设置为16,所以,我在PE32X的OPTNL头文件里提供的数据还是一个_IMG_DATA_DIR结构.但长度为0.
经管没有PEIMAGE_DATA_DIRECTORY数量的规定,但是_IMG_DATA_DIR结构里的PEIMAGE_DATA_DIRECTORY出现的顺序却是有规矩的.规矩就如上.第一个代表引出表,第二个代表引入表等等.每个PEIMAGE_DATA_DIRECTORY的结构如下.
typedef struct {
DWORD RVA;
DWORD SIZE;
}PEIMAGE_DATA_DIRECTORY,*pPEIMAGE_DATA_DIRECTORY;
 
这回就说到这里,且听下会分解.
PE6.0格式研究 (6)
作者:陆麟
转载请征得作者同意.
1999.9.13
 
 
--------------------------------------------------------------------------------
typedef struct {
PEIMAGE_DATA_DIRECTORY ExpTbl; //Export Table RVA and size
PEIMAGE_DATA_DIRECTORY ImpTbl; //Import ..................
PEIMAGE_DATA_DIRECTORY ResTbl; //Resources ...............
PEIMAGE_DATA_DIRECTORY ExcptTbl; //Execption ...........
PEIMAGE_DATA_DIRECTORY CrtfTbl; //Certificate .........
......
}_IMG_DATA_DIR,*pIMG_DATA_DIR;
这个结构是上次就公开了的.想来还有部分东西要讲解.
ExpTbl如果存在的话,将是一个.data SECTION的RVA. RVA是相对于HMODULE的偏移.例如RVA 1000H,HMOD为400000,实际内存里的地址为401000H.使用RVA的原因是为了在MODULE重定位时减少麻烦.有关SECTION的概念将在以后讲到.因为接下来要写的结构就是有关SECTION的.
ImpTbl如果存在,将指向.idata节.同样也是RVA.还有TLSTBL等,都是RVA.
强调是RVA就意味者他们并不一定是相对于文件开始的偏移.当然,在实际编程中,90%的情况下RVA其实和文件偏移是相等的.但是,由于文件对齐和内存定位对齐可能不同,而且CPU的页对齐和文件对齐也并不一定相同,所以,在实际编程中一定要将RVA-SECTION RAW DATA后得出的偏移才是正确的偏移.
单纯地得到COFF OPTIONAL HEADER里的RVA是没有用的.因为他不是文件内部的指针.所以尽管PE文件头提供了这些信息,这些信息却只有在PE文件被映射到进程的地址空间里才有用.说到这里,另外要说的就是.HMOD所指向的数据结构其实就是这里公开的所有的数据结构加上以后要讲的SECTION结构.
这回就说到这里,且听下会分解.
PE6.0格式研究 (7)
作者:陆麟
转载请征得作者同意.
1999.9.19
 
 
--------------------------------------------------------------------------------
今天要讲SECTION_TABLE结构了.它的结构定义为:
typedef struct {
char Name[8];
DWORD VirtualSize;
DWORD VirtualAddress;
DWORD SizeOFRawData;
DWORD pRawData;
DWORD pRelocations;
DWORD pLineNumber;
WORD NumberOfRelocations;
WORD NumberOfLineNumbers;
DWORD SecFlag;
}SECTION_TABLE,*pSECTION_TABLE;
在PE的可执行文件里,Name永远是小于等于8字节长的.如果名字少于8字节,后面就以0填满.如果正好名字是8字节.那么后面就连1个0也不填.紧跟VirtualSize. 在NT,9X下.有几个名字是保留的.他们的命名有关于系统将来的运作.以下是列表.
.arch 存储ALPHA架构信息.
.bss 未初始化数据
.data 初始化数据
.edata 引出函数表
.idata 引入函数表
.pdata EXECPTION信息
.rdata 只读数据
.reloc 重定位
.rsrc 资源目录
.text 可执行代码段
.tls 局部线程存储
.xdata EXECPTION信息
他们都对应于固定的SECTION FLAG.而且,系统的运行依赖于他们的存在或收到影响.例如TLS,TLS信息里有个有关CALLBACK的项.尽管目前没有任何编译器支持TLS的CALLBACK.但是MS还是为将来保留了TLS CALLBACK功能.
好今天先讲到这里.从下回开始要分析另外的关键数据结构:引入/引出函数表了.欢迎观赏:D
PE6.0格式研究 (8)
作者:陆麟
转载请征得作者同意.
1999.9.30
 
 
--------------------------------------------------------------------------------
上次说到PE的节.最近由于搞了点其他KERNEL MODE的东西,PE分析器就没写下去,国庆节我要出门,主页无法更新,所以想趁出门前先贴点东西,免得大家等得失望. :)
就讲解一下节里的部分东西吧!
先讲.edata引出函数表吧.
这个表现在已经基本被废弃了.只有老式的编译器还使用它.大家可以发现.用WIN9X自己带的QUICKVIEW能够看到引出函数或引入函数的DLL已经很少了.为什么.就是因为现在的VC5,6已经不使用该表了.现在的PE文件里,通过PEIMAGE_DATA_DIRECTORY ExpTbl来查找文件的引出函数表.而该指针指向的RVA其实就是文件里的偏移.
有时候我们能够发现,有些函数尽管被引出了,但是却没有函数名.(那是MS常干的事.隐藏调一些关键API.争取自己软件写作上的优势.)这时,历遍函数名称要千万注意,要用引出函数计数来确定到底有多少个被引出的函数是有名字的.哪些没名字的只好用序数调用了.(注意,KERNEL32.DLL里的隐藏API调用需要有些小技巧,这些技巧在MATT的WIN95系统编程奥秘里已经公开了.我就不罗嗦了.)
好了,我要再写个系统类的文章,这里就先止住了.
 
顶部