PE文件结构

PE文件结构

MS-DOS头部

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
 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
+0ah e_minalloc WORD
+0ch e_maxalloc WORD
+0eh e_ss WORD
+10h e_sp WORD
+12h e_csum WORD
+14h e_ip WORD
+16h e_cs WORD
+18h e_lfarlc WORD
+1ah e_ovno WORD
+1ch e_res WORD
+24h e_oemid WORD
+26h e_oeminfo WORD
+28h e_mres2 WORD
+3ch e_lfanew DWORD //指向PE文件头,是RVA
}IMAGE_DOS_HEADER_ENDS

PE文件头

1
2
3
4
5
IMAGE_NT_HEADERS STRUCT{
+0h Signature DWORD --> #define IMAGE_NT_SIGNATURE
+4h FileHeader IMAGE_FILE_HEADER
+18h OptionalHeader IMAGE_OPTIONAL_HEADER32
}IMAGE_NT_HEADERS64 ENDS

64位PE文件头

1
2
3
4
5
IMAGE_NT_HEADERS STRUCT{
+0h Signature DWORD --> #define IMAGE_NT_SIGNATURE
+4h FileHeader IMAGE_FILE_HEADER
+18h OptionalHeader IMAGE_OPTIONAL_HEADER64
}IMAGE_NT_HEADERS64 ENDS

IMAGE_FILE_HEADER

1
2
3
4
5
6
7
8
9
10
IMAGE_FILE_HEADER STRUCT{
+04H Machine WORD //运行平台
+06H NumberOfSections WORD //文件的区块数
+08H TimeDateStamp DWORD //文件创建日期和时间
+0CH PointerToSymbolTable DWORD //指向符号表
+10H NumberOfSymbols DwORD //符号表符号的个数
+14H SizeOfOptionalHeader WORD IMAGE_OPTIONAL_HEADER32//结构的大小
+16H Characteristics WORD //文件属性

}

IMAGE_OPTIONAL_HEADER

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
IMAGE_OPTIONAL_HEADER32 STRUCT
+18H Magic WORD;标志字
+1AH MajorLinkerVersion BYTE;链接器主版本号
+1BH MinorLinkerVersion BYTE;链接器次版本号
+1CH SizeOfCode DWORD;所有含有代码的区块的大小
+20H SizeOfInitializedData DWORD;所有初始化数据区块的大小
+24H SizeOfUninitializedData DWORD;所有未初始化数据区块的大小
+28H AddressOfEntryPoint DWORD;程序指向入口RVA
+2CH BaseOfCode DWORD;代码区块起始RVA
+30H BaseOfData DWORD;数据区块起始RVA
+34H ImageBase DWORD;程序默认载入基地址
+38H SectionAlignment DWORD;内存中区块的对齐值
+3cH FileAlign DWORD;文件中区块的对齐值
+40H MajorOperationSystemVersion WORD;操作系统主版本号
+42H MinoroperationSystemVersion 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 WORD;映像校验和
+5cH Subsystem WORD;文件子系统
+5eH DLLCharacteristics WORD;显示DLL特性的旗帜
+60H SizeOfStackReserve DWORD;初始化时栈的大小
+64H SizeOfStackCommit DWORD;初始化时实际提交栈的大小
+68H SizeOfHeapReserve DWORD;初始化时堆的大小
+6cH SizeOfHeapCommit DWORD;初始化时实际提交堆的大小
+70H LoaderFlags DWORD;与调试相关,默认值为0
+74H NumberOfRvaAndSize DWORD;数据目录表的项数
+78H DataDirectory DWORD;IMAGE_DATA_DIRECTROY

