节表由多个节表项(IMAGE_SECTION_ HEADER)组成,每个节表项(40个字节)记录了 PE中与某个特定的节有关的信息,如节的属性、节 的大小、在文件和内存中的起始位置等。节表中节的数量由字段IMAGE_FILE_HEADER. NumberOfSections来定义。
●节表项的数据结构详细定义如下:
typedef struct _IMAGE_SECTION_HEADER {
BYTE Name[IMAGE_SIZEOF_SHORT_NAME]; // 节区名称,8 字节
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;
Offset | 大小 | 字段 | 说明 |
0 | 8 | 名称 | 8 字节的 Null 填充 UTF-8 编码字符串。 如果字符串长度正好为 8 个字符,则无终止 null 字符。 对于较长的名称,此字段包含一个斜杠 (/),后跟十进制数的 ASCII 表示形式,该十进制数是字符串表中的偏移量。 可执行映像不使用字符串表,也不支持长度超过 8 个字符的节名称。 如果向可执行文件发送对象文件中的长名称,则这些长名称将被截断。 |
8 | 4 | VirtualSize | 加载到内存中时节的总大小。 如果此值大于 SizeOfRawData,则节中会用零填充。 此字段仅对可执行映像有效,应针对对象文件设置为零。 |
12 | 4 | VirtualAddress | 对于可执行映像,是指当节加载到内存中时,该节相对于映像基址的第一个字节的地址。 对于对象文件,此字段是应用重定位前第一个字节的地址;为简单起见,编译器应将此字段设置为零。 否则,它是重定位期间从偏移量中减去的任意值。 |
16 | 4 | SizeOfRawData | 节(对于对象文件)的大小或磁盘上已初始化的数据的大小(对于映像文件)。 对于可执行映像,这必须是可选标头中的 FileAlignment 的倍数。 如果此值小于 VirtualSize,则节的其余部分用零填充。 由于 SizeOfRawData 字段被舍入,但 VirtualSize 字段未被舍入,因此 SizeOfRawData 也可能大于 VirtualSize。 当节仅包含未初始化的数据时,此字段应为零。 |
20 | 4 | PointerToRawData | 指向 COFF 文件中节的第一页的文件指针。 对于可执行映像,这必须是可选标头中的 FileAlignment 的倍数。 对于对象文件,该值应在 4 字节边界上对齐,以获取最佳性能。 当节仅包含未初始化的数据时,此字段应为零。 |
24 | 4 | PointerToRelocations | 指向节的重定位项的开头的文件指针。 对于可执行映像或没有重定位的情况,这项设置为零。 |
28 | 4 | PointerToLinenumbers | 指向节的行号项开头的文件指针。 如果没有 COFF 行号,则此字段设置为零。 映像的此值应为零,因为 COFF 调试信息已被弃用。 |
32 | 2 | NumberOfRelocations | 节的重定位项数。 对于可执行映像,此字段设置为零。 |
34 | 2 | NumberOfLinenumbers | 节的行号项数。 映像的此值应为零,因为 COFF 调试信息已被弃用。 |
36 | 4 | 特征 | 描述节特性的标志。如可执行、可读、可写等。 有关更多信息,请参见MSDN节标记。 |
IMAGE_SECTION_HEADER结构的每个实例对应于可执行文件中的一个具体节,它们按顺序排列。结构的字段提供了关于每个节的重要信息,如名称、大小、位置和属性等。
注意
具体的可执行文件格式可能会有所不同,因此在不同的格式中,IMAGE_SECTION_HEADER结构的字段名称和用途可能会有所不同。上述示例是基于PE格式的IMAGE_SECTION_HEADER结构,其他格式可能具有类似的结构,但字段名称和用途可能有所不同。
●节名称
每个节都有一个名称,用于标识其在文件中的位置和含义。下表是PE文件节表中常见的一些节名称。
名称 | 描述 |
.arch | 包含指定处理器架构的特定代码。 |
.bss | 包含未初始化的全局变量和静态变量。 |
.cormeta | 包含用于支持.NET程序的元数据。 |
.data | 包含已初始化的全局变量和静态变量。 |
.debug | 包含调试信息,用于程序的调试和分析。 |
.drectve | 包含连接器指令,指导连接器的行为。 |
.data.rel | 包含重定位信息,用于在程序加载时调整已初始化数据的地址。 |
.idata | 包含导入表,用于指定程序运行时所需的外部函数和库。 |
.edata | 包含导出表,用于指定程序提供给其他模块使用的函数和数据。 |
.pdata | 包含异常处理信息,用于处理程序中的异常情况。 |
.rdata | 包含只读数据,如字符串常量。 |
.reloc | 包含重定位信息,用于在程序加载时调整代码和数据的地址。 |
.rsrc | 包含资源数据,如图标、位图、字符串等。 |
.sxdata | 包含安全异常处理信息。 |
.text | 包含程序的可执行代码。 |
.tls | 包含线程本地存储(Thread Local Storage)的数据。 |
.vmp0 | 用于虚拟机保护的特殊节表。 |
.xdata | 包含异常处理信息,特别是在x64架构中使用的异常处理信息。 |
.textbss | 为了支持Visual Studio在调试过程中动态编译和更新代码的功能(“编辑并继续”功能)。当你在Debug模式下修改了代码,Visual Studio会将被修改的函数放到.textbss节里,然后修改对应的ILT表项(增量同步表),使它指向这个位置。 |
.gfids | 包含GFIDS 表,(RVA)相对虚拟地址排序列表,其中包含有关有效 CFG 调用目标的信息。 |
.msvcjmc | 用于存储调试信息的节,它是由Visual Studio在Debug模式下生成的。它包含了一些用于支持“编辑并继续”功能的符号,比如函数的入口点、返回地址等。这个功能可以让你在调试过程中修改代码,并且不需要重新链接程序。在Release模式下,这个节通常会被删除或合并到其他节中。 |
.00cfg | 用于支持Control Flow Guard (CFG) 功能。Control Flow Guard 是一种在编译时和运行时增强程序的安全性的技术,用于检测和防止恶意代码利用程序的控制流(缓冲区溢出)来进行攻击。.00cfg节区包含与Control Flow Guard 相关的元数据和指令,用于验证程序的控制流。这些元数据包括函数的控制流图信息、检查函数的有效目标地址列表和检查函数调用的指令。 |
节名称在可执行文件中是以字符串形式存储的,并且具有一定的可读性。这些名称通常由开发人员或编译器指定,以反映对应节的用途和内容。通过使用具有良好可读性的节表名称,开发人员和分析人员能够更轻松地理解和解释可执行文件的结构,快速定位所需的代码、数据和资源,从而提高开发、调试和分析的效率。
●特殊节
有一些节是Visual Studio在Debug模式下使用的特殊的节,它不是PE文件结构中的标准部分。
1..textbss
.textbss节为了支持Visual Studio在调试过程中动态编译和更新代码的功能(“编辑并继续”功能)。
当你在Debug模式下修改了代码,Visual Studio会将被修改的函数放到.textbss节里,然后修改对应的ILT表项(增量同步表),使它指向这个位置。
.textbss节有以下几个特点:
a.它是未初始化的可执行代码节,也就是说,它具有可执行属性,在PE文件中不占用硬盘文件空间,在加载到内存时不填充数据。
图3-10 VS2017启用或禁用增量链接选项
b.它是在.text节之后的一个额外的节,它的大小和位置是由Visual Studio动态分配和调整的。
c.它只在Debug模式下有效,Release模式下默认禁用Incremental Linking,所以不会生成这个节。以VS2017版本为例,如图3-10所示:
【注】在Debug模式下,VS默认启用增量链接。“项目配置属性”>“C/C++”>“常规”>“调试信息格式”选项改为“用于“编辑并继续”的程序数据库 (/ZI)”。
2..msvcjmc
.msvcjmc节是一个用于存储调试信息的节,它是由Visual Studio在Debug模式下生成的。
它包含了一些用于支持“编辑并继续”功能的符号,比如函数的入口点、返回地址等。这个功能可以让你在调试过程中修改代码,并且不需要重新链接程序。
在Release模式下,这个节通常会被删除或合并到其他节中。
VS2017配置选项如图3-11所示:
图3-11 VS2017 JMC配置选项
3..00cfg
.00cfg节是一个用于存储控制流保护(Control Flow Guard)相关信息的节,它也是由Visual Studio生成的。
控制流保护是一种用于防止缓冲区溢出攻击的技术,它可以检查间接调用的目标是否有效。.00cfg节中包含了一些指向检查函数的指针。我们来看一下微软MSDN官方的解释:
a.什么是控制流防护?
控制Flow防护 (CFG) 是一项高度优化的平台安全功能,旨在打击内存损坏漏洞。 通过严格限制应用程序可以从何处执行代码,利用漏洞(如缓冲区溢出)执行任意代码会更加困难。 CFG 扩展了以前的攻击缓解技术,如 /GS、 DEP 和 ASLR。
防止内存损坏和勒索软件攻击。
将服务器的功能限制为在特定时间点所需的任何功能,以减少攻击面。
更难通过缓冲区溢出等漏洞利用任意代码。
此功能在 2015 Microsoft Visual Studio中提供,并在Windows的“CFG 感知”版本上运行,即适用于桌面和 Windows 10服务器的 x86 和 x64 版本,Windows 8.1 更新 (KB3000850) 。
我们强烈建议开发人员为其应用程序启用 CFG。 无需为代码的每个部分启用 CFG,因为已启用 CFG 的非 CFG 代码将执行罚款。 但是,未能为所有代码启用 CFG 可能会打开保护中的空白。 此外,已启用 CFG 的代码适用于Windows的“CFG-Unaware”版本,因此与它们完全兼容。
图3-12 启动控制流防护
b.如何启用 CFG?
在大多数情况下,无需更改源代码。 只需向 Visual Studio 2015 项目添加一个选项,编译器和链接器将启用 CFG。
.最简单的方法是导航到“Project”>“属性”>“配置属性”>“C/C++ ”>“代码生成”>“控制流防护”选择“是”, (/guard:cf) 控制Flow防护,如图3-12所示。
或者,将 /guard:cf 添加到“Project”>“属性”>“配置属性”>“C/C++”>“命令行”>“其他选项”和 “Project”>“属性”>“配置属性”>“链接器”>“命令行”>“其他选项”。
.如果要从命令行生成项目,可以添加相同的选项。 例如,如果要编译名为 test.cpp 的项目,请使用 cl /guard:cf test.cpp /link /guard:cf。
.还可以选择使用内存管理 API 中的 SetProcessValidCallTargets 动态控制 CFG 视为有效的 icall 目标地址集。 可以使用同一 API 来指定页面是无效还是有效的 CFG 目标。 VirtualProtect 和 VirtualAlloc 函数默认将指定的可执行文件和提交的页面区域视为有效的间接调用目标。 可以通过指定在调用 VirtualAlloc 时或 PAGE_TARGETS_NO_UPDATE调用VirtualProtect 时按内存保护常量下详述的方式指定PAGE_TARGETS_INVALID来替代此行为,例如在实现实时编译器时。
c.如何判断二进制PE文件处于控制流防护之下?
使用 /headers 和 /loadconfig 选项从 Visual Studio 命令提示符运行Visual Studio 2015 安装) 中包含的转储绑定 工具:dumpbin /headers /loadconfig test.exe。CFG下的二进制文件的输出应显示标头值包括“Guard”,并且加载配置值包括“CF检测”和“FID表存在”。
图3-13 判断PE文件处于控制流防护之下
d. CFG 如何真正工作?
软件漏洞通常通过向正在运行的程序提供不太可能、异常或极端的数据来利用。例如,攻击者可以通过向程序提供比预期更多的输入来利用缓冲区溢出漏洞,从而过度运行程序保留的区域来保存响应。这可能会损坏可能保存函数指针的相邻内存。当程序通过此函数调用时,它可能会跳转到攻击者指定的意外位置。
但是,CFG 的编译和运行时支持的有效组合实现了控制流完整性,严格限制间接调用指令可以执行的位置。
编译器执行以下操作:
将轻型安全检查添加到已编译的代码。
标识应用程序中用于间接调用的有效目标的函数集。
Windows内核提供的运行时支持:
有效维护标识有效间接调用目标的状态。
实现验证间接调用目标的逻辑是否有效。
为了说明:
图3-14 控制流防护执行流程
当 CFG 检查在运行时失败时,Windows会立即终止程序,从而破坏尝试间接调用无效地址的任何攻击。
开启控制流防护后,编译生成的PE文件中将会新增.gfids节区。
4..gfids
作为有效间接调用目标的函数在附加到负载配置目录的 GuardCFFunctionTable 中列出,为了简洁起见,有时称为 GFIDS 表。这是(RVA)相对虚拟地址排序列表,其中包含有关有效 CFG 调用目标的信息。 一般来说,这些是地址采用的函数符号。想要强制实施 CFG 的映像必须枚举其 GFIDS 表中所有采用的地址函数符号。GFIDS 表中的 RVA 列表必须正确排序,否则不会加载映像。 GFIDS 表是一个 4 + n 个字节的数组,其中n 由 ( (GuardFlags & IMAGE_GUARD_CF_FUNCTION_TABLE_SIZE_MASK) >> IMAGE_GUARD_CF_FUNCTION_TABLE_SIZE_SHIFT) 提供。 “GuardFlags”是负载配置目录的 GuardFlags 字段。 这允许将来将额外的元数据附加到 CFG 调用目标。当前唯一定义的元数据是可选的1字节额外标志字段(“GFIDS 标志”),如果任何调用目标具有元数据,则附加到每个GFIDS条目。
注意
1.如果要开启控制流防护,为了防止冲突,需要将“Project”>“属性”>“配置属性”>“C/C++”>“常规”>“调试信息格式”选项设置改为“程序数据库 (/Zi)”。否则将会出现下面的提示信息:
1>cl : 命令行 error D8016: “/ZI”和“/guard:cf”命令行选项不兼容
2.上述特殊节只存在于Debug模式下,Release版本中不会不存在。
●PE文件的节表名称在可执行文件的结构中起着重要的作用,其主要功能如下:
1.识别和定位不同类型的数据和代码:通过节表名称,可以确定和识别可执行文件中各个节的内容和用途。例如,.text节通常包含可执行代码,.data节包含已初始化的数据,.rsrc节包含资源数据等。这些名称提供了对文件结构的关键描述,使得解析和理解可执行文件变得更加容易。
2.内存分配和加载:节表中的节表项描述了每个节在内存中的位置和大小。操作系统在加载可执行文件时,根据节表中的信息,将不同的节映射到适当的内存地址。这样,程序在运行时可以访问和执行所需的代码和数据。
3.调试和分析:节表名称对于调试和分析可执行文件也非常有用。调试器和分析工具可以根据节表名称,定位到特定的节,以查看和修改对应的代码、数据或资源。开发人员可以利用这些名称来诊断问题、跟踪程序执行路径和优化性能。
4.资源管理:可执行文件中的资源(如图标、位图、字符串等)通常存储在特定的节中(如.rsrc节)。通过节表中的资源节,程序可以准确地定位和访问这些资源,以供运行时使用或在需要时进行提取和显示。
5.安全性和防御:节表名称可以帮助安全工具和防御机制对可执行文件进行分析和检查。安全软件可以检查特定的节表名称,以识别和阻止潜在的恶意行为或漏洞利用。
●当涉及到PE文件的节表名称时,还有一些其他方面需要了解:
1.特定功能节表:除了上述提到的常见节表名称外,某些特定功能的PE文件可能还包含其他特定的节表。例如,针对特定编程语言、框架或库的PE文件可能包含与其相关的专用节表,用于存储特定的元数据、符号表、调试信息等。
2.对齐和填充:节表名称也与对齐和填充有关。在PE文件中,节表的大小和位置通常按照对齐规则(512个字节)进行对齐,以提高性能和内存访问效率。填充节(如.bss节)用于在文件中占据空间,以便在运行时进行初始化。
3.节表顺序:节表在PE文件中的顺序通常是固定的,但也可以根据需要进行调整。例如,某些优化工具可以重新排列节表的顺序,以改进加载性能或减小文件大小。
4.自定义节表名称:虽然PE文件的节表名称中有一些通用的约定,但并没有严格的规定。因此,开发人员可以根据自己的需求和偏好为节表定义自定义的名称。这些自定义名称通常在工具链的设置或配置文件中指定。
5.节区名称的可读性:虽然节表名称在可执行文件中非常重要,但在运行时并不直接对用户可见。PE文件的节表名称并没有严格的可读性要求,它们通常是由编译器、链接器或其他工具根据一定的命名规则生成的。因此,这些名称可能看起来比较晦涩或缩写,对于非熟悉PE文件格式的人来说可能不太容易理解。然而,一些常见的节表名称(如.text、.data、.rsrc等)在一定程度上具有可读性,因为它们反映了节的用途或内容。
6.节的重定位:重定位信息(如.reloc节)描述了程序在加载时需要进行的地址调整。这些调整是为了适应可执行文件被加载到不同基地址的情况,通常发生在动态链接和装载过程中。
●节区属性
PE文件的节区(Section)具有各自的属性,这些属性描述了节区在文件和内存中的特性和行为。以下是一些常见的节区属性:
1.可执行性(Executable):指示节区是否包含可执行的机器代码。具有此属性的节区通常包含程序的指令和可执行代码。
2.可读性(Readable):指示节区是否允许读取操作。具有此属性的节区可以被读取,例如,用于存储常量数据或只读数据。
3.可写性(Writable):指示节区是否允许写入操作。具有此属性的节区可以被写入,例如,用于存储可修改的数据、全局变量或静态变量。
4.共享性(Shareable):指示节区是否可以被其他进程或模块共享。具有此属性的节区可以在多个实例之间共享,以减少内存占用。
5.初始化(Initialized):指示节区是否包含已初始化的数据。具有此属性的节区在加载时将被初始化为指定的初始值。
6.虚拟内存(Virtual Memory):指示节区是否在虚拟内存中有对应的映射。具有此属性的节区在加载时将在虚拟内存中分配空间。
7.重定位(Relocatable):指示节区是否包含需要进行重定位的位置。具有此属性的节区包含依赖于基址的指针或地址,需要在加载时进行调整。
8.对齐(Alignment):指示节区在内存中的对齐要求。具有此属性的节区需要按照指定的对齐边界对齐,以提高内存访问效率。
这些节区属性在PE文件的节表项中以标志位的形式表示,每个标志位对应一个属性。通过这些属性,操作系统和加载器可以了解如何处理和映射各个节区,以满足程序的需求,并提供适当的内存保护和访问权限。
9.节的属性参考:
00000020h 包含代码
00000040h 包含已经初始化的数据,如.const
00000080h 包含未初始化数据,如 .data?
02000000h 数据在进程开始以后被丢弃,如.reloc
04000000h 节中数据不经过缓存
08000000h 节中数据不会被交换到磁盘
10000000h 数据将被不同进程共享
20000000h 可执行
40000000h 可读
80000000h 可写
常见的代码节一般为:60000020h,数据节一般为:c0000040h,常量节一般为:40000040h。
以下是MSDN给出的节属性的所有标志值:
标志 | 值 | 说明 |
0x00000000 | 保留供将来使用。 | |
0x00000001 | 保留供将来使用。 | |
0x00000002 | 保留供将来使用。 | |
0x00000004 | 保留供将来使用。 | |
IMAGE_SCN_TYPE_NO_PAD | 0x00000008 | 不应将节填充到下一个边界。 此标志已过时并且已被 IMAGE_SCN_ALIGN_1BYTES 取代。 这仅对对象文件有效。 |
0x00000010 | 保留供将来使用。 | |
IMAGE_SCN_CNT_CODE | 0x00000020 | 该节包含可执行代码。 |
IMAGE_SCN_CNT_INITIALIZED_DATA | 0x00000040 | 该节包含初始化的数据。 |
IMAGE_SCN_CNT_UNINITIALIZED_ DATA | 0x00000080 | 该节包含未初始化的数据。 |
IMAGE_SCN_LNK_OTHER | 0x00001000 | 保留供将来使用。 |
IMAGE_SCN_LNK_INFO | 0x00000200 | 该节包含注释或其他信息。 .drectve 节具有此类型。 这仅对对象文件有效。 |
0x00040000 | 保留供将来使用。 | |
IMAGE_SCN_LNK_REMOVE | 0x00000800 | 该节将不会成为映像的一部分。 这仅对对象文件有效。 |
IMAGE_SCN_LNK_COMDAT | 0x00001000 | 该节包含 COMDAT 数据。 有关详细信息,请参阅 COMDAT 节(仅限对象)。 这仅对对象文件有效。 |
IMAGE_SCN_GPREL | 0x00008000 | 该节包含通过全局指针 (GP) 引用的数据。 |
IMAGE_SCN_MEM_PURGEABLE | 0x00020000 | 保留供将来使用。 |
IMAGE_SCN_MEM_16BIT | 0x00020000 | 保留供将来使用。 |
IMAGE_SCN_MEM_LOCKED | 0x00040000 | 保留供将来使用。 |
IMAGE_SCN_MEM_PRELOAD | 0x00080000 | 保留供将来使用。 |
IMAGE_SCN_ALIGN_1BYTES | 0x00100000 | 在 1 字节边界上对齐数据。 仅对对象文件有效。 |
IMAGE_SCN_ALIGN_2BYTES | 0x00200000 | 在 2 字节边界上对齐数据。 仅对对象文件有效。 |
IMAGE_SCN_ALIGN_4BYTES | 0x00300000 | 在 4 字节边界上对齐数据。 仅对对象文件有效。 |
IMAGE_SCN_ALIGN_8BYTES | 0x00400000 | 在 8 字节边界上对齐数据。 仅对对象文件有效。 |
IMAGE_SCN_ALIGN_16BYTES | 0x00500000 | 在 16 字节边界上对齐数据。 仅对对象文件有效。 |
IMAGE_SCN_ALIGN_32BYTES | 0x00600000 | 在 32 字节边界上对齐数据。 仅对对象文件有效。 |
IMAGE_SCN_ALIGN_64BYTES | 0x00700000 | 在 64 字节边界上对齐数据。 仅对对象文件有效。 |
IMAGE_SCN_ALIGN_128BYTES | 0x00800000 | 在 128 字节边界上对齐数据。 仅对对象文件有效。 |
IMAGE_SCN_ALIGN_256BYTES | 0x00900000 | 在 256 字节边界上对齐数据。 仅对对象文件有效。 |
IMAGE_SCN_ALIGN_512BYTES | 0x00A00000 | 在 512 字节边界上对齐数据。 仅对对象文件有效。 |
IMAGE_SCN_ALIGN_1024BYTES | 0x00B00000 | 在 1024 字节边界上对齐数据。 仅对对象文件有效。 |
IMAGE_SCN_ALIGN_2048BYTES | 0x00C00000 | 在 2048 字节边界上对齐数据。 仅对对象文件有效。 |
IMAGE_SCN_ALIGN_4096BYTES | 0x00D00000 | 在 4096 字节边界上对齐数据。 仅对对象文件有效。 |
IMAGE_SCN_ALIGN_8192BYTES | 0x00E00000 | 在 8192 字节边界上对齐数据。 仅对对象文件有效。 |
IMAGE_SCN_LNK_NRELOC_OVFL | 0x01000000 | 此节包含扩展的重定位。 |
IMAGE_SCN_MEM_DISCARDABLE | 0x02000000 | 可以根据需要丢弃此节。 |
IMAGE_SCN_MEM_NOT_CACHED | 0x04000000 | 无法缓存此节。 |
IMAGE_SCN_MEM_NOT_PAGED | 0x08000000 | 此节不可分页。 |
IMAGE_SCN_MEM_SHARED | 0x10000000 | 此节可以在内存中共享。 |
IMAGE_SCN_MEM_EXECUTE | 0x20000000 | 此节可以作为代码执行。 |
IMAGE_SCN_MEM_READ | 0x40000000 | 可以读取此节。 |
IMAGE_SCN_MEM_WRITE | 0x80000000 | 可以写入到此节中。 |
实验十五:解析32位和64位PE文件节的属性
我们以32位和64位HelloWorld.exe为例,在WinHex工具中解析节的属性。
●32位HelloWorld32.exe(Debug版)
将HelloWorld32.exe拖入WinHex,在文件头的第二个字段NumberOfSections中存储节区的数量,这个值为9。在NT头的下方就是节表,如下所示。
000001E0 00 00 00 00 00 00 00 00 2E 74 65 78 74 62 73 73 .........textbss
000001F0 00 00 01 00 00 10 00 00 00 00 00 00 00 00 00 00 ................
00000200 00 00 00 00 00 00 00 00 00 00 00 00 A0 00 00 E0 ............?.?
00000210 2E 74 65 78 74 00 00 00 C6 4F 00 00 00 10 01 00 .text...芆......
00000220 00 50 00 00 00 04 00 00 00 00 00 00 00 00 00 00 .P..............
00000230 00 00 00 00 20 00 00 60 2E 72 64 61 74 61 00 00 .... ..`.rdata..
00000240 29 20 00 00 00 60 01 00 00 22 00 00 00 54 00 00 ) ...`..."...T..
00000250 00 00 00 00 00 00 00 00 00 00 00 00 40 00 00 40 ............@..@
00000260 2E 64 61 74 61 00 00 00 B8 05 00 00 00 90 01 00 .data...?......
00000270 00 02 00 00 00 76 00 00 00 00 00 00 00 00 00 00 .....v..........
00000280 00 00 00 00 40 00 00 C0 2E 69 64 61 74 61 00 00 ....@...idata..
00000290 B0 0A 00 00 00 A0 01 00 00 0C 00 00 00 78 00 00 ?...?......x..
000002A0 00 00 00 00 00 00 00 00 00 00 00 00 40 00 00 40 ............@..@
000002B0 2E 6D 73 76 63 6A 6D 63 0F 01 00 00 00 B0 01 00 .msvcjmc.....?.
000002C0 00 02 00 00 00 84 00 00 00 00 00 00 00 00 00 00 .....?.........
000002D0 00 00 00 00 40 00 00 C0 2E 30 30 63 66 67 00 00 ....@...00cfg..
000002E0 04 01 00 00 00 C0 01 00 00 02 00 00 00 86 00 00 .....?......?.
000002F0 00 00 00 00 00 00 00 00 00 00 00 00 40 00 00 40 ............@..@
00000300 2E 72 73 72 63 00 00 00 3C 04 00 00 00 D0 01 00 .rsrc...<....?.
00000310 00 06 00 00 00 88 00 00 00 00 00 00 00 00 00 00 .....?.........
00000320 00 00 00 00 40 00 00 40 2E 72 65 6C 6F 63 00 00 ....@..@.reloc..
00000330 45 05 00 00 00 E0 01 00 00 06 00 00 00 8E 00 00 E....?......?.
00000340 00 00 00 00 00 00 00 00 00 00 00 00 40 00 00 42 ............@..B
这是一个Debug版的32位HelloWorld32.exe PE文件。其中包含3个特殊的节区:.textbss、.msvcjmc、.00cfg。这三个节区在Release版中不再存在。
1..textbss节区:未初始化的可执行代码节。它具有可执行属性,在PE文件中不占用硬盘文件空间,在加载到内存时不填充数据。
节区在虚拟内存中的大小:00 01 00 00h;
节区在内存中的虚拟地址:00 00 10 00h;
节区在文件中的大小(字节):00 00 00 00h;
节区在文件中的偏移位置:00 00 00 00h;
节区属性值:0xE00000A0;
低字节A0分解为80+20,表示该节包含未初始化的数据、可执行代码。
高字节E0分解为80+40+20,表示此节可以作为代码执行,可以读取和写入此节。
2..msvcjmc节区:用于存储调试信息的节。
节区在虚拟内存中的大小:00 00 01 0Fh;
节区在内存中的虚拟地址:00 01 B0 00h;
节区在文件中的大小(字节):00 00 02 00h;
节区在文件中的偏移位置:00 00 84 00h;
节区属性值:0xC0000040;
低字节40表示该节包含已初始化的数据。
高字节C0分解为80+40,表示可以读取和写入此节。
3..00cfg节区:用于存储控制流保护(Control Flow Guard)相关信息的节。
节区在虚拟内存中的大小:00 00 01 04h;
节区在内存中的虚拟地址:00 01 C0 00h;
节区在文件中的大小(字节):00 00 02 00h;
节区在文件中的偏移位置:00 00 86 00h;
节区属性值:0x40000040;
低字节40表示该节包含已初始化的数据。
高字节40表示可以读取此节。
4..text节区:包含程序的可执行代码。
节区在虚拟内存中的大小:00 00 4F C6h;
节区在内存中的虚拟地址:00 01 10 00h;
节区在文件中的大小(字节):00 00 50 00h;
节区在文件中的偏移位置:00 00 04 00h;
节区属性值:0x60000020;
低字节20表示该节包含可执行代码。
高字节60分解为40+20表示可以读取和写入此节。
5..rdata:包含只读数据,如字符串常量。
节区在虚拟内存中的大小:00 00 20 29h;
节区在内存中的虚拟地址:00 01 60 00h;
节区在文件中的大小(字节):00 00 22 00h;
节区在文件中的偏移位置:00 00 54 00h;
节区属性值:0x40000040;
低字节40表示该节包含已初始化的数据。
高字节40表示可以读取此节。
6..data:包含已初始化的全局变量和静态变量。
节区在虚拟内存中的大小:00 00 05 B8h;
节区在内存中的虚拟地址:00 01 90 00h;
节区在文件中的大小(字节):00 00 02 00h;
节区在文件中的偏移位置:00 00 76 00h;
节区属性值:0xC0000040;
低字节40表示该节包含已初始化的数据。
高字节C0分解为80+40,表示可以读取和写入此节。
7..idata:包含导入表,用于指定程序运行时所需的外部函数和库。
节区在虚拟内存中的大小:00 00 0A B0h;
节区在内存中的虚拟地址:00 01 A0 00h;
节区在文件中的大小(字节):00 00 0C 00h;
节区在文件中的偏移位置:00 00 78 00h;
节区属性值:0x40000040;
低字节40表示该节包含已初始化的数据。
高字节40表示可以读取此节。
8..rsrc:包含资源数据,如图标、位图、字符串等。
节区在虚拟内存中的大小:00 00 04 3Ch;
节区在内存中的虚拟地址:00 01 D0 00h;
节区在文件中的大小(字节):00 00 06 00h;
节区在文件中的偏移位置:00 00 88 00h;
节区属性值:0x40000040;
低字节40表示该节包含已初始化的数据。
高字节40表示可以读取此节。
9..reloc:包含重定位信息,用于在程序加载时调整代码和数据的地址。
节区在虚拟内存中的大小:00 00 05 45h;
节区在内存中的虚拟地址:00 01 E0 00h;
节区在文件中的大小(字节):00 00 06 00h;
节区在文件中的偏移位置:00 00 8E 00h;
节区属性值:0x42000040;
低字节40表示该节包含已初始化的数据。
高字节42表示可以读取此节,可以根据需要丢弃此节。
32位HelloWorld64.exe的程序入口:IMAGE_OPTIONAL_HEADER32. AddressOfEntryPoint的值为0x0001123A。
32位HelloWorld64.exe的基址:IMAGE_OPTIONAL_HEADER32. ImageBase 的值为0x00400000。
Name | VirtualSize | Virtual Address | SizeOf RawData | PointerTo RawData | Characteristics |
.textbss | 00010000H | 00001000H | 00000000H | 00000000H | E00000A0H |
.text | 00004FC6H | 00011000H | 00005000H | 00000400H | 60000020H |
.rdata | 00002029H | 00016000H | 00002200H | 00005400H | 40000040H |
.data | 000005B8H | 00019000H | 00000200H | 00007600H | C0000040H |
.idata | 00000AB0H | 0001A000H | 00000C00H | 00007800H | 40000040H |
.msvcjmc | 0000010FH | 0001B000H | 00000200H | 00008400H | C0000040H |
.00cfg | 00000104H | 0001C000H | 00000200H | 00008600H | 40000040H |
.rsrc | 0000043CH | 0001D000H | 00000600H | 00008800H | 40000040H |
.reloc | 00000545H | 0001E000H | 00000600H | 00008E00H | 42000040H |
64位HelloWorld64.exe留给读者独立分析,并对比debug版本和release版本之间的差异。
总结
编译后的 PE 文件中的节区可能因多种因素而不同,包括编译器选项、链接器选项、优化级别、使用的库和代码结构等。
●节数据
节的初始化数据由简单的字节块组成。 但是,对于全部为零的节,不需要包含节数据。
每个节的数据都位于节标头中 PointerToRawData 字段给出的文件偏移量处。 文件中此数据的大小由 SizeOfRawData 字段来指示。 如果 SizeOfRawData 小于 VirtualSize,则剩余部分用零填充。
在映像文件中,节数据必须在可选标头中 FileAlignment 字段指定的边界上对齐。 节数据必须按相应节的 RVA 值的顺序显示(与节表中的各个节标题一样)。
如果可选标头中的 SectionAlignment 值小于体系结构的页面大小,则图像文件还有其他限制。 对于此类文件,文件中节数据的位置必须与加载映像时内存中的节数据位置匹配,以便节数据的物理偏移量与 RVA 相同。
实验十六:C语言实现将RVA地址转换为VA地址。
RVA地址转换为VA地址的方法:
sectionStartVa =sectionHeader->VirtualAddress + ntHeaders->OptionalHeader.ImageBase;
va = sectionStartVa + (rva - sectionStartRva)。
■源代码
/*------------------------------------------------------------------------
FileName:RvaToVa.c
实验16:Rva地址转换为VA地址
(c) bcdaren, 2024
-----------------------------------------------------------------------*/
#include <windows.h>
#include <dbghelp.h>
#define MAXSIZE 1024
#pragma comment (lib,"dbghelp")
/*
函数使用64位PE的NT头结构体
*/
PVOID RvaToVa64(PIMAGE_NT_HEADERS64 ntHeaders, DWORD rva) {
PIMAGE_SECTION_HEADER sectionHeader = IMAGE_FIRST_SECTION(ntHeaders);
WORD numberOfSections = ntHeaders->FileHeader.NumberOfSections;
for (WORD i = 0; i < numberOfSections; i++) {
DWORD sectionStartRva = sectionHeader->VirtualAddress;
DWORD sectionEndRva = sectionStartRva +
sectionHeader->Misc.VirtualSize;
if (rva >= sectionStartRva && rva < sectionEndRva) {
//节区起始地址=节区虚拟内存起始地址+PE模块基址
DWORD sectionStartVa = (DWORD)sectionHeader->VirtualAddress +
(DWORD)ntHeaders->OptionalHeader.ImageBase;
unsigned long va = sectionStartVa + (rva - sectionStartRva);
return (PVOID)va;
}
sectionHeader++;
}
return NULL; // RVA not found
}
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
{
const TCHAR szCaption[MAXSIZE] = TEXT("RvaToVa");
static TCHAR szText[MAXSIZE] = { 0 };
// 获取当前模块的句柄
HMODULE hModule = GetModuleHandle(NULL);
// 获取模块的 IMAGE_NT_HEADERS 结构
PIMAGE_NT_HEADERS ntHeaders = ImageNtHeader(hModule);
if (ntHeaders) {
// 假设要转换的 RVA 是 0x1000
ULONG rva = 0x1000;
//32位系统
//PVOID va = ImageRvaToVa(ntHeaders, hModule, rva, NULL);
//64位系统
PVOID va = RvaToVa64((PIMAGE_NT_HEADERS64)ntHeaders, rva);
if (va) {
wsprintf(szText,
TEXT("RVA 0x%08X converted to virtual address: %p\n"),
rva, va);
}
else {
wsprintf(szText,
TEXT("Failed to convert RVA to virtual address.\n"));
}
}
else {
wsprintf(szText, TEXT("Failed to retrieve IMAGE_NT_HEADERS.\n"));
}
MessageBox(NULL,szText,szCaption, MB_OK);
return 0;
}
运行:
图3-15 RVA 地址转VA地址
注意
1.注意观察多次在Windows 64位和32位系统下运行时,转换后的VA地址的变化。
2.XP系统使用VC6.0编译时,需要在编译目录C:\Program Files\Microsoft Visual Studio\VC98\Lib和Include中添加dbghelp.lib和dbghelp.h(注意选用i386处理器版本)。如果dbghelp.h头文件出现错误提示,请将其错误行去掉就可以了。
3.ImageRvaToVa函数在64位系统中会得到一个错误的VA地址,ImageRvaToVa函数只能在32位Windows系统中使用。为了兼容64位Windows系统,我们自定义了一个RvaToVa64函数,通过遍历节表的方法,确定RVA地址属于哪个节区,然后通过“节区起始地址=节区虚拟内存起始地址+PE模块基址”的方法获取VA地址。
4.PE文件内获取的基址是静态的基址,64位Windows系统将映像文件加载内存后动态获取的基址为重定位后的基址。
练习
使用汇编代码实现上述功能。可以考虑两种不同的方法实现:
方法1:调用ImageRvaToVa函数(64位系统中错误);
方法2:自己实现ImageRvaToVa函数的功能,兼容32位和64位操作系统;
实验十七:C语言实现将RVA地址转换为FOA地址。
RVA地址转换为FOA地址的方法:
sectionStartVa =sectionHeader->VirtualAddress + ntHeaders->OptionalHeader.ImageBase;
foa = sectionHeader->PointerToRawData(文件中的起始地址) + (rva - sectionStartRva)。
■源代码
/*------------------------------------------------------------------------
FileName:RvaToFoa.c
实验17:Rva地址转换为FOA地址
(c) bcdaren, 2024
-----------------------------------------------------------------------*/
#include <windows.h>
#include <dbghelp.h>
#define MAXSIZE 1024
#pragma comment (lib,"dbghelp")
DWORD RvaToFoa(PIMAGE_NT_HEADERS ntHeaders, DWORD rva) {
// ntHeaders+4+sizeof(IMAGE_FILE_HEADER)+FileHeader.SizeOfOptionalHeader(32或64位PE)
PIMAGE_SECTION_HEADER sectionHeader = IMAGE_FIRST_SECTION(ntHeaders);
WORD numberOfSections = ntHeaders->FileHeader.NumberOfSections;
for (WORD i = 0; i < numberOfSections; i++) {
DWORD sectionStartRva = sectionHeader->VirtualAddress;
DWORD sectionEndRva = sectionStartRva + sectionHeader->SizeOfRawData;
if (rva >= sectionStartRva && rva < sectionEndRva) {
DWORD foa = sectionHeader->PointerToRawData +
(rva - sectionStartRva);
return foa;
}
sectionHeader++;
}
return 0; // RVA not found
}
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
{
const TCHAR szCaption[MAXSIZE] = TEXT("RvaToVa");
static TCHAR szText[MAXSIZE] = { 0 };
HMODULE hModule = GetModuleHandle(NULL); // 获取当前模块的句柄
// 获取模块的 IMAGE_NT_HEADERS 结构
PIMAGE_NT_HEADERS ntHeaders = ImageNtHeader(hModule);
if (ntHeaders) {
// 假设要转换的 RVA 是 0x1000
ULONG rva = 0x1000;
DWORD foa = RvaToFoa(ntHeaders, rva);
if (foa) {
wsprintf(szText,
TEXT("RVA 0x%08X converted to FOA: 0x%08X\n"), rva, foa);
}
else {
wsprintf(szText, TEXT("Failed to convert RVA to FOA.\n"));
}
}
else {
wsprintf(szText, TEXT("Failed to retrieve IMAGE_NT_HEADERS.\n"));
}
MessageBox(NULL, szText, szCaption, MB_OK);
return 0;
}
运行:
图3-16 RVA地址转FOA地址
练习
使用汇编代码实现上述功能。
实验十八:PE文件头中的定位(C语言)
实现对任一PE文件头中几个关键地址的定位:
PEHeader.exe PE标识RVA+模块基地址为:?
PEHeader.exe导入表数据所在VA为:?
PEHeader.exe导入表数据在文件地址FOA为:?
PEHeader.exe第2个节表项在内存的VA地址为:?
PEHeader.exe第2个节表项在文件的偏移为:?
kernel32.dll的校验和为:?
■源代码
pemain.c
/*------------------------------------------------------------------------
FileName:pemain.c
实验18:实现对任一PE文件头中几个关键地址的定位(静态PE文件分析)
(c) bcdaren, 2024
-----------------------------------------------------------------------*/
/*
;实验实现对任一PE文件头中几个关键地址的定位:
*/
#include <windows.h>
#include <richedit.h> //CHARFORMAT富文本结构定义
#include <commctrl.h> //通用控件
#pragma comment(lib,"comctl32.lib")
#include <strsafe.h> //StringCchCopy
#include <stdlib.h>
#include "resource.h"
#include "info.h"
//#define X64 //64位版本
HANDLE hInstance;
HWND hWinMain, hWinEdit;
HMODULE hRichEdit;
TCHAR szFileName[MAX_PATH];
void ShowErrMsg()
{
//TCHAR szBuf[80];
LPVOID lpMsgBuf;
DWORD dw = GetLastError();
FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM,
NULL,
dw,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPTSTR)&lpMsgBuf,
0, NULL);
MessageBox(NULL, lpMsgBuf, L"系统错误", MB_OK | MB_ICONSTOP);
LocalFree(lpMsgBuf);
}
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int nCmdShow)
{
TCHAR szDllEdit[] = TEXT("RichEd20.dll");
TCHAR szClassEdit[] = TEXT("RichEdit20W");//peinfo.rc中定义
hRichEdit = LoadLibrary((LPCWSTR)&szDllEdit);
hInstance = GetModuleHandle(NULL);
DialogBoxParam(hInstance, MAKEINTRESOURCE(DLG_MAIN), NULL,
(DLGPROC)DlgProc, (LPARAM)0);
FreeLibrary(hRichEdit);
return 0;
}
//初始化窗口函数
void init()
{
CHARFORMAT stCf;
TCHAR szClassEdit[] = TEXT("RichEdit20A");
TCHAR szFont[] = TEXT("宋体");
HINSTANCE hInstance;
hWinEdit = GetDlgItem(hWinMain, IDC_INFO);
//为窗口程序设置图标
hInstance = GetModuleHandle(NULL);
HICON hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(ICO_MAIN));
//HICON hIcon = LoadIcon(hInstance, TEXT("#111"));
SendMessage(hWinMain,WM_SETICON,ICON_BIG,(LPARAM)hIcon);
//设置编辑控件
SendMessage(hWinEdit, EM_SETTEXTMODE, TM_PLAINTEXT, 0);
RtlZeroMemory(&stCf, sizeof(stCf));
stCf.cbSize = sizeof(stCf);
stCf.yHeight = 9 * 20;
stCf.dwMask = CFM_FACE | CFM_SIZE | CFM_BOLD;
StringCchCopy((LPTSTR)&stCf.szFaceName, lstrlen(szFont) + 1,
(LPCTSTR)&szFont);
SendMessage(hWinEdit, EM_SETCHARFORMAT, 0, (LPARAM)&stCf);
SendMessage(hWinEdit, EM_EXLIMITTEXT, 0, -1);//设为-1表示无限制
}
//富文本窗口回调函数
BOOL CALLBACK DlgProc(HWND hWnd, UINT wMsg, WPARAM wParam, LPARAM lParam)
{
const TCHAR szErr[] = TEXT("文件格式错误!");
const TCHAR szErrFormat[] = TEXT("这个文件不是PE格式的文件!");
switch (wMsg)
{
case WM_CLOSE:
EndDialog(hWnd,0);
return TRUE;
case WM_INITDIALOG:
hWinMain = hWnd;
init(); //初始化
return TRUE;
case WM_COMMAND:
switch (wParam)
{
case IDM_EXIT:
EndDialog(hWnd,0);
return TRUE;
case IDM_OPEN:
_OpenFile();
return TRUE;
case IDM_1:
MessageBox(NULL,szErrFormat,szErr,MB_ICONWARNING);
return TRUE;
case IDM_2:
MessageBox(NULL, szErrFormat, szErr, MB_ICONWARNING);
return TRUE;
case IDM_3:
MessageBox(NULL, szErrFormat, szErr, MB_ICONWARNING);
return TRUE;
}
}
return FALSE;
}
//执行比对PE文件
void _OpenFile()
{
OPENFILENAME stOF;
HANDLE hFile = NULL;
HANDLE hMapFile = NULL;
PBYTE lpMemory = NULL; //PE文件内存映射文件地址
int dwFileSize;
const TCHAR szDefaultExt[] = TEXT("exe");
const TCHAR szFilter[] = TEXT("PE Files (*.exe)\0*.exe\0")\
TEXT("DLL Files(*.dll)\0*.dll\0")\
TEXT("SCR Files(*.scr)\0*.scr\0")\
TEXT("FON Files(*.fon)\0*.fon\0")\
TEXT("DRV Files(*.drv)\0*.drv\0")\
TEXT("All Files(*.*)\0*.*\0\0");
const TCHAR szErr[] = TEXT("文件格式错误!");
const TCHAR szErrFormat[] = TEXT("操作文件时出现错误!");
const TCHAR szOut1[] = TEXT("\r\nkernel32.dll的校验和为:\t\t%08x\r\n");
const TCHAR szOut[] = TEXT("\r\n地址为:%08x\r\n");
const TCHAR szExeFile[] = TEXT("c:\\windows\\system32\\kernel32.dll");
static TCHAR szBuffer[256];
#ifdef X64
ULONGLONG address;
IMAGE_NT_HEADERS64 *lpstNT; //PE文件头地址
#else
DWORD address;
IMAGE_NT_HEADERS32 *lpstNT; //PE文件头地址
#endif
DWORD dwdwCheckSum1, dwdwCheckSum2;
IMAGE_DOS_HEADER *lpstDOS; //DOS块地址
//显示打开文件对话框
RtlZeroMemory(&stOF, sizeof(stOF));
stOF.lStructSize = sizeof(stOF);
stOF.hwndOwner = hWinMain;
stOF.lpstrFilter = szFilter;
stOF.lpstrFile = szFileName;
stOF.nMaxFile = MAX_PATH;
stOF.Flags = OFN_FILEMUSTEXIST | OFN_PATHMUSTEXIST;
stOF.lpstrDefExt = szDefaultExt;
if (!GetOpenFileName(&stOF))
return;
//打开PE文件
hFile = CreateFile(szFileName,GENERIC_READ,FILE_SHARE_READ |
FILE_SHARE_WRITE,NULL,
OPEN_EXISTING,FILE_ATTRIBUTE_ARCHIVE,NULL);
if (hFile == INVALID_HANDLE_VALUE)
MessageBox(NULL,TEXT("打开文件失败!"),NULL,MB_ICONWARNING);
else
{
//获取文件大小
dwFileSize = GetFileSize(hFile, NULL);
//创建内存映射文件
if (dwFileSize)
{
if (hMapFile = CreateFileMapping(hFile, NULL,
PAGE_READONLY, 0, 0, NULL))
{
//获得文件在内存的映象起始位置
lpMemory = MapViewOfFile(hMapFile, FILE_MAP_READ, 0, 0, 0);
//异常处理方法2:
if (!lpMemory)
{
atexit(Exception);
goto _ErrFormat;
exit(EXIT_FAILURE);
}
//检查PE文件是否有效
lpstDOS = (IMAGE_DOS_HEADER *)lpMemory;
if (lpstDOS->e_magic != IMAGE_DOS_SIGNATURE)
{
//如果非PE文件-异常处理
atexit(Exception);
goto _ErrFormat;
exit(EXIT_FAILURE);
}
lpstNT = (IMAGE_NT_HEADERS *)(lpMemory + lpstDOS->e_lfanew);
if (lpstNT->Signature != IMAGE_NT_SIGNATURE)
{
//如果非PE文件-异常处理
atexit(Exception);
goto _ErrFormat;
exit(EXIT_FAILURE);
}
//定位到PE标识,参数1返回RVA+模块基地址
#ifdef X64
address = (ULONGLONG)_rPE(lpMemory, 1);
wsprintf(szBuffer, L"\r\nPEHeader.exe PE标识RVA+模块基地址
为:\t%016I64X\r\n", address);
#else
address = (DWORD)_rPE(lpMemory, 1);
wsprintf(szBuffer, L"\r\nPEHeader.exe PE标识RVA+模块基地址
为:\t%08x\r\n", address);
#endif
_AppendInfo(szBuffer);
//定位到PE标识,参数0返回FOA地址
#ifdef X64
address = (ULONGLONG)_rPE(lpMemory, 0);
wsprintf(szBuffer, L"\r\nPEHeader.exe PE标识FOA地址
为:\t\t%016I64X\r\n", address);
#else
address = (DWORD)_rPE(lpMemory, 0);
wsprintf(szBuffer, L"\r\nPEHeader.exe PE标识FOA地址
为:\t\t%08x\r\n", address);
#endif
_AppendInfo(szBuffer);
//PEHeader.exe导入表数据所在VA
#ifdef X64
//1表示数据目录项第1项,0表示返回VA地址
address = (ULONGLONG)_rDDEntry(lpMemory,1,0);
wsprintf(szBuffer, L"\r\nPEHeader.exe导入表数据所在VA
为:\t\t%016I64X\r\n", address);
#else
//1表示数据目录项第1项,0表示返回VA地址
address = (DWORD)_rDDEntry(lpMemory, 1, 0);
wsprintf(szBuffer, L"\r\nPEHeader.exe导入表数据所在VA
为:\t\t%08x\r\n", address);
#endif
_AppendInfo(szBuffer);
//PEHeader.exe导入表数据在文件地址FOA
#ifdef X64
//1表示数据目录项第1项,1表示返回FOA地址
address = (ULONGLONG)_rDDEntry(lpMemory,1,1);
wsprintf(szBuffer, L"\r\nPEHeader.exe导入表数据在文件地址FOA
为:\t%016I64X\r\n", address);
#else
//1表示数据目录项第1项,1表示返回FOA地址
address = (DWORD)_rDDEntry(lpMemory, 1, 1);
wsprintf(szBuffer, L"\r\nPEHeader.exe导入表数据在文件地址FOA
为:\t%08x\r\n", address);
#endif
_AppendInfo(szBuffer);
//PEHeader.exe第2个节表项在内存的VA地址
#ifdef X64
address = (ULONGLONG)_rSection(lpMemory, 2, 0);
wsprintf(szBuffer, L"\r\nPEHeader.exe第2个节表项在内存的VA地址
为:%016I64X\r\n", address);
#else
address = (DWORD)_rSection(lpMemory,2,0);
wsprintf(szBuffer, L"\r\nPEHeader.exe第2个节表项在内存的VA地址
为:%08x\r\n", address);
#endif
_AppendInfo(szBuffer);
//PEHeader.exe第2个节表项在文件的偏移
#ifdef X64
address = (ULONGLONG)_rSection(lpMemory, 2, 1);
wsprintf(szBuffer, L"\r\nPEHeader.exe第2个节表项在文件的偏移
为:\t%016x\r\n", address);
#else
address = (DWORD)_rSection(lpMemory, 2, 1);
wsprintf(szBuffer, L"\r\nPEHeader.exe第2个节表项在文件的偏移
为:\t%08x\r\n", address);
#endif
_AppendInfo(szBuffer);
//计算校验和
dwdwCheckSum1 = _checkSum1(szExeFile);
dwdwCheckSum2 = _checkSum2(szExeFile);
if (dwdwCheckSum1 == dwdwCheckSum2)
{
wsprintf(szBuffer, szOut1, dwdwCheckSum1);
_AppendInfo(szBuffer);
}
//错误检查
//ShowErrMsg();
goto _ErrorExit;
_ErrFormat:
MessageBox(hWinMain,szErrFormat,NULL,MB_OK);
_ErrorExit:
if(lpMemory)
UnmapViewOfFile(lpMemory);
}
if(hMapFile)
CloseHandle(hMapFile);
}
if (hFile)
CloseHandle(hFile);
}
return;
}
//RITCH控件添加文本信息
void _AppendInfo(const TCHAR * _lpsz)
{
CHARRANGE stCR;
DWORD dwPos = 0;
RtlZeroMemory(&stCR,sizeof(stCR));
dwPos = GetWindowTextLength(hWinEdit);//返回文本控件中字符个数
stCR.cpMin = dwPos;
stCR.cpMax = dwPos;
//择并替换文本控件的选定内容
SendMessage(hWinEdit, EM_EXSETSEL, 0, (LPARAM)&stCR);
SendMessage(hWinEdit, EM_REPLACESEL, FALSE, (LPARAM)_lpsz);
}
void _Init()
{
CHARFORMAT stCF;
static TCHAR szClassEdit[] = TEXT("RichEdit20W"); //UNICODE版本
//static TCHAR szClassEdit[] = TEXT("RichEdit20A"); //ASCII码版本
static TCHAR szFont[] = TEXT("宋体");
hWinEdit = GetDlgItem(hWinMain, IDC_INFO);
SendMessage(hWinMain, EM_SETTEXTMODE, TM_PLAINTEXT, 0);
RtlZeroMemory(&stCF, sizeof(stCF));
stCF.cbSize = sizeof(stCF);
stCF.yHeight = 9 * 20;
stCF.dwMask = (CFM_FACE | CFM_SIZE | CFM_BOLD);
lstrcpy(stCF.szFaceName, szFont);
SendMessage(hWinEdit, EM_SETCHARFORMAT, 0, (LPARAM)&stCF);
SendMessage(hWinEdit, EM_EXLIMITTEXT, 0, -1);
}
//异常处理
void Exception(void)
{
MessageBox(hWinMain, TEXT("获得文件在内存的映象起始位置失败或非PE格式文
件!"), NULL, MB_OK);
}
GetPE.c
#include <windows.h>
//#define X64 //64位版本
/*
;-------------------
; 定位到PE标识
; _lpHeader 头部基地址
; _dwFlag
; 为0表示_lpHeader是PE映像文件头
; 为1表示_lpHeader是内存映射文件头
;-------------------
*/
#ifndef X64
DWORD _rPE(PBYTE _lpHeader, int _dwFlag)
{
//x86版本
DWORD retAddress, imageBase;
IMAGE_DOS_HEADER * lpstDOS;
IMAGE_NT_HEADERS32 * lpstNT;
lpstDOS = (IMAGE_DOS_HEADER *)_lpHeader;
lpstNT = (IMAGE_NT_HEADERS32 *)(_lpHeader + lpstDOS->e_lfanew);
imageBase = lpstNT->OptionalHeader.ImageBase; //程序的建议装载地址
if (_dwFlag == 1)//VA
retAddress = (DWORD)lpstNT - (DWORD)_lpHeader + imageBase;
else//FOA
retAddress = (DWORD)lpstNT - (DWORD)_lpHeader;
return retAddress;
}
#else//x64版本
ULONGLONG _rPE(PBYTE _lpHeader, int _dwFlag)
{
ULONGLONG retAddress, imageBase;
IMAGE_DOS_HEADER * lpstDOS;
IMAGE_NT_HEADERS64 * lpstNT;
lpstDOS = (IMAGE_DOS_HEADER *)_lpHeader;
lpstNT = (IMAGE_NT_HEADERS64 *)(_lpHeader + lpstDOS->e_lfanew);
imageBase = lpstNT->OptionalHeader.ImageBase; //程序的建议装载地址
if (_dwFlag == 1)//VA
retAddress = (ULONGLONG)lpstNT - (ULONGLONG)_lpHeader + imageBase;
else//FOA
retAddress = (ULONGLONG)lpstNT - (ULONGLONG)_lpHeader;
return retAddress;
}
#endif
GetImport.c
#include <windows.h>
#include "info.h"
//#define X64 -1
/*
;-------------------
; 定位到指定索引的数据目录项所在数据的起始地址
; _lpHeader PE映像文件头基址
; _index 数据目录表索引,从0开始
; _dwFlag
; 为0表示返回VA地址:RVA+映像基址
; 为1表示返回FOA地址:节区文件基址
;-------------------
*/
#ifdef X64
ULONGLONG _rDDEntry(PBYTE _lpHeader, int _index, int _dwFlag)
{
ULONGLONG retAddress, rva, imageBase;
IMAGE_DOS_HEADER * lpstDOS;
IMAGE_NT_HEADERS64 * lpstNT;
lpstDOS = (IMAGE_DOS_HEADER *)_lpHeader;
lpstNT = (IMAGE_NT_HEADERS64 *)(_lpHeader + lpstDOS->e_lfanew);
imageBase = lpstNT->OptionalHeader.ImageBase; //程序的建议装载地址
rva = lpstNT->OptionalHeader.DataDirectory[_index].VirtualAddress;//RVA
if (_dwFlag == 0) //VA地址
{
PIMAGE_SECTION_HEADER sectionHeader = IMAGE_FIRST_SECTION(lpstNT);
retAddress = imageBase + rva;
}
else //FOA地址
{
retAddress = RVAToOffset((IMAGE_DOS_HEADER *)_lpHeader, rva);
}
return retAddress;
}
#else
DWORD _rDDEntry(PBYTE _lpHeader, int _index, int _dwFlag)
{
DWORD retAddress, rva, imageBase;
IMAGE_DOS_HEADER * lpstDOS;
IMAGE_NT_HEADERS32 * lpstNT;
lpstDOS = (IMAGE_DOS_HEADER *)_lpHeader;
lpstNT = (IMAGE_NT_HEADERS32 *)(_lpHeader + lpstDOS->e_lfanew);
imageBase = lpstNT->OptionalHeader.ImageBase; //程序的建议装载地址
rva = lpstNT->OptionalHeader.DataDirectory[_index].VirtualAddress;//RVA
if (_dwFlag == 0) //VA地址
{
PIMAGE_SECTION_HEADER sectionHeader = IMAGE_FIRST_SECTION(lpstNT);
retAddress = imageBase + rva;
}
else //FOA地址
{
retAddress = RVAToOffset((IMAGE_DOS_HEADER *)_lpHeader, rva);
}
return retAddress;
}
#endif
GetSection.c
#include <windows.h>
#include "info.h"
//#define X64
/*
;-------------------
; 定位到指定索引的节表项
; _lpHeader 头部基地址
; _index 表示第几个节表项,从0开始
; _dwFlag
; 为0表示节表VA地址
; 为1表示节表FOA地址
;-------------------
*/
#ifdef X64
ULONGLONG _rSection(PBYTE _lpHeader, int _index, int _dwFlag)
{
ULONGLONG retAddress, imageBase;
IMAGE_DOS_HEADER * lpstDOS;
IMAGE_NT_HEADERS64 * lpstNT;
IMAGE_SECTION_HEADER * lpdtSECT;
lpstDOS = (IMAGE_DOS_HEADER *)_lpHeader;
lpstNT = (IMAGE_NT_HEADERS64 *)(_lpHeader + lpstDOS->e_lfanew);
imageBase = lpstNT->OptionalHeader.ImageBase; //程序的建议装载地址
lpdtSECT = (IMAGE_SECTION_HEADER *)((ULONGLONG)lpstNT + sizeof(IMAGE_NT_HEADERS64));//节表地址
//节表索引项所在的地址
retAddress = (ULONGLONG)lpdtSECT + (ULONGLONG)(_index * 40);
if (_dwFlag == 0)
{
//VA
return retAddress - (ULONGLONG)_lpHeader + imageBase;
}
else
{
//FOA
return retAddress - (ULONGLONG)_lpHeader;
}
}
#else
DWORD _rSection(PBYTE _lpHeader, int _index, int _dwFlag)
{
DWORD retAddress, imageBase;
IMAGE_DOS_HEADER * lpstDOS;
IMAGE_NT_HEADERS * lpstNT;
IMAGE_SECTION_HEADER * lpdtSECT;
lpstDOS = (IMAGE_DOS_HEADER *)_lpHeader;
lpstNT = (IMAGE_NT_HEADERS *)(_lpHeader + lpstDOS->e_lfanew);
imageBase = lpstNT->OptionalHeader.ImageBase; //程序的建议装载地址
lpdtSECT = (IMAGE_SECTION_HEADER *)((DWORD)lpstNT +
sizeof(IMAGE_NT_HEADERS));//节表地址
retAddress = (DWORD)lpdtSECT + (DWORD)(_index * 40);//节表索引项所在的地址
if (_dwFlag == 0)
{
//VA
return retAddress - (DWORD)_lpHeader + imageBase;
}
else
{
//FOA
return retAddress - (DWORD)_lpHeader;
}
}
#endif
CheckSum.c
#include <windows.h>
#include <imagehlp.h>
#pragma comment(lib,"imagehlp.lib")
#include "info.h"
/*
;---------------------------------
; 通过调用API函数计算校验和
; 32位kernel32.dll的校验和为:0009d206
;---------------------------------
*/
//计算校验和方法1:通过调用API函数计算校验和
DWORD _checkSum1(PCWSTR _lpExeFile)
{
DWORD cSum,hSum,retAddress;
retAddress = MapFileAndCheckSumW(_lpExeFile,&hSum,&cSum);
return cSum;
}
// 计算指定地址处字校验和
unsigned short calculateWordChecksum(PBYTE address, size_t size)
{
unsigned int sum = 0;
unsigned short checksum = 0;
for (size_t i = 0; i < size; i += 2) {
sum += *(unsigned short*)(address + i);
if (sum >> 16 > 0)
{
sum = sum & 0x0000ffff;//高16位清零
sum = sum + 1;//1为进位值
}
else
{
sum = sum & 0x0000ffff;//高16位清零
}
}
checksum = (unsigned short)sum;
return checksum;
}
//计算校验和方法2:自己编写程序计算校验和
DWORD _checkSum2(PCWSTR _lpExeFile)
{
PBYTE hBase;
HANDLE hFile;
DWORD dwSize;
DWORD size;
unsigned short sum = 0;
IMAGE_DOS_HEADER * lpstDOS;
IMAGE_NT_HEADERS * lpstNT;
hFile = CreateFile(_lpExeFile,GENERIC_READ,
FILE_SHARE_READ,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,0);
dwSize = GetFileSize(hFile, NULL);
//为文件分配内存,并读入
hBase = VirtualAlloc(NULL, dwSize, MEM_COMMIT, PAGE_READWRITE);
ReadFile(hFile, hBase, dwSize, &size, NULL);
//关闭文件
CloseHandle(hFile);
//第一步,将CheckSum清零
lpstDOS = (IMAGE_DOS_HEADER *)hBase;
lpstNT = (IMAGE_NT_HEADERS *)(hBase + lpstDOS->e_lfanew);
lpstNT->OptionalHeader.CheckSum = 0;
//第二步,按字进位加,需要加进位值
sum = calculateWordChecksum(hBase, dwSize);
/*
int i = (dwSize + 1) / 2;
//错误:原因,没有忽略溢出忽略了进位值
while (i--)
{
sum += *(PWORD)hBase;
hBase += 2;
}
//内联汇编
_asm
{
mov esi,hBase
mov ecx,i
xor ebx,ebx
clc
loc1:
lodsw
adc bx,ax
loop loc1
mov sum,bx
}*/
VirtualFree(hBase, dwSize, MEM_DECOMMIT);
//第三步,加文件长度
return sum + (DWORD)dwSize;
}
RvaToFileOffset.c
/*
1.将 RVA 转换成FOA文件偏移地址
2.查找 RVA 所在的节区
*/
#include <windows.h>
//#define X64
extern HANDLE hWinEdit;
extern HWND hWinMain;
const TCHAR szNotFound[] = TEXT("无法查找");
#ifdef X64
//将 RVA 转换成FOA文件偏移地址
ULONGLONG RVAToOffset(IMAGE_DOS_HEADER * lpFileHead, ULONGLONG dwRVA)
{
ULONGLONG dwReturn;
IMAGE_NT_HEADERS64 * lpstNT; //PE文件头
IMAGE_SECTION_HEADER * lpstSE; //节表,8个字节的节区名称为ASCII码字符
IMAGE_DOS_HEADER * lpstDOS;
lpstDOS = lpFileHead;
lpstNT = (IMAGE_NT_HEADERS64 *)((PBYTE)lpstDOS + lpstDOS->e_lfanew); //PE文件头地址
lpstSE = (IMAGE_SECTION_HEADER *)((PBYTE)lpstNT + sizeof(IMAGE_NT_HEADERS64));//节表地址
int count = lpstNT->FileHeader.NumberOfSections; //节区数目
//扫描每个节区并判断 RVA 是否位于这个节区内
for (int i = 0; i < count; i++)
{
if ((dwRVA >= lpstSE->VirtualAddress) && //节区的RVA地址
(dwRVA < (lpstSE->VirtualAddress + lpstSE->SizeOfRawData))) //在文件中对齐后的尺寸
{
//实际位置=在文件内的偏移+节区偏移 ---(节区偏移=dwRVA-节区的RVA地址)
dwReturn = (lpstSE->PointerToRawData + (dwRVA –
lpstSE->VirtualAddress));
return dwReturn;
}
lpstSE++;
}
MessageBox(hWinEdit, szNotFound, NULL, MB_OK);
return dwReturn = 0;
}
//查找 RVA 所在的节区
/*
根据导入表描述符桥1所在的地址区间判断所属的节区
dwRVA = pstIM->OriginalFirstThunk
dwRVA >= lpstSE->VirtualAddress &&
dwRVA < (lpstSE->VirtualAddress + lpstSE->SizeOfRawData)
*/
ULONGLONG GetRVASection(IMAGE_DOS_HEADER * lpFileHead, DWORD dwRVA)
{
ULONGLONG dwReturn;
IMAGE_NT_HEADERS64 * lpstNT; //PE文件头
IMAGE_SECTION_HEADER * lpstSE; //节表,8个字节的节区名称为ASCII码字符
IMAGE_DOS_HEADER * lpstDOS;
const TCHAR szNotFound[] = TEXT("无法查找");
lpstDOS = lpFileHead;
lpstNT = (IMAGE_NT_HEADERS64 *)((PBYTE)lpstDOS + lpstDOS->e_lfanew); //PE文件头地址
lpstSE = (IMAGE_SECTION_HEADER *)((PBYTE)lpstNT +
sizeof(IMAGE_NT_HEADERS64));//节表地址
int count = lpstNT->FileHeader.NumberOfSections; //节区数目
//扫描每个节区并判断 RVA 是否位于这个节区内
for (int i = 0; i < count; i++)
{
if ((dwRVA >= lpstSE->VirtualAddress) &&
(dwRVA < (lpstSE->VirtualAddress + lpstSE->SizeOfRawData)))
{
//实际位置=在文件内的偏移+RVA虚拟地址+节区偏移
dwReturn = (ULONGLONG)lpstSE;
return dwReturn;
}
lpstSE++;
}
MessageBox(hWinEdit, szNotFound, NULL, MB_OK);
return dwReturn = 0;
}
#else
//将 RVA 转换成FOA文件偏移地址
DWORD RVAToOffset(IMAGE_DOS_HEADER * lpFileHead, DWORD dwRVA)
{
DWORD dwReturn;
IMAGE_NT_HEADERS * lpstNT; //PE文件头
IMAGE_SECTION_HEADER * lpstSE; //节表,8个字节的节区名称为ASCII码字符
IMAGE_DOS_HEADER * lpstDOS;
lpstDOS = lpFileHead;
lpstNT = (IMAGE_NT_HEADERS *)((PBYTE)lpstDOS + lpstDOS->e_lfanew); //PE文件头地址
lpstSE = (IMAGE_SECTION_HEADER *)((PBYTE)lpstNT +
sizeof(IMAGE_NT_HEADERS));//节表地址
int count = lpstNT->FileHeader.NumberOfSections; //节区数目
//扫描每个节区并判断 RVA 是否位于这个节区内
for (int i = 0; i < count; i++)
{
if ((dwRVA >= lpstSE->VirtualAddress) &&//节区的RVA地址
(dwRVA < (lpstSE->VirtualAddress + lpstSE->SizeOfRawData))) //在文件中对齐后的尺寸
{
//实际位置=在文件内的偏移+节区偏移 ---(节区偏移=dwRVA-节区的RVA地址)
dwReturn = (DWORD)(lpstSE->PointerToRawData +
(dwRVA - lpstSE->VirtualAddress));
return dwReturn;
}
lpstSE++;
}
MessageBox(hWinEdit, szNotFound, NULL, MB_OK);
return dwReturn = 0;
}
//查找 RVA 所在的节区
DWORD GetRVASection(IMAGE_DOS_HEADER * lpFileHead, DWORD dwRVA)
{
DWORD dwReturn;
IMAGE_NT_HEADERS * lpstNT; //PE文件头
IMAGE_SECTION_HEADER * lpstSE; //节表,8个字节的节区名称为ASCII码字符
IMAGE_DOS_HEADER * lpstDOS;
lpstDOS = lpFileHead;
lpstNT = (IMAGE_NT_HEADERS *)((PBYTE)lpstDOS + lpstDOS->e_lfanew); //PE文件头地址
lpstSE = (IMAGE_SECTION_HEADER *)((PBYTE)lpstNT + sizeof(IMAGE_NT_HEADERS));//节表地址
int count = lpstNT->FileHeader.NumberOfSections; //节区数目
//扫描每个节区并判断 RVA 是否位于这个节区内
for (int i = 0; i < count; i++)
{
if ((dwRVA >= lpstSE->VirtualAddress) &&
(dwRVA < (lpstSE->VirtualAddress + lpstSE->SizeOfRawData)))
{
//实际位置=在文件内的偏移+RVA虚拟地址+节区偏移
dwReturn = (DWORD)lpstSE;
return dwReturn;
}
lpstSE++;
}
MessageBox(hWinEdit, szNotFound, NULL, MB_OK);
return dwReturn = 0;
}
#endif
info.h
#pragma once
#ifndef INFO_H_
#define INFO_H_
//#define X64 //64位版本
//函数声明
BOOL CALLBACK DlgProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
void Exception(void);
void init(); //初始化
void _OpenFile();//打开PE文件并处理
// 将内存偏移量RVA转换为文件偏移
DWORD RVAToOffset(IMAGE_DOS_HEADER * lpFileHead, DWORD dwRVA);
//查找 RVA 所在的节区
DWORD GetRVASection(IMAGE_DOS_HEADER * lpFileHead, DWORD dwRVA);
void ShowErrMsg();
void _AppendInfo(const TCHAR * _lpsz);//往文本框中追加文本
#ifdef X64
//PE文件处理模块
ULONGLONG _rPE(PBYTE, int);//获取PE头标识所在内存地址VA和文件地址FOA
//获取PE文件的导入表数据所在内存地址VA和文件地址FO
ULONGLONG _rDDEntry(PBYTE, int, int);
//获取PE文件的第2个节表项在内存的VA地址和在文件的偏移
ULONGLONG _rSection(PBYTE, int, int);
DWORD _checkSum1(PCWSTR);//计算校验和方法1
DWORD _checkSum2(PCWSTR);//计算校验和方法2
#else
//PE文件处理模块
DWORD _rPE(PBYTE, int);//获取PE头标识所在内存地址VA和文件地址FOA
//获取PE文件的导入表数据所在内存地址VA和文件地址FO
DWORD _rDDEntry(PBYTE, int, int);
//获取PE文件的第2个节表项在内存的VA地址和在文件的偏移
DWORD _rSection(PBYTE, int, int);
DWORD _checkSum1(PCWSTR);//计算校验和方法1
DWORD _checkSum2(PCWSTR);//计算校验和方法2
#endif
#endif
resource.h
//{{NO_DEPENDENCIES}}
// Microsoft Visual C++ 生成的包含文件。
// 供 peinfo.rc 使用
//
#define ICO_MAIN 111
#define DLG_MAIN 1000
#define IDC_INFO 1001
#define IDM_MAIN 2000
#define IDM_OPEN 2001
#define IDM_EXIT 2002
#define IDM_1 4000
#define IDM_2 4001
#define IDM_3 4002
// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE 113
#define _APS_NEXT_COMMAND_VALUE 4003
#define _APS_NEXT_CONTROL_VALUE 1002
#define _APS_NEXT_SYMED_VALUE 101
#endif
#endif
peinfo.rc
// Microsoft Visual C++ generated resource script.
//
#include "resource.h"
#define APSTUDIO_READONLY_SYMBOLS
/
//
// Generated from the TEXTINCLUDE 2 resource.
//
#include "winres.h"
/
#undef APSTUDIO_READONLY_SYMBOLS
/
// 中文(简体,中国) resources
#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_CHS)
LANGUAGE LANG_CHINESE, SUBLANG_CHINESE_SIMPLIFIED
#ifdef APSTUDIO_INVOKED
/
//
// TEXTINCLUDE
//
1 TEXTINCLUDE
BEGIN
"resource.h\0"
END
2 TEXTINCLUDE
BEGIN
"#include ""winres.h""\r\n"
"\0"
END
3 TEXTINCLUDE
BEGIN
"\r\n"
"\0"
END
#endif // APSTUDIO_INVOKED
/
//
// Dialog
//
DLG_MAIN DIALOGEX 50, 50, 427, 300
STYLE DS_SETFONT | DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
CAPTION "PE文件头中几个关键地址的定位:"
MENU IDM_MAIN
FONT 9, "宋体", 0, 0, 0x0
BEGIN
CONTROL "",IDC_INFO,"RichEdit20W",ES_MULTILINE | ES_NOHIDESEL | ES_READONLY | ES_WANTRETURN | WS_BORDER | WS_VSCROLL | WS_HSCROLL | WS_TABSTOP,7,7,411,280,WS_EX_ACCEPTFILES
END
/
//
// DESIGNINFO
//
#ifdef APSTUDIO_INVOKED
GUIDELINES DESIGNINFO
BEGIN
DLG_MAIN, DIALOG
BEGIN
LEFTMARGIN, 7
TOPMARGIN, 7
END
END
#endif // APSTUDIO_INVOKED
/
//
// AFX_DIALOG_LAYOUT
//
DLG_MAIN AFX_DIALOG_LAYOUT
BEGIN
0
END
RESULT_MODULE AFX_DIALOG_LAYOUT
BEGIN
0
END
/
//
// Menu
//
IDM_MAIN MENU
BEGIN
POPUP "文件(&F)"
BEGIN
MENUITEM "打开文件(&O)...", IDM_OPEN
MENUITEM SEPARATOR
MENUITEM "退出(&x)", IDM_EXIT
END
POPUP "编辑(&E)"
BEGIN
MENUITEM SEPARATOR
END
POPUP "格式(&O)"
BEGIN
MENUITEM SEPARATOR
END
POPUP "查看(&V)"
BEGIN
MENUITEM "源文件", IDM_1
MENUITEM "窗口透明度", IDM_2
MENUITEM SEPARATOR
MENUITEM "大小", IDM_3
END
POPUP "帮助(&H)"
BEGIN
MENUITEM SEPARATOR
END
END
/
//
// Icon
//
// Icon with lowest ID value placed first to ensure application icon
// remains consistent on all systems.
ICO_MAIN ICON "main.ico"
#endif // 中文(简体,中国) resources
/
#ifndef APSTUDIO_INVOKED
/
//
// Generated from the TEXTINCLUDE 3 resource.
//
/
#endif // not APSTUDIO_INVOKED
运行:
图3-17 PE文件头关键字段
总结
实验十八使用条件编译语句,编写了x64和x86两个不同的版本。分别读取63位PE文件和32位PE文件的PE特征码、导入表、指定节表项在内存中的VA地址和在静态PE文件中的FOA地址。
【注意】32位PE文件和64位PE文件的差异在于IMAGE_NT_HEADERS64和IMAGE_NT_HEADERS32结构体的区别。即NT头内的扩展头的大小以及32位地址和64位地址的区别。
练习
1.将实验十八C语言代码改为汇编代码实现。
2.在WinHex中,给HelloWorld.exe程序的PE头部各个结构体字段添加注释信息,如图3-18所示。
图3-18 使用WinHex给PE文件添加注释
添加注释方法:
- 鼠标选定要添加的字段,点击鼠标右键,选中菜单“Add Bookmark”。
- 在弹出的注释对话框左侧栏填写注释信息。
- 在右侧栏“Color:”选项选择颜色。建议不同的字段选择不同的颜色,方便区分。
- Offset和Length文本框可以手动修改注释开始的文件偏移地址和长度。
2.对比分析在Windows 64位系统中编译生成的HelloWorld.exe程序(包括汇编版本和C语言版本)和在Windows XP系统中编译生成的HelloWorld.exe程序(包括汇编版本和C语言版本)的PE文件NT头有什么区别?