PE文件结构解析

RVA是相对虚拟地址(Relative Virtual Address)的缩写。RVA是当PE
文件被装载到内存中后,某个数据位置相对于文件头的偏移量。

PE头

说明:本文件中各种文件头格式截图基本都来自看雪的《加密与解密》;本文相当《加密与解密》的阅读笔记。

例如:导入表的位置和大小可以从PE文件头中IMAGE_OPTIONAL_HEADER32结构的数据目录字段中获取,对应的项目是DataDirectory字段的第2个IMAGE_DATA_DIRECTORY结构。从IMAGE_DATA_DIRECTORY结构的VirtualAddress字段得到的是导入表的RVA值,如果在内存中查找导入表,那么将RVA值加上PE文件装入的基址就是实际的地址;如果在PE文件中查找导入表,需要将RVA转换成File
Offset(也就是数据在文件中的位置)。

typedef struct _IMAGE_NT_HEADERS {
  DWORD                 Signature;  PE头标识 为固定的ascii码 PE\0\0
  IMAGE_FILE_HEADER     FileHeader;  标准PE头
  IMAGE_OPTIONAL_HEADER OptionalHeader;  扩展PE头
} IMAGE_NT_HEADERS, *PIMAGE_NT_HEADERS;

 

RVA转换到文件偏移地址的方法如下:

标准PE头结构

1.PE文件总体结构

PE文件框架结构,就是exe文件的排版结构。也就是说我们以十六进制打开一个.exe文件,开头的那些内容就是DOS头内容,下来是PE头内容,依次类推。

如果能认识到这样的内含,那么“exe开头的内容是不是就直接是我们编写的代码”(不是,开头是DOS头内容)以及“我们编写的代码被编排到了exe文件的哪里”(在.text段,.text具体地址由其相应的IMAGE_SECTION_HRADER指出)此类的问题答案就显而易见了。

图片 1

exe文件从磁盘加载到内存,各部份的先后顺序是保持不变的,但由于磁盘(一般200H)和内存(一般1000H)区块的对齐大小不一样,所以同一内容在磁盘和在内存中的地址是不一样的。

换言之你在磁盘上看到一段内容一内容要到在内存中找到它–假设它是能映射到内容的部份–那么要做相应的地址转换。(比如你在Ultraedit中看到某几个字节而想在OllyDbg中找到这几个字节那么需要进行地址转换)

另外要注意,PE文件中存放的地址值都是内存中的地址,这些地址在OllyDbg中不需要转换到其指定的位置就能找到其指向的内容;这要根据这个地址找到内容在Ultraedit的地址,需要将此RVA址转换成文件偏移地址。

还要注意DOS头/PE头/块表,映射到内存时属同一区块而且是第一区块,所以此三者上的RVA和文件偏移地址是相等的。

图片 2

 

步骤一:循环扫描区块表得出每个区块在内存中的起始
RVA(根据IMAGE_SECTION_HEADER 中的VirtualAddress
字段),并根据区块的大小(根据IMAGE_SECTION_HEADER 中的SizeOfRawData
字段)算出区块的结束 RVA(两者相加即可),最后判断目标 RVA
是否落在该区块内。
步骤二:通过步骤一定位了目标 RVA 处于具体的某个区块中后,那么用目标 RVA
减去该区块的起始 RVA ,这样就能得到目标 RVA 相对于起始地址的偏移量
RVA2.
步骤三:在区块表中获取该区块在文件中所处的偏移地址(根据IMAGE_SECTION_HEADER
中的PointerToRawData 字段), 将这个偏移值加上步骤二得到的 RVA2
值,就得到了真正的文件偏移地址。