64位下IMAGE_OPTIONAL_HEADER

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
IMAGE_OPTIONAL_HEADER32 STRUCT
+18H Magic WORD;标志字
+1AH MajorLinkerVersion BYTE;链接器主版本号
+1BH MinorLinkerVersion BYTE;链接器次版本号
+1CH SizeOfCode DWORD;所有含有代码的区块的大小
+20H SizeOfInitializedData DWORD;所有初始化数据区块的大小
+24H SizeOfUninitializedData DWORD;所有未初始化数据区块的大小
+28H AddressOfEntryPoint DWORD;程序指向入口RVA
+2CH BaseOfCode DWORD;代码区块起始RVA
+30H ImageBase ULONGLONGU7;程序默认载入基地址
+38H SectionAlignment DWORD;内存中区块的对齐值
+3cH FileAlign DWORD;文件中区块的对齐值
+40H MajorOperationSystemVersion WORD;操作系统主版本号
+42H MinoroperationSystemVersion 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 WORD;映像校验和
+5cH Subsystem WORD;文件子系统
+5eH DLLCharacteristics WORD;显示DLL特性的旗帜
+60H SizeOfStackReserve ULONGLONG;初始化时栈的大小
+68H SizeOfStackCommit ULONGLONG;初始化时实际提交栈的大小
+70H SizeOfHeapReserve ULONGLONG;初始化时堆的大小
+78H SizeOfHeapCommit ULONGLONG;初始化时实际提交堆的大小
+80H LoaderFlags DWORD;与调试相关,默认值为0
+84H NumberOfRvaAndSize DWORD;数据目录表的项数
+88H DataDirectory DWORD;IMAGE_DATA_DIRECTROY

IMAGE_DATA_DIRECTORY

1
2
3
IMAGE_DATA_DIRECTORY STRUCT
VirtualAddress DWORD;数据块的起始RVA
Size DWORD;数据块的长度

数据目录的成员

序号 成员 结构 偏移量(PE/PE32+)
0 Export Table IMAGE_DIRECTORY_ENTRY_EXPORT 78H/88H
1 Import Table IMAGE_DIRECTORY_ENTRY_IMPORT 80H/90H
2 Resources Table IMAGE_DIRECTORY_ENTRY_RESOURCE 88H/98H
3 Exception Table IMAGE_DIRECTORY_ENTRY_EXCEPTION 90H/A0H
4 Security Table IMAGE_DIRECTORY_ENTRY_SECURITY 98H/A8H
5 Base relocation Table IMAGE_DIRECTORY_ENTRY_BASERELOC A0H/B0H
6 Debug IMAGE_DIRECTORY_ENTRY_DEBUG A8H/B8H
7 Copyright IMAGE_DIRECTORY_ENTRY_COPYRIGHT B0H/C0H
8 Global Ptr IMAGE_DIRECTORY_ENTRY_GLOBALPTR D8H/C8H
9 Thread local storage(TLS) IMAGE_DIRECTORY_ENTRY_TLS C0H/D0H
10 Load configuration IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG C8H/D8H
11 Bound Import IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT D0H/E0H
12 Import Address Table(IAT) IMAGE_DIRECTORY_ENTRY_IAT D8H/E8H
13 Delay Import IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT E0H/F0H
14 COM descriptor IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR E8H/F8H
15 保留,必须为0 F0H/100H

IMAGE_SECTION_HEADER

1
2
3
4
5
6
7
8
9
10
11
12
13
14
IMAGE_SECTION_HEADER 
NAME BYTE8;8字节得块名
union Misc ;区块尺寸
PhysicalAddress DWORD
VirtualSize DWORD;实际被使用的区块的大小
VirtualAddress DWORD;区块的RVA地址
SizeOfRawData DWORD;在文件中对齐后的尺寸
PointerToRawData DWORD;在文件中的偏移
PointerToRelocations DWORD;在OBJ文件中使用,重定位的偏移
PointerToLinenumbers DWORD;行号表的偏移(供调试用)
NumberOfRelocations WORD;在OBJ文件中使用,重定位项数目
NumberOfLinenumbers WORD;行号表中行号的数目
Characteristics DWORD;区块的属性

