《Windows PE》3.2.4节表

节表由多个节表项(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 /guardcf test.cpp /link /guardcf

还可以选择使用内存管理 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.exeCFG下的二进制文件的输出应显示标头值包括“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文件添加注释

       添加注释方法:

  1. 鼠标选定要添加的字段,点击鼠标右键,选中菜单“Add Bookmark”。
  2. 在弹出的注释对话框左侧栏填写注释信息。
  3. 在右侧栏“Color:”选项选择颜色。建议不同的字段选择不同的颜色,方便区分。
  4. Offset和Length文本框可以手动修改注释开始的文件偏移地址和长度。

2.对比分析在Windows 64位系统中编译生成的HelloWorld.exe程序(包括汇编版本和C语言版本)和在Windows XP系统中编译生成的HelloWorld.exe程序(包括汇编版本和C语言版本)的PE文件NT头有什么区别?

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/439468.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

防止错误输入!Excel单元格限制输入内容的三种有效方式

在Excel中&#xff0c;限制单元格输入内容可以帮助避免数据输入错误&#xff0c;确保数据的一致性和准确性。今天小编分享三种方法&#xff0c;可以轻松限制Excel单元格的输入内容&#xff0c;确保数据输入符合预期要求&#xff0c;一起来看看吧&#xff01; 方法一&#xff1a…

el-pagination组件封装

组件使用 源代码&#xff1a; <script setup> import Pagination from /components/pagination/index.vue import {ref} from "vue";const pageNum ref(1) const pageSize ref(10) const total ref(120)function loadData() {// 加载数据 } </script>…

[云] Hands-on with a sample application--DockerCoins 挖矿程序!

DockerCoins 挖矿程序&#xff01;&#x1f4b0;&#x1f433;&#x1f4e6;&#x1f6a2; 不&#xff0c;你不能用 DockerCoins 买咖啡。 DockerCoins 如何工作&#xff1a; 生成一些随机字节&#xff1a; 程序首先生成一串随机的字节数据。这些随机字节用于模拟挖矿过程中的…

R语言绘制散点图

散点图是一种在直角坐标系中用数据点直观呈现两个变量之间关系、可检测异常值并探索数据分布的可视化图表。它是一种常用的数据可视化工具&#xff0c;我们通过不同的参数调整和包的使用&#xff0c;可以创建出满足各种需求的散点图。 常用绘制散点图的函数有plot()函数和ggpl…

算法专题三: 二分查找

目录 1. 朴素版: 二分查找2. 查找排序数组元素第一个和最后一个位置3. 搜索插入位置4. x的平方根5. 山脉数组的峰顶索引6. 寻找旋转数组中的最小值7. 点名 博客主页: 酷酷学!!! 感谢您的关注~ 正文开始 1. 朴素版: 二分查找 题目思路: 仅需根据题意, 找出二段性, 正确更新下标…

躺平成长:微信小程序运营日记第二天

在进行属于生活的开源之后&#xff0c;自己更加感受到自己存在的渺茫&#xff0c;同时更加开始深刻领会&#xff0c;开源的重要性&#xff0c;在开源&#xff0c;开放&#xff0c;创造&#xff0c;再创新的思维模式下&#xff0c;不发布八部金刚功相关的训练视频&#xff0c;自…

课设实验-数据结构-线性表-手机销售

题目&#xff1a; 代码&#xff1a; #include<stdio.h> #include<string.h> #define MaxSize 10 //定义顺序表最大长度 //定义手机结构体类型 typedef struct {char PMod[10];//手机型号int PPri;//价格int PNum;//库存量 }PhoType; //手机类型 //记录手机的顺序…

如何快速切换电脑的ip地址

在当今的数字化时代&#xff0c;IP地址作为网络身份的重要标识&#xff0c;其重要性日益凸显。无论是出于保护个人隐私的需要&#xff0c;还是为了访问特定的网络服务等&#xff0c;快速切换电脑的IP地址已成为许多用户的迫切需求。本文将为你介绍几种实用的方法&#xff0c;帮…

草莓成熟度检测数据集 3700张 草莓成熟 带标注voc yolo 3类

草莓成熟度检测数据集 3700张 草莓成熟 带标注voc yolo 草莓成熟度检测数据集 名称 草莓成熟度检测数据集 (Strawberry Maturity Detection Dataset) 规模 图像数量&#xff1a;共3713张图像。类别&#xff1a;分为三个级别&#xff1a;未熟 (raw)、半熟 (turning) 和 成熟…

01_SQLite

文章目录 ** SQLite 存储各类和数据类型 **** SQLite 五种亲缘类型** SQLite 创建数据表删除数据表插入数据信息从数据表中获取数据&#xff0c;以结果表的形式返回数据&#xff08;结果集&#xff09;updatedistinctorder bygroup byhaving触发器删除一个触发器&#xff08;tr…

软件设计师——数据结构

本博文所有内容来自于B站up主zst_2001 目录 时间复杂度 常规数据结构 链表 栈与队列 ​编辑 串 数组 树 卡特兰数&#xff1a; 平衡二叉树 哈夫曼 图 AOV 排序 顺序 折半 哈希 时间复杂度 常规数据结构 链表 栈与队列 串 找i位置前面的字符串&#xff0c…

TIM输入捕获及其应用场景

一&#xff0c;TIM输入捕获介绍&#xff08;IC&#xff08;Input Capture&#xff09;输入捕获&#xff09; 定义&#xff1a;输入捕获模式下&#xff0c;当通道输入引脚出现指定电平跳变&#xff08;如上升沿或下降沿&#xff09;时&#xff0c;当前定时器的计数值&#xff0…

【Matlab案例】imageJ + matlab 实现物体轨迹追踪及路径彩色上色

我们经常看到一些文献中对细胞或者粒子的运动轨迹进行上色&#xff0c;不同的颜色对应着不同的时间。一纯色的轨迹实现起来很方便&#xff0c;彩色的轨迹如何实现呢&#xff1f;本文使用imageJ获取轨迹数据&#xff0c;使用matlab对轨迹进行上色。结果如下&#xff1a; 1. im…

酒店新科技,飞睿智能毫米波雷达人体存在感应器,智能照明创新节能新风尚

在这个日新月异的时代&#xff0c;科技正以未有的速度改变着我们的生活。从智能手机到智能家居&#xff0c;每一个细微之处都渗透着科技的魅力。而今&#xff0c;这股科技浪潮已经席卷到了酒店行业&#xff0c;为传统的住宿体验带来了翻天覆地的变化。其中&#xff0c;引人注目…

Linux驱动开发(速记版)--设备树

第五十二章 初识设备树 52.1 设备树介绍 设备树&#xff08;Device Tree&#xff09;是嵌入式系统和Linux内核中用于描述硬件的一种机制。 设备树概述 目的&#xff1a;描述硬件设备的特性、连接关系和配置信息。 优势&#xff1a;与平台无关&#xff0c;提高系统可移植性和可…

外贸网站怎么搭建对谷歌seo比较好?

外贸网站怎么搭建对谷歌seo比较好&#xff1f;搭建一个网站自然不复杂&#xff0c;但要想搭建一个符合谷歌seo规范的网站&#xff0c;那就要多注意了&#xff0c;你的网站做的再酷炫&#xff0c;再花里胡哨&#xff0c;但如果页面都是js代码&#xff0c;或者页面没有源代码内容…

相机基础概念

景深&#xff1a; 景深的定义 DOF:depth of filed 是指在摄影机镜头或其他成像器前沿能够取得清晰图像的成像所测定的被摄物体前后距离范围。光圈、镜头、及焦平面到拍摄物的距离是影响景深的重要因素。定义3&#xff1a;在镜头前方&#xff08;焦点的前、后&#xff09;有一…

【RISCV指令集手册】向量扩展v1.0

概述 从rvv 0.9说起 此前写过向量扩展0.9的阅读记录&#xff0c;三年已过&#xff0c;本以为不再参与RVV的相关开发&#xff0c;奈何造化弄人&#xff0c;旧业重操&#xff0c;真就世事难料呀。 总的来说1.0版本相比0.9版本的扩充了较多内容&#xff0c;但大部分为指令功能的…

Qt中使用QPainter绘制阴影

困扰了很久的问题&#xff0c;今天终于明白了如何绘制QGraphicDropShadowEffect同样效果的阴影&#xff0c;故写下这篇文章分享给大家。其方法是复制Qt源代码中QGraphicDropShadowEffect绘制实现的核心代码然后稍作修改实现&#xff0c;先看效果和封装过后的源代码&#xff1a;…

深度探索Kali Linux的精髓与实践应用

Kali Linux简介 Kali Linux作为全球网络安全领域的首选操作系统之一&#xff0c;其强大的功能性及广泛的适用范围令人瞩目。除了上述基础介绍外&#xff0c;让我们深入探究Kali Linux的几个关键特性及其在实际操作中的具体应用案例。 Kali工具集成&#xff1a;全面的安全工具…