typedef struct _IMAGE_FILE_HEADER {
  WORD  Machine;  PE文件运行的平台类型
  WORD  NumberOfSections;  文件中"节"的数量
  DWORD TimeDateStamp;
  DWORD PointerToSymbolTable;
  DWORD NumberOfSymbols;
  WORD  SizeOfOptionalHeader;  扩展PE头的长度
  WORD  Characteristics;   文件属性 如:DLL文件, EXE文件等
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;

 2.DOS头部

既,已知某虚拟地址(如va)和某区块的虚拟地址(text_va),虚拟地址在区块中,同时还知道此区块在文件中的位置(text_file_offset),解出此虚拟地址在文件中的具体位置。解:根据他们的偏移量相同(都是text_va

Characteristics属性位的含义

2.1MS-DOS头部(IMAGE_DOS_HEADER)

图片 3

最后的e_lfanew即是PE文件的RVA地址

图片 4

我们在前边已经提过,对于DOS头/PE头/区块表三部分RVA和文件偏移地址是相等的,所以上边在十六进制文本编缉器中,直接转向e_lfanew指向的000000B0可以正好找到PE头。

2.2DOS stub

DOS
stub是当操作系统不支持PE文件时执行的部分,一般由编译器自己生成内容是输出“This
program cannot be run in MS-DOS mode”等提示。

PE文件头的位置由e_lfanew指出而不是在固定位置,所以DOS
stub允许你改成自己想要执行的代码,想写多长写多长;但一般直接不理会。

 

  • va)可知,答案为 text_file_offset + (text_va – va)。

扩展PE头结构

3.PE头部

图片 5

typedef struct _IMAGE_OPTIONAL_HEADER {
  WORD                 Magic;  魔术字,说明文件的类型 10bH表示32位的PE文件  20bH表示64位的PE文件  107H表示ROM映像
  BYTE                 MajorLinkerVersion;
  BYTE                 MinorLinkerVersion;
  DWORD                SizeOfCode;
  DWORD                SizeOfInitializedData;
  DWORD                SizeOfUninitializedData;
  DWORD                AddressOfEntryPoint;
  DWORD                BaseOfCode;
  DWORD                BaseOfData;
  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_HEADER, *PIMAGE_OPTIONAL_HEADER;

3.1 PE Signature

四个字节,内容“PE\0\0”,对应十六进制“50 45 00 00”

 

 

3.2 IMAGE_FILE_HEADER

图片 6

SizeOfOptionalHeader指了OptionalHeader的大小,NumberOfSections指出了文件的区块数;没有指向OptionalHeader和区块表的指针,这暗示区块表紧接在OpthionalHeader后,OpthonalHeader紧接在FileHeader扣,紧接的意思是没有空格的。

 

 

3.3 IMAGE_OPTIONAL_HEADER

图片 7

图片 8

其中ImageBase指出程序装载的基地址

 

4.区块表

4.1 IMAGE_NT_HEADER

图片 9

图片 10

其中VirtualAddress指出了区块进入内存后的RVA地址(OD找区块用这个地址)PointerToRawDATA指出区块在磁盘文件中的地址(十六进制编缉器找区块用这个地址)

 

4.2 文件偏移地址和相对偏移地址的换算

图片 11

图片 12

  1. 各区块自身不论多大,其自身差值都不会爱影响

2.
但如果其大小大于200h那么会影响下一区块的差值:比如当.text大小大于200h那么.rdata的文件偏移量将会后移;如果.text大小大于1000h那么.rdata的RVA也会后移

3.也就说表10-7中的差值只是说一般是这样子,但当程序很大时各区块的差值还是得重新计算;当然无论怎么样总是有:差值=区块RVA-区块文件偏移地址

 

5.数据目录表

数据目录表是IMAGE_OPTIONAL_HEADER结构的最后一个成员,类型为IMAGE_DATA_DIRECTORY
* 16

图片 13

数据目录表各成员位置由VirtualAddress指出,并且不是像PE头接在DOS头后不远处一样,一般都在很远的地址;所以一般都是跳过先讲完区块然后再回头讲,也因此初学者可能会感到有些混乱。

 

5.1输出表

数据目录表的第一个成员指向输出表的RVA,指向的地址是IMAGE_EXPORT_DIRECTORY结构。

图片 14

其中AddressofFunctions指向输出函数的地址数组,AddressOfNames指向输出函数的名称数组,AddressOfNameOrdinale指向输出函数的输出序数数组。

AddressOfNames和AddressOfNameOrdinale的顺序是相同的,也就是说AddressOfNameOrdinale第一个元素的值就是就是AddressOfNames第一个函数的输出序数,依次类推。

使用输出序数做为数组下标到AddressofFunctions指向的地址数组即可找到函数对应的地址。PE重写IAT时使用GetProcAddress通过函数名获取函数地址基本也就是这个流程。

 

5.2输入表

数据目录表的第二个成员指向输入表的RVA;指向的地址是IMAGE_IMPORT_DESCRIPTOR(IID)结构,一个IID对应一个DLL,最后以一个全0的IID表示结束

图片 15

图片 16

 OriginalFristThunk和FirstThunk都指向IMAGE_THUNK_DATA结构;IMAGE_THUNK_DATA都指向同一个IMAGE_IMPORT_BY_NAME

图片 17

图片 18

图片 19

 最重要的还是要理解为什么需要INT和IAT两个东西指向同一个东西;其流程是这样:

1.INT是不可写的,IAT是PE加载器可重写的

2.在编译的时候,编译器不懂IAT要填什么,就随便填成了和INT一样的内容(所以用十六进制编缉器查看时IAT和INT的内容是一样的)

3.PE装载器加载时根据INT找到IMAGE_IMPORT_BY_NAME中的函数名,然后使用GetProcAddress(HMODULE
hModule,LPCSTR
lpProcName)找到函数对应的地址(hModule是DLL的句柄,lpProcName是IMAGE_IMPORT_BY_NAME中的函数名)

4.PE装载器使用查找到地址重写IAT(所以用OllyDbg查看时IAT和INT的内容是不一样的)

5.所以可以直接这样理解:IAT初始是什吐槽内容并不要紧、INT就是为了重写IAT而存在的

图片 20

 

5.3资源

windows系统中的各种可视元素叫做资源,包括快捷键(Accelerator)、位图(Bitmap)、光标(Cursor)、对话框(Dialog
Box)、图标(Icon)、菜单(Menu)、字符串表(String
Table)、工具栏(Toolbar)、版本信息(Version Information)等。

数据目录表的第三个成员指向资源结构的RVA,资源结构一般是相同的三层IMAGE_RESOURCE_DIRECTORY+n
*
IMAGE_RESOURCE_DIRECTORY_ENTRY加上指向最终资源代码的IMAGE_RESOURCE_DATA_ENTRY构成。

图片 21

其中NumberOfNameEntries的值加上NumberOfIdEntries的值等于紧接在IMAGE_RESOURCE_DIRECTORY后边的IMAGE_RESOURCE_DIRECTORY_ENTRY的个数。

图片 22

根据IMAGE_RESOURCE_DIRECTORY_ENTRY所在层级的不同,Name和OffsetToData的含义不相同。

Name:

不管在哪层,当最高位为1时低位值做指针使用;当最高位为0时低位值做编号使用。

更具体的,一般在第一层时高位为0低位用做编号表示资源类型比如是对话框还是菜单;第二高位为1低位为指向IMAGE_RESOURCE_DIR_STRING_U的指针该结构保存资源的名称;第三层时高位为0低位用做编号表示该资源中的语言比如是英语还是汉语。

OffsetToData:

不管在哪层,当最高位为1时指向下一层目录块的起始地址;当最高位为0时指向IMAGE_RESOURCE_DATA_ENTRY。

更具体的,一般在第一和第二层时最高位为1,指向下一层目录块的起始地址;在第三层时最高位为0,指向IMAGE_RESOURCE_DATA_ENTRY。

相关文章