字段值 地址 用途
IMAGE_SCN_SNT_CODE 00000020H 包含代码,常与100000000h一起设置
IMAGE_SCN_CNT_INITIALIZED_DATA 00000040H 该块包含已初始化的数据
IMAGE_SCN_CNT_UNINITIALIZED_DATA 00000080H 该块包含未初始化的数据
IMAGE_SCN_MEM_DISCARDABLE 02000000H 该块可被丢弃,因为它一旦被载入,进程就不再需要它,常见的可丢弃块是.reloc(重定位块)
IMAGE_SCN_MEM_SHARED 10000000H 该块为共享块
IMAGE_SCN_MEM_READ 40000000H 该块可读。可执行文件中的块总是被设置该标志
IMAGE_SCN_MEM_EXECUTE 20000000H 该块可以执行。通常当00000020H标志被设置时,该标志也被设置
IMAGE_SCN_MEM_WRITE 80000000H 该块可写。如果PE文件中没有设置该标志,装载程序就会将内存映像页标记为

常见区块

名称 描述
.text 代码区块,内容全是指令代码。链接器把所有目标文件的.text块链接成一个大的.text块。
.data 默认的读/写数据区块。全局变量、静态变量一般放在这里
.rdata 默认的只读区块,程序很少用到该块。有两种情况需要用到.rdata块。一是在Microsoft链接器产生的EXE文件中,用于存放调试目录;二是用于存放说明字符串。如果程序的DEF文件中指定了DESCRIPTION,字符串就会出现在.rdata块中。
.idata 包含其他外来的DLL的函数及数据信息,即输入表。将.idata区块合并到另一个区块已成为惯例,典型的是.rdata区块。
.edata 输出表。当创建一个输出API或数据的可执行文件时,链接器会创建一个.EXP文件,这个.EXP文件包含一个.edata区块,它会被加入最后的可执行文件中。idata区块也经常被合并
.rsrc 资源。包含模块的全部资源,例如图标、菜单、位图等。这个区块是只读的,不能命名位.rsrc以外的名字,也不能被合并。
.bss 未初始化数据。
.crt 用于支持c++运行时(CRT)所添加的数据
.tls TLS的意思是线程局部存储器,用于支持通过__declspec(thread)声明的线程局部存储变量的数据,既包括数据的初始化值,也包括运行时所需的额外变量
.reloc 可执行文件的基址重定位。基址重定位一般只是DLL需要。在Release模式下,链接器不回给EXE文件加上基质重定位。
.sdata IA-64的常规大小的全局变量放在这个区块。
.pdata 异常表,包含一个CPU特定的IMAGE_RUNTIME_FUNCTION_ENTRY结构数组,DataDirectory中的IMAGE_DIRECTORY_ENTRY_EXCEPTION指向它。它用于异常处理,是基于表的体系结构,就像IA-64。唯一不使用基于表的以尝处理架构体系是x86
.debug$S OBJ文件中Codeview格式的符号。这是一个变量长度的Codeview格式的符号记录流
.debug$T OBJ文件中Codeview格式的类型记录。这是一个变量长度的Codeview格式的类型记录流
.debug$P 当使用预编译的头时,可以在OBJ文件中找到它
.drectve 包含链接器命令,只能在OBJ中找到它。命令是能被传递给练级额其命令行的字符串。例如:-defaultlib:LIBC,命令行用空格字符分开
.didat 延迟载入的输入数据,只能在非Release模式的可执行文件中找到。在Release模式下,延迟载入的数据会被合并到另一个区块中

区块的对齐值

有两个对齐值,一种用于磁盘内,另一种用于内存中。PE文件头指出了这两个值。在PE文件头里,FileAlignment定义了磁盘区块的对齐值。每一个区块从对齐值的倍数的偏移位置开始,但是区块的实际代码或数据的大小不一定刚好是这么多,不足用00H填充。被填充的空间被称为区块间隙。

