PE文件基础
Posted 可可西
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了PE文件基础相关的知识,希望对你有一定的参考价值。
① PE (Portable Executable):微软参考COFF(Common Object File Format)规范,在Windows NT系统上制定的一种标准,
用于exe可执行文件、obj目标文件和dll动态链接库等文件格式。PE32+是PE的64位扩展,其并未添加额外结构,只是把原来32位的字段变成了64位。
与COFF一样,PE也是基于段(Segment,注:有时也被叫节Section)的结构,
按照不同属性将信息分段存放,常见的段有:代码段(.text)、数据段(.data)、只读数据段(.rdata)、资源表(.rsrc)、重定位表(.reloc)等。
因为PE文件在装载时被直接映射到进程的虚拟空间中运行,它是进程的虚拟空间的映像。所以PE文件很多时侯被叫做映像文件(Image File)。
② RVA (Relative Virtual Address):相对于PE文件装载基地址(Base Address)的一个地址偏移。
③ windows系统使用的x86的处理器,为小端序;WORD的二进制1A15实际的数值为:5402(数学写法为:151A);
DWORD的二进制B2356A18实际的数值为:409613746(数学写法为:186A35B2)
PE文件结构:
注1:IMAGE_DOS_HEADER、IMAGE_NT_HEADER、IMAGE_SECTION_HEADER等都定义在winNT.h中
注2:IMAGE_DOS_HEADER和DOS Stub是兼容DOS应用而存在的结构,PE文件的内容从IMAGE_NT_HEADER开始
typedef struct _IMAGE_DOS_HEADER { // DOS .EXE header WORD e_magic; // Magic number WORD e_cblp; // Bytes on last page of file WORD e_cp; // Pages in file WORD e_crlc; // Relocations WORD e_cparhdr; // Size of header in paragraphs WORD e_minalloc; // Minimum extra paragraphs needed WORD e_maxalloc; // Maximum extra paragraphs needed WORD e_ss; // Initial (relative) SS value WORD e_sp; // Initial SP value WORD e_csum; // Checksum WORD e_ip; // Initial IP value WORD e_cs; // Initial (relative) CS value WORD e_lfarlc; // File address of relocation table WORD e_ovno; // Overlay number WORD e_res[4]; // Reserved words WORD e_oemid; // OEM identifier (for e_oeminfo) WORD e_oeminfo; // OEM information; e_oemid specific WORD e_res2[10]; // Reserved words LONG e_lfanew; // File address of new exe header } IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER; /////////////////////////////////////////////////////////////////////////// typedef struct _IMAGE_FILE_HEADER { WORD Machine; WORD NumberOfSections; DWORD TimeDateStamp; DWORD PointerToSymbolTable; DWORD NumberOfSymbols; WORD SizeOfOptionalHeader; WORD Characteristics; } IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER; 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_HEADER64 { WORD Magic; BYTE MajorLinkerVersion; BYTE MinorLinkerVersion; DWORD SizeOfCode; DWORD SizeOfInitializedData; DWORD SizeOfUninitializedData; DWORD AddressOfEntryPoint; DWORD BaseOfCode; ULONGLONG 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; ULONGLONG SizeOfStackReserve; ULONGLONG SizeOfStackCommit; ULONGLONG SizeOfHeapReserve; ULONGLONG SizeOfHeapCommit; DWORD LoaderFlags; DWORD NumberOfRvaAndSizes; IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]; } IMAGE_OPTIONAL_HEADER64, *PIMAGE_OPTIONAL_HEADER64; 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; typedef struct _IMAGE_NT_HEADERS64 { DWORD Signature; IMAGE_FILE_HEADER FileHeader; IMAGE_OPTIONAL_HEADER64 OptionalHeader; } IMAGE_NT_HEADERS64, *PIMAGE_NT_HEADERS64; typedef struct _IMAGE_NT_HEADERS { DWORD Signature; IMAGE_FILE_HEADER FileHeader; IMAGE_OPTIONAL_HEADER32 OptionalHeader; } IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32; typedef struct _IMAGE_ROM_HEADERS { IMAGE_FILE_HEADER FileHeader; IMAGE_ROM_OPTIONAL_HEADER OptionalHeader; } IMAGE_ROM_HEADERS, *PIMAGE_ROM_HEADERS; #ifdef _WIN64 typedef IMAGE_NT_HEADERS64 IMAGE_NT_HEADERS; typedef PIMAGE_NT_HEADERS64 PIMAGE_NT_HEADERS; #else typedef IMAGE_NT_HEADERS32 IMAGE_NT_HEADERS; typedef PIMAGE_NT_HEADERS32 PIMAGE_NT_HEADERS; #endif /////////////////////////////////////////////////////////////////////////// #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;
程序编译后,变量与函数存储在PE文件中的位置如下:
注:BSS段存放的是未初始化的全局变量与静态变量(含全局和局部);以上代码,vc编译器会去除BSS段,将这部分内容直接放到数据段中
主要的原因是段的最小单元为4KB,所以为了节省PE文件、内存占用及加快PE文件的映射过程,vc编译器会尽量将属性一致的段进行合并
数据目录
① 一般来说,导出表、导入表、导入地址表存放在.rdata段(只读数据段)、重定位表存放在独立的.reloc段(重定位表段)
② 在PE文件中,将段表中各段的VirtualAddress(段的RVA)及VirtualSize(段的实际大小)与所求RVA比较,就可知该RVA落在哪个段中
假设落在段A中,则所求RVA在PE文件中offset为:段A的PointerToRawData + (所求RVA - 段A的VirtualAddress)
(1)导出表
导出符号:当前模块(DLL)导出供其他模块使用的函数和变量。
注1:Base一般为1,AddressOfNames、AddressOfNameOrdinals指向的表的size一致,且里面元素一一对应
注2:AddressOfFunctions指向的表的size >= AddressOfNames指向的表的size,主要有以下两点原因导致:
① 当在def文件中指定的符号Ordinal不连续时,会导致一些空地址(即:0地址)元素
如:导出2个Orinal为1,3的符号,那么AddressOfFunctions指向的表的size为3(第2个元素为空地址),AddressOfNames指向的表的size为2
def文件符号导出如下:
DllAdd @ 1
g_nDll @ 3
② 在def中只导出Ordinal而不导出符号名称 如:DllAdd @ 1 NONAME
注3:符号在导出地址表(EAT)中的索引号为:名字序号对应表中的Ordinal - Base
导出方法:
① 在代码中使用__declspec(dllexport)关键字
② def文件
③ 链接器/export参数
(2)导入表:
导入符号:在当前模块使用,但在其他模块(DLL)中定义实现的函数和变量。
导入方法:
① 在dll的头文件中,对导出的函数和变量使用__declspec(dllimport)
② 链接dll的导入库
每个导入模块(dll)使用一个IMAGE_IMPORT_DESCRIPTOR结构体存放,并以全0内容的IMAGE_IMPORT_DESCRIPTOR作为结尾。
IAT、INT中的元素使用IMAGE_THUNK_DATA结构体存放, 并以全0内容的IMAGE_THUNK_DATA作为结尾。
IAT中IMAGE_THUNK_DATA最高位为1,则低31位为导入符号的序号值;否则IMAGE_THUNK_DATA为指向IMAGE_IMPORT_BY_NAME结构的RVA。
动态连接器使用IMAGE_IMPORT_BY_NAME的Hint值去定位符号在目标导出表中的位置,若刚好找到则命中,如果没命中就按照二分查找方法进行符号查找。
INT未绑定时,内容与IAT一样;绑定时,IMAGE_THUNK_DATA中存放的是导入符号运行时的虚拟内存地址,如果外部模块的时间戳与TimeDataStamp一致,
且在载入外部模块未发生重定基址,则直接使用该地址来访问该符号;否则,使用IAT进行符号解析与查找。
注1:在动态链接器刚完成映射还没有开始重定位和符号解析时,IAT中元素值表示相对应的导入符号的序号或者是符号名;当完成该模块链接时,元素值会被动态链接器改成该符号的真正地址。
注2:程序每次运行时,所有被依赖的dll都会被装载,然后一系列的导入导出符号依赖关系都会被重新解析。然而在大多数情况下,这些dll都会以同样的顺序被装载到同样的内存地址,所以它们的导出符号的地址都是不变的。
若将这些导出函数的地址保存到模块的导入表中,就可以省去每次启动时符号解析过程,这种DLL性能优化方式被叫做DLL绑定(DLLBinding)。
(3)导入地址表
注:导入地址表(IAT)存放着所有导入模块的地址信息(外部地址),各个模块之间用一个4字节的0隔开。模块的先后顺序不保证与导入表模块的顺序一致。
(4)重定位表
按照基址生成出的绝对地址,如果目标地址被占用或基于安全考虑,则这些地址就需要重定位。
类型(高4位) | 含义 |
0 | 无意义,仅用来做对齐用 |
1 | 高16位需要修正 |
2 | 低16位需要修正 |
3 | 32位都需要修正 |
PE文件工具
https://down.52pojie.cn/Tools/PEtools/
(1)PE-Explorer
(2)LordPE
(3)ExeinfoPe
以上是关于PE文件基础的主要内容,如果未能解决你的问题,请参考以下文章