一直想做一个PE结构的总结,只是学的时候有很多东西就没搞懂,加上时间一长,很多知识也早忘了,也就一直没完成。这几天从头看了下,好不容易理清楚了,整理一下,以免又忘了
pe文件框架结构,图片贴过来太模糊了就画个表格代替一下
DOS文件头 | PE文件头 | 区块表 | 各区块 | 调试信息 |
DOS文件头
DOS文件头包括DOS MZ头和DOS stub
MS-DOS头部是一个IMAGE_DOS_HEADER结构(代码来自windows.inc)
IMAGE_DOS_HEADER STRUCT +0h e_magic WORD ? ;DOS可执行文件标记"MZ" +2h e_cblp WORD ? +4h e_cp WORD ? +6h e_crlc WORD ? +8h e_cparhdr WORD ? +0a e_minalloc WORD ? +0c e_maxalloc WORD ? +0e e_ss WORD ? +10 e_sp WORD ? +12 e_csum WORD ? +14 e_ip WORD ? +16 e_cs WORD ? +18 e_lfarlc WORD ? +1a e_ovno WORD ? +1c e_res WORD 4 dup(?) +24 e_oemid WORD ? +26 e_oeminfo WORD ? +28 e_res2 WORD 10 dup(?) +3c e_lfanew DWORD ? ;指向PE文件头 IMAGE_DOS_HEADER ENDS
此结构共64字节
这里面只有第一个和最后一个字段较为重要,e_magic字段被设置为5A4Dh,其ASCII值为"MZ"(4D5Ah)<小端存储>
偏移为3ch的e_lfanew字段指出真正的PE头的文件偏移位置
接下来的DOS stub(DOS块)实际上是一个有效的EXE,在DOS下显示一个错误提示
PE文件头
PE文件头是一个IMAGE_NT_HEADERS结构
IMAGE_NT_HEADERS STRUCT +0h Signature DWORD ? ;PE文件表示 +4h FileHeader IMAGE_FILE_HEADER <> ;映像文件头,包含了PE文件的一些基本信息 +18h OptionalHeader IMAGE_OPTIONAL_HEADER32 <> ;可选映像头,补充PE文件属性 IMAGE_NT_HEADERS ENDS
对于32位PE文件,此结构通常为248字节
对于Signature的定义为
IMAGE_NT_SIGNATURE equ 00004550h
其ASCII值为:"PE00"(50450000h)
后面的两个结构最重要的就是偏移78h的数据目录表
IMAGE_FILE_HEADER结构
IMAGE_FILE_HEADER STRUCT +04h Machine WORD ? ;可执行文件的目标CPU类型,14Ch为Intel i386 +06h NumberOfSections WORD ? ;区块的数目 +08h TimeDateStamp DWORD ? ;文件创建日期和时间 +0ch PointerToSymbolTable DWORD ? ;COFF符号表的文件偏移位置 +10h NumberOfSymbols DWORD ? ;符号个数 +14h SizeOfOptionalHeader WORD ? ;IMAGE_OPTIONAL_HEADER32结构的大小,32位PE文件通常是E0h,64位PE32+是F0h +16h Characteristics WORD ? ;文件属性 IMAGE_FILE_HEADER ENDS
IMAGE_OPTIONAL_HEADER32结构
IMAGE_OPTIONAL_HEADER32 STRUCT +18h Magic WORD ? ;标志字,ROM映像(0107h)普通可执行映像(010Bh)PE32+(020Bh) +1Ah MajorLinkerVersion BYTE ? ;链接程序的主版本号 +1Bh MinorLinkerVersion BYTE ? ;链接程序的次版本号 +1Ch SizeOfCode DWORD ? ;所有带有IMAGE_SCN_CNT_CODE属性区块的总大小 +20h SizeOfInitializedData DWORD ? ;已初始化数据块的大小 +24h SizeOfUninitializedData DWORD ? ;未初始化数据块的大小(通常在.bss块中) +28h AddressOfEntryPoint DWORD ? ;程序执行入口RVA(这个地址并不直接指向Main、WinMain、DllMain,而是指向运行库代码并由它来调用上述函数) +2Ch BaseOfCode DWORD ? ;代码段的起始RVA(Microsoft链接器生成的执行文件通常是1000h)<内存中代码段通常在PE文件头之后、数据块之前> +30h BaseOfData DWORD ? ;数据段的起始RVA。这个域的值对于不同版本的微软链接器是不一致的,在64位可执行文件中是不出现的 +34h ImageBase DWORD ? ;文件在内存中的首选装入地址 +38h SectionAlignment DWORD ? ;内存中的区块对齐值 +3Ch FileAlignment DWORD ? ;磁盘上区块的对齐值 +40h MajorOperatingSystemVersion WORD ? ;操作系统最低主版本号 +42h MinorOperatingSystemVersion WORD ? ;操作系统最低次版本号 +44h MajorImageVersion WORD ? ;用户自定义主版本号 +46h MinorImageVersion WORD ? ;用户自定义次版本号 +48h MajorSubsystemVersion WORD ? ;要求最低子系统版本主版本号 +4Ah MinorSubsystemVersion WORD ? ;要求最低子系统版本次版本号 +4Ch Win32VersionValue DWORD ? ;不用字段,常设为0 +50h SizeOfImage DWORD ? ;映像装入内存后的总尺寸 +54h SizeOfHeaders DWORD ? ;MS-DOS头部、PE头部、区块表的组合尺寸。域值四舍五入至文件对齐的倍数 +58h CheckSum DWORD ? ;映像校验和,链接器的/RELEASE开关被使用时,校验和被置于文件中 +5Ch Subsystem WORD ? ;标明可执行文件所期望的子系统(用户界面类型)的枚举值 +5Eh DllCharacteristics WORD ? ;DLL特性旗标,DllMain()函数何时被调用,默认为0 +60h SizeOfStackReserve DWORD ? ;为线程保留的堆栈大小,它一开始只提交其中一部分,只有在必要时,才提交剩下的部分 +64h SizeOfStackCommit DWORD ? ;一开始即被委派给堆栈的内存数量。默认值是4KB +68h SizeOfHeapReserve DWORD ? ;为进程的默认堆保留的内存。默认值是1MB,在当前Windows里,堆值在用户不干涉的情况下就能增长超过这个值 +6Ch SizeOfHeapCommit DWORD ? ;EXE文件里,委派给堆的内存大小。默认值是4KB +70h LoaderFlags DWORD ? ;与调试有关,默认为0 +74h NumberOfRvaAndSizes DWORD ? ;数据目录的项数,从最早的Windows NT发布以来一直是16 +78h DataDirectory IMAGE_DATA_DIRECTORY IMAGE_NUMBEROF_DIRECTORY_ENTRIES dup(<>) IMAGE_OPTIONAL_HEADER32 ENDS
这两个结构中有很多常用到的信息,如IMAGE_FILE_HEADER结构中的区块数目, IMAGE_OPTIONAL_HEADER中的ImageBase、SectionAlignment和FileAlignment等字段
区块表
对于数据目录表,最后再来总结
区块表是一个IMAGE_SECTION_HEADER结构数组,区块的个数由IMAGE_NT_HEADERS.FileHeader.NumberOfSections指出
区块表的后面与区块之间一般是大量的填充位
IMAGE_SECTION_HEADER结构:40字节
IMAGE_SECTION_HEADER STRUCT Name1 db IMAGE_SIZEOF_SHORT_NAME dup(?) ;区块名 union Misc PhysicalAddress dd ? VirtualSize dd ? ;区块实际大小 ends VirtualAddress dd ? ;区块的RVA地址 SizeOfRawData dd ? ;在文件中对齐后的尺寸 PointerToRawData dd ? ;在文件中的偏移 PointerToRelocations dd ? ;在OBJ文件中使用,重定位的偏移,指向一个IMAGE_RELOCATION结构数组 PointerToLinenumbers dd ? ;行号表在文件中的偏移值,这是文件调试信息 NumberOfRelocations dw ? ;重定位项数目,在EXE文件中无意义 NumberOfLinenumbers dw ? ;该块在行号表中的行号数目 Characteristics dd ? ;块属性 IMAGE_SECTION_HEADER ENDS
此结构常用VirtualAddress和PointerToRawData字段来定位区块,并实现文件偏移与虚拟地址的转换
数据目录表
数据目录表位于IMAGE_NT_HEADER的78h偏移处,是一个IMAGE_DATA_DIRECTORY结构数组,从最早的Windows NT发布以来项数一直是16
IMAGE_DATA_DIRECTORY结构为:
IMAGE_DATA_DIRECTORY STRUCT VirtualAddress DWORD ? ;数据块的起始RVA isize DWORD ? ;数据块的大小 IMAGE_DATA_DIRECTORY ENDS
一、数组的第一项指向输出表
输出表是一个IMAGE_EXPORT_DIRECTORY结构
IMAGE_EXPORT_DIRECTORY STRUCT Characteristics DWORD ? ;输出属性,目前未定义,总是为0 TimeDateStamp DWORD ? ;输出表创建时间(GMT时间) MajorVersion WORD ? ;输出表主版本号,未使用 MinorVersion WORD ? ;次版本号,未使用 nName DWORD ? ;指向一个ASCII字符串的RVA,这个字符串是与这些输出函数关联的DLL名字 nBase DWORD ? ;基数,加上序数就是函数地址数组的索引值 NumberOfFunctions DWORD ? ;EAT中条目数(输出函数地址表) NumberOfNames DWORD ? ;ENT中的条目数(输出函数名称表) AddressOfFunctions DWORD ? ;EAT的RVA AddressOfNames DWORD ? ;ENT的RVA AddressOfNameOrdinals DWORD ? ;输出序数表的RVA IMAGE_EXPORT_DIRECTORY ENDS
输出表结构
AddressOfNameOrdinals指向的输出序数表的作用是把EAT和ENT联系起来,当PE装载器在ENT中找到对应函数的位置,再从输出序数表读取相应位置的值,这个值便是在EAT中的偏移,这个值作为进入EAT的索引(这里有一点不明白,看雪加密解密第三版书上说此值作为输出函数的输出序数并且要考虑Base阈值,但是实例中输出序数表中的值是函数在EAT中的偏移)。当通过序数来查询一个输出函数是,减去nBase阈值得到进入EAT的索引
二、数据目录表第二项输入表
输入表以一个IMAGE_IMPORT_DESCRIPTOR(IID)结构数组开始,数组以一个全为0的IID结构结束
IMAGE_IMPORT_DESCRIPTOR STRUCT union Characteristics dd ? OriginalFirstThunk dd ? ;指向输入名称表(INT)的RVA ends TimeDateStamp dd ? ;时间标志 ForwarderChain dd ? ;第一个被转向的API的索引,一般为0 Name1 dd ? ;指向DLL的名字 FirstThunk dd ? ;指向输入地址表(IAT)的RVA IMAGE_IMPORT_DESCRIPTOR ENDS
INT和IAT都是一个IMAGE_THUNK_DATA结构数组,同样以一个全为0的IMAGE_THUNK_DATA结构作为结束
IMAGE_THUNK_DATA32 STRUCT union u1 ForwarderString dd ? ;指向一个转向者字符串的RVA Function dd ? ;被输入的函数的内存地址 Ordinal dd ? ;被输入的API的序数值 AddressOfData dd ? ;指向IMAGE_IMPORT_BY_NAME ends IMAGE_THUNK_DATA32 ENDS
INT中当IMAGE_THUNK_DATA值得最高位为1是,表示函数以序号方式输入;最高位为0是以函数名的方式输入,此时指向一个IMAGE_IMPORT_BY_NAME结构
IMAGE_IMPORT_BY_NAME结构为:
IMAGE_IMPORT_BY_NAME STRUCT Hint dw ? ;指出此函数在所驻留的DLL的输出表中的序号,有些连接器将此值设为0 Name1 db ? ;输入函数的函数名,这是一个可变尺寸域 IMAGE_IMPORT_BY_NAME ENDS
PE加载器先同过INT找到相应的函数,再将对应函数的地址重写入IAT,此时程序仅依靠IAT提供的函数地址就可以正常运行了
因为一开始IAT跟INT是一样的,有些链接器就只用一个IAT结构就完成了输入(不知道在哪儿看到过这句话,大概意思就这个)
三、数据目录表第三项资源表
数据目录表中的IMAGE_DIRECTORY_ENTRY_RESOURCE条目包含资源的RVA和大小,资源目录每一节点包含一个IMAGE_RESOURCE_DIRECTORY结构和数个IMAGE_RESOURCE_DIRECTORY_ENTRY结构,IMAGE_RESOURCE_DIRECTORY指出紧跟在后面的IMAGE_RESOURCE_DIRECTORY_ENTRY结构的个数
从网上找了张图,这张图对资源目录结构的描述很清晰
IMAGE_RESOURCE_DIRECTORY STRUCT Characteristics dd ? ;理论上是资源的属性标志,但通常为0 TimeDateStamp dd ? ;资源建立的时间 MajorVersion dw ? ;理论上放置资源的版本,但通常为0 MinorVersion dw ? ; NumberOfNamedEntries dw ? ;使用名字的资源条目的个数 NumberOfIdEntries dw ? ;使用ID数字资源条目的个数 IMAGE_RESOURCE_DIRECTORY ENDS
NumberOfNamedEntries和NumberOfIdEntries加起来的和等于本目录IMAGE_RESOURCE_DIRECTORY_ENTRY的个数
IMAGE_RESOURCE_DIRECTORY_ENTRY STRUCT union rName RECORD NameIsString:1,NameOffset:31 Name1 dd ? Id dw ? ends union OffsetToData dd ? rDirectory RECORD DataIsDirectory:1,OffsetToDirectory:31 ends IMAGE_RESOURCE_DIRECTORY_ENTRY ENDS
就先总结到这儿吧2015-07-02/17:11:08