在PE文件里,SectionAlignment定义了内存中区块的对齐值。当PE文件被映射到内u那种时,区块总是至少从一个页边界处开始。每个区块的第一个字节对应于某个内存页。在x86系统CPU中,内存页是按4KB(1000h)排列的;在x64中内存是按8KB(2000h)排列的。

文件偏移与虚拟地址地址的转换

PE文件为了减少题基,磁盘对齐值不是一个内存页1000h,而是200h。因此这类文件被映射到内存中后,同一数据相对于文件头的偏移量在内存中与在磁盘中不同。

输入表

可执行文件使用来自其他DLL的代码或数据的动作称为输入。当PE文件被载入后,Windows加载器的工作就是定位所有被输入的函数和数据,并让正在载入的文件可以使用那些地址。这个过程是通过PE文件的输入表(Import Table,也称导入表)完成的。输入表中保存的是函数名和其驻留的DLL名等动态链接所需要的信息。

输入函数的调用

输入函数就是被程序调用但其执行代码不在程序中的函数,这些函数的代码位于相关的DLL中,在调用者程序中只保留相关的函数信息,例如函数名、DLL文件名等。对磁盘上的PE文件来说,它无法得知这些输入函数在内存中的地址。只有当PE文件载入内存后,Windows加载器才将相关DLL载入,并将调用输入函数的指令和函数实际所处的地址联系起来。

  • 隐式调用:当应用程序调用一个DLL的代码和数据时,程序就被隐式地连接到DLL,这个过程是由Windows加载器完成地。
  • 显示调用:首先需要确定目标DLL已被加载,然后寻找API地地址,是通过LoadLibrary和GetProcAddress完成的。

PE文件内有一组数据结构,分别对应于被输入的DLL。每一个这样的结构都给出了被输入的DLL的名称并指向一组函数指针。这组函数指针被称为输入地址表(Import Address Table,IAT)。每一个被引入的API在IAT里都保留位置,在IAT里API可以被Windows加载器写入输入函数的地址

输入表结构

1
2
3
4
5
6
7
8
9
10
IMAGE_IMPORT_DESCRIPTOR STRUCT
union
characteristics DWORD
OriginalFirstThunk DWORD
ends
TimeDateStamp DWORD
ForwarderChain DWORD
Name DWORD
FirstThunk DWORD
IMAGE_IMPORT_DESCRIPTOR ENDS
  • OriginalFirstThunk(Characteritics):包含指向输入名称表(INT)的RVA。INT是一个IMAGE_THUNK_DATA结构的数组,数组中的每个IMAGE_THUNK_DATA结构都指向IMAGE_IMPORT_BY_NAME结构,数组以一个内容为0的IMAGE_THUNK_DATA结构结束。
  • TimeDateStamp:一个32位的时间标志
  • ForwarderChain:这是第一个被转向的API的索引,一般为0,在程序引入一个DLL中的API,而这个API又在引用其他DLL的API时使用。
  • Name:DLL名字的指针。是以\x00结尾的ASCII字符的RVA地址,该字符串包含输入的DLL名。
  • FirstThunk:包含指向输入地址表(IAT)的RVA。IAT是一个IMAGE_THUNK_DATA结构的数组

1
2
3
4
5
6
7
8
IMAGE_THUNK_DATA	STRUCT
union u1
ForwarderString DWORD ;指向一个转向者字符串的RVA
Function DWORD ;被输入的函数的内存地址
Oridinal DWORD ;被输入的API的序数值
AddressOfData DWORD ;指向IMAGE_IMPORT_BY_NAME
ends
IMAGE_THUNK_DATA_ENDS

当IMAGE_THUNK_DATA值的最高位为1时,表示函数以序号方式输入,这时低31位被看成一个函数序号。当双字最高位为0时,表示函数以字符串类型的函数名方式输入,这时双字的值是一个RVA,指向一个IMAGE_IMPORT_BY_NAME结构

1
2
3
4
IMAGE_IMPORT_BY_NAME STRUCT
Hint WORD
Name BYTE
IMAGE_IMPORT_BY_NAME ENDS
  • Hint:本函数在其所驻留DLL的输入表中的序号。该域被PE装载器用来在DLL的输入表里快速查询函数。该值不是必需的,一些链接器将它设为0.
  • Name:含有输入函数的函数名。函数名是一个ASCII字符串,以”NULL”结尾。

输入表地址

为什么会有两个并行的指针数组指向IMAGE_IMPORT_BY_NAME结构

  • 第一个数组OriginalFirstThunk所指向的数组,为单独的一项,不可改写,称为INT。也称提示名表
  • 第二个数组有FirstThunk所指向的数组,由PE装载器重写的。
  • PE装载器先搜索OriginalFirst结构所指向的输入函数的地址。然后,加载器用函数真正的入口地址来代替由FirstThunk指向的IMAGE_THUNK_DATA数组里元素的值。Jmp dword ptr [xxxxx]语句中的[xxxxx]是指FirstThunk数组的一个入口,因此被称为输入地址表(IAT Import Address Table)。此时输入表的其他部分就不太重要了,程序依靠IAT提供的函数地址就可以正常运行。程序依靠IAT提供的函数地址就可以正常运行。

  • 另一种情况是程序OrignalFirstThunk的值为0。初始化时,系统根据FirstThunk的值找到指向函数名的地址串,根据地址串找到函数名,再根据函数名得到入口地址,然后用入口地址取代FirstThunk指向的地址串中的原值。

输入表实例分析

OrignalFirstThunk TimeDateStamp ForwardChain Name First Thunk
8C20 0000 0000 0000 0000 0000 7421 0000 1020 0000
7C20 0000 0000 0000 0000 0000 B421 0000 0020 0000
0000 0000 0000 0000 0000 0000 0000 0000 0000 0000

每个IID中的第4个字段是指向DLL名称的指针。OrignalFirstThunk中指向的是IMAGE_THUNK_DATA数组,它存储的是指向IMAGE_IMPORT_BY_NAME结构

IMAGE_THUNK_DATA数据

1021 0000 1c21 0000 f420 0000 e020 0000
5021 0000 6421 0000 0221 0000 ce20 0000
bc20 0000 2e21 0000 4221 0000 0000 000

同一IID结构中FirstThunk的字段值为2010h,即文件偏移为610h

1610721193755

其数据与PrignalFirstThunk字段所指的完全一样。

name字段所指向的不是刚好ASCII字符串,前面还有2个字节的空缺,这是作为函数名(Hint)引用的,可以为0.

API函数

提示名表(RVA) 提示名表(File Offset) Hint ApiName
00002110h 710h 019bh LoadIconA
0000211ch 71ch 01DDH PostQuitMessage
000020f4h 6f4h 0128h GetMessageA
000020e0h 6e0h 0094h DispatchMessageA
00002150h 750h 072dh TranslateMessage
00002164h 764h 028bh UpdateWindow
00002102h 702h 0197h LoadCursorA
000020ceh 6ceh 0083h DefWindowProcA
000020bch 72eh 01efh RegisterClassExA
00002142h 742h 0265h ShowWindow

绑定输入

当PE装载器载入PE文件时,会检查输入表并将相关的DLL映射到进程地址空间,然后遍历IAT里的IMAGE_THUNK_DATA数组并用输入函数的真实地址替换它。如果程序员能正确预测函数地址,PE装载器就不用再每次载入PE文件时都去修改IMAGE_THUNK_DATA的值。

当一个可执行文件被绑定时,IAT中的IMAGE_THUNK_DATA结构被输入函数的实际地址改写了。在磁盘中可执行文件的IAT里,有的存放的是与DLL输出函数相关的实际内存地址。

在整个进程执行期间,Bind程序做了如下两个重要假设

  • 当进程初始化时,需要的DLL实际上加载到了它们的首选基地址中。
  • 自从绑定操作执行依赖,DLL输出表中引用的符号位置一直没有改变。

IMAGE_BOUND_IMPORT_DESCRIPTOR

1
2
3
4
5
IMAGE_BOUND_IMPORT_DESCRIPTOR STRUCT
TimeDateStamp DWORD
offsetModuleName WORD
NumberOfModuleForwarderRefs WORD
IMAGE_BOUND_IMPORT_DESCRIPTOR ENDS
  • TimeDateStamp:一个汉字,包含一个被输入DLL的时间/日期戳。它允许加载器快速判断是否是新的。
  • OffsetModuleName:一个字,包含一个被输入DLL的名称的偏移。这个字段是与第一个IBID结构之间的偏移(不是RVA)
  • NumberOfModuleForwarderRes:一个字,包含紧跟该结构的IMAGE_BOUND_FORWARDER_REF结构的数目。

输出表

当一个DLL函数能被EXE或另一个DLL文件使用时,它就被输出。输出信息保存在输出表中,DLL文件通过输出表向系统提供输出函数名、序列号和入口地址等。

输出表结构

输出表的主要内容是一个表格,其中包括函数名称、输出序数。序数是指定DLL中某个函数的16位数字,在做指向的DLL独一无二。

输出表是数据目录表的第1个成员,指向IMAGE_EXPORT_DIRECTORY(IED)结构。

1
2
3
4
5
6
7
8
9
10
11
12
IMAGE_EXPORT_DIRECTORY STRUCT
Characteristics DWORD ;未使用,总是为0
TimeDateStamp DWORD ;文件生成时间
MajorVersion WORD ;主版本号,一般为0
MinorVersion WORD ;次版本号,一般为0
Name DWORD ;模块的真实名称
Base DWORD ;基数,序数减这个基数就是函数地址数组的索引值
NumberOfFunctions DWORD ;AddressOfFunctions阵列中的元素个数
NumberOfNames DWORD ;AddressOfName阵列中的元素个数
AddressOfFunctions DWORD ;指向函数地址数组
AddressOfNames DWORD ;函数名字的指针地址
AddressOfNameOrdinals DWORD ;指向输出序列号数组
  • Characteristics:输出属性的旗标。目前还没有定义,总是为0
  • TimeDateStamp:输出表创建的时间(GMT时间)
  • MajorVersion:输出表的主版本号。未使用,设置为0
  • MinorVersion:输出表的次版本号。未使用,设置为0
  • Name:指向一个ASCII字符串的RVA。这个字符串是与这些输出函数相关联的DLL的名字
  • Base:这个字段包含用于这个PE文件输出表的起始数值(基数)。正常情况下这个数值为1.当通过序数查询一个输出函数时,这个值从序数里被减去,其结果将作为进入输出地址表(EAT)的索引
  • NumberOfFunctions:EAT中的条目数量。当条目为0时,这个序数值表名没有代码或数据被输出
  • NumberOfNames:输出函数名称表(ENT)里的条目数量。NumberOfNames的值总是小于或等于NumberOfFunctions的值,小于的情况发生在符号只通过序数输出的时候。另外,当被赋值的序数里有数字间距时也会是小于的情况,这个值也是输出序数表的长度。
  • AddressOfFunctions:EAT的RVA。EAT是一个RVA数组,数组中的每一个非零的RVA都对应于一个被输出的符号。
  • AddressOfNames:ENT的RVA。ENT是一个指向ASCII字符串的RVA数组。每一个ASCII字符串对应于一个通过名字输出的符号。因为这个表是要排序的,所以ASCII字符串也是按顺序排序的。
  • AddressOfNameOrdinals:输出序数表的RVA。这个表是字的数组。这个表将ENT中的数组索引映射到相应的输出地址表条目。

基址重定位

在PE文件中,重定位表往往单独作为一块,用.reloc表示

基址重定位的概念

PE格式不参考外部DLL或模块中的其他区块,而是把文件中所有可能需要修改的地址放在一个数组里。如果PE文件不在首选的地址载入,那么文件中的每一个定位都需要被修改。

基址重定位表的结构

基址重定位表位于一个.reloc区块内,找到它们得正确方式是通过数据目录表得IMAGE_DIRECTORY_ENTRY_BASERELOC条目查找。基址重定位数据采用类似按页分割的方法组织,是由许多重定位块串接成的,每个块中存放4KB(1000h)的重定位信息,每个重定位数据块的大小必须以DWORD(4字节)对齐。以IMAGE_BASE_RELOCATION结构开始

1
2
3
4
5
IMAGE_BASE_RELOCATION STRUCT
VirtualAddress DWORD ;重定位数据的开始RVA地址
SizeOfBlock DWORD ;重定位块的长度
TypeOffset DWORD ;重定位项数组
IMAGE_BASE_RELOCATION ENDS
  • VirtualAddress:这组重定位数据的开始RVA地址。各重定位项的地址加这个值才是该重定位项的完整RVA地址
  • SizeOfBlock:当前重定位结构的大小。因为VirtualAddress和SizeOfBlock的大小都是固定的4字节,所以这个值减8就是TypeOffset数组的大小。
  • TypeOffset:一个数组。数组每项大小为2字节,共16位。这16位分为高4位和低12位。高4位代表重定位类型;低12位是重定位地址,它与VirtualAddress相加就是指向PE映像中需要修改的地址数据的指针。
类型 winnt.h里的预定义值 含义
0 IMAGE_REL_BASED_ABSOLUTE 没有具体的含义,只是为了让每个段4字节对齐
3h IMAGE_REL_BASED_HIGHLOW 重定位指向的整个地址都需要修改
10h IMAGE_REL_BASED_DIR4 出现在64位PE文件中,对指向的整个地址进行修正

重定位数据转换

项目 重定位数据1 重定位数据2 重定位数据3 重定位数据4
原始数据 0F30h 2330h 0000 0000
TypeOffset值 300Fh 3023h - -
TypeOffset高4位(类型) 3h 3h - -
TypeOffset低12位(地址) 00fh 023h - -
低12位加VirtualAddress 100fh(RVA) 1023h(RVA) - -
转换成文件偏移地址 60fh 623h - -

60fh和623h分别指向00402000h和00403030h处

资源

Windows程序的各种界面称为资源,包括加速键、位图、光标、对话框、图标、菜单、串表、工具栏和版本信息等。

资源结构

资源使用类型于磁盘目录结构的方式保存,目录通常包含三层。

  • 第一层类似于文件系统的根目录,每个根目录下的条目总是在它自己权限下的一个目录。
  • 第二层目录中的每一个都对应于一个资源类型。
  • 每个第2层资源类型目录下是第3层目录

资源目录结构

  • IMAGE_DIRECTORY_ENTRY_RESOURCE条目包含资源的RVA和大小
  • IMAGE_RESOURCE_DIRECTORY和数个IMAGE_RESOURCE_DIRECTORY_ENTRY组成资源目录结构

IMAGE_RESOURCE_DIRECTORY结构长度为16字节

1
2
3
4
5
6
7
IMAGE_RESOURCE_DIRECTORY STRUCT
Characteristics DWORD;理论上是资源的属性标志,通常为0
TimeDateStamp DWORD;资源建立时间
MajorVersion WORD;理论上是放置资源的版本,通常为0
MinorVersion WORD;
NumberOfNamedEntries WORD;使用名字的资源条目的个数
NumberOfEntries WORD;使用ID数字资源条目的个数

资源目录入口结构

1
2
3
IMAGE_RESOURCE_DIRECTORY_ENTRY	STRUCT
Name DWORD;目录项的名称字符串指针或ID
offsetToDate DWORD;资源数据偏移地址或子目录偏移地址

PE文件结构
https://h0pe-ay.github.io/PE文件结构/
作者
hope
发布于
2023年6月27日
许可协议