PE文件结构:节表

admin 2024年12月16日15:30:35评论39 views字数 8377阅读27分55秒阅读模式

在 PE(Portable Executable)文件结构中,节表(Section Table)是 PE 文件的重要组成部分之一,位于 PE 头之后。节表记录了各节的虚拟地址、大小、节在 PE 文件中的实际大小和节属性等内容,用于定义程序在内存中的逻辑布局,操作系统加载 PE 文件时,根据节表将不同节的数据映射到正确的内存区域;定义了各节的属性,如是否可执行、可读、可写等,操作系统会根据这些属性对相应内存区域进行保护。在调试过程中,节表可以用来定位代码和数据在文件和内存中的位置,辅助调试器解析程序结构。

节:每个节实际上是一个容器,可以包含代码、数据等等,每个节可以有独立的内存权限,比如代码节默认有读/执行权限,节的名字和数量可以自己定义,未必是上图中的三个。

数据映射

PE 文件是一种存储在磁盘上的静态格式,而程序需要在内存中以动态形式运行。将节映射到内存中可以将代码、数据、以及资源加载到合适的内存地址,以便 CPU 和操作系统能够访问并执行这些内容。

当一个 PE 文件被加载到内存中以后,我们称之为"映象"(image)。

代码节(.text):包含指令代码,必须加载到内存中供 CPU 执行。
数据节(.data, .bss):包含全局变量、静态变量等,需要在内存中分配地址以供程序访问。
资源节(.rsrc):包含图标、字符串等资源,运行时需要内存访问这些资源。
PE文件结构:节表

节表的数量和位置

NT头部中存在一个名叫NumberOfSections的字段(如果对NT头部内容比较陌生可以看笔者的PE系列的上一篇文章《PE文件结构:NT头部》),该字段就记录了程序的节(表)数量:

PE文件结构:节表

以该程序为例子,我们从NT头部得知该程序包含了9个节(每个节都有自己对应的节表),这个时候可以将画面拉到NT头部结尾;这个时候就能看到紧跟在NT头部后面的9个节表(红色部分)。

PE文件结构:节表

接着我们就通过节表的字段解析进行进一步说明节的映射,打开Visual Studio,在任意.cpp/.c文件中敲入_IMAGE_SECTION_HEADER

接着选中该结构体类型,按下F12进行跳转,查看节表结构。

PE文件结构:节表
#define IMAGE_SIZEOF_SHORT_NAME             8

typedefstruct_IMAGE_SECTION_HEADER {
BYTEName[IMAGE_SIZEOF_SHORT_NAME];
union {
DWORDPhysicalAddress;
DWORDVirtualSize;
  }Misc;
DWORDVirtualAddress;
DWORDSizeOfRawData;
DWORDPointerToRawData;
DWORDPointerToRelocations;
DWORDPointerToLinenumbers;
WORDNumberOfRelocations;
WORDNumberOfLinenumbers;
DWORDCharacteristics;
}IMAGE_SECTION_HEADER,*PIMAGE_SECTION_HEADER;
1.Name[IMAGE_SIZEOF_SHORT_NAME](8字节)

大小为8字节,该字段表示的是节名称,通常是一个字符串,例如,.text表示代码节,.data表示数据节。这个时候可以通过010 Editor打开样例程序(在笔者公众号的《PE文件结构-DOS头部&DOS stub》文章中提及)。

PE文件结构:节表

样例程序中出现了如下节:

.textbss 、.text、 .rdata、.data、.idata、.msvcjmc、.00cfg、.rsrc、.reloc

.textbss:这通常是一个特定编译器或工具链生成的节,可能包含代码或未初始化数据的特殊部分。用于特定场景下优化代码和未初始化数据的存储。textbss节只在Debug模式下有效,Release模式下默认禁用Incremental Linking,所以不会生成这个节

.text:代码节,存放程序的可执行指令,

.rdata:只读数据节,存放程序的只读全局变量和常量。

.data:已初始化数据节,存放程序运行前已初始化的全局变量和静态变量。

.idata:导入数据节,包含了程序需要从其他动态链接库(DLL)导入的函数和变量的引用。这个节帮助程序在运行时解析和加载这些外部依赖。

.msvcjmc:这是一个特定于Microsoft Visual C++编译器的节,用于存储编译器生成的一些元数据,可能与调试信息或异常处理有关。

.00cfg:这个节名看起来像是特定于某个编译器或程序的配置数据。它可能包含了程序的配置信息,但这不是PE文件的标准节名称,因此具体内容可能需要查看相应的文档或编译器生成的文件来确定。

.rsrc:资源节,包含了程序的资源,如图标、菜单、对话框、字符串和其他用户界面元素。这些资源在程序运行时可以被访问和使用。

.reloc:重定位节,包含了重定位表,用于在程序加载到内存时调整地址。这是因为PE文件可能被加载到不同的内存地址,重定位表确保所有的地址引用都是正确的。

事实上该字段是描述性字段,可以随意修改,不会影响程序运行。

PE文件结构:节表
2. Misc联合体(DWORD)

在PE文件结构的节表(IMAGE_SECTION_HEADER)中,Misc字段是一个联合体,用于描述节的大小信息。

union {
DWORDPhysicalAddress;
DWORDVirtualSize;
  }Misc;

联合体(union)是一种特殊的数据类型,它允许在相同的内存位置存储不同的数据类型。这意味着联合体可以存储多种不同的数据类型,但是任何时候只能存储其中一种。

节表的Misc联合体字段中的PhysicalAddress最初用于描述节的实际物理地址,但在现代 PE 文件中,这个字段已经被废弃;出于兼容性原因,仍然保留此名称,但其实际用途已被重定义。所以在现代 PE 文件中该字段中上存储的数据为VirtualSize

VirtualSize表示节在内存中的大小;VirtualSize通常需要按内存对齐值(SectionAlignment)对齐,加载器根据VirtualSize和对齐要求为节分配内存。c此时样例程序的VirtualSizeSectionAlignment是如下值:

PE文件结构:节表
VirtualSize:        0x27334
SectionAlignment:  0x1000

当加载到内存时,实际分配的内存大小将是VirtualSize向上对齐到SectionAlignment的倍数,因此,加载后节会占用0x28000字节的内存,而其中只有0x27334字节包含实际数据,剩余的部分会被填充为零。使用x32dbg打开样例程序,转到内存布局页面查看该数值:

PE文件结构:节表

在该页面即可看到这几个字段的对应关系:以.textbss节为例子,该节的VirtualSize0x27298,由于在内存中是以页为存储单位,所以在存储后0x334大小的数据时需要单独开出一页,也就是VirtualSize需要与SectionAlignMent进行对齐。

3.VirtualAddress

VirtualAddress是 PE 文件中每个节的虚拟地址,描述了该节在内存中的起始位置(相对于ImageBase的偏移量),它指示操作系统加载器将该节映射到内存的哪个位置。节中的所有数据在运行时的虚拟地址可以通过以下公式计算:

实际虚拟地址 = ImageBase + VirtualAddress

.textbss为例:此时VirtualAddress的值为00 00 10 00,那么这个实际虚拟地址也就是0040 0000+0000 1000=0040 1000.

PE文件结构:节表

这个时候再来看一下x32dbg中的内存分布:

PE文件结构:节表

此时实际的虚拟就是我们所计算的0040 1000

VirtualAddress必须与SectionAlignment对齐,确保节的起始地址满足内存对齐要求。VirtualSize定义节在内存中的大小,从VirtualAddress + ImageBase开始计算。

4.SizeOfRawData(DWORD)

SizeOfRawData表示节在文件中占用的大小(以字节为单位),这是节在磁盘上的对齐后的大小,该值必须是FileAlignment的倍数,即文件对齐值;如果节中的实际数据小于对齐后的大小,文件中会填充空字节(通常为0x00)以达到对齐要求。

PE文件结构:节表
5.PointerToRawData(DWORD)

PointerToRawData是一个 4 字节(DWORD)的字段,表示该节在 PE 文件中开始位置的偏移量(以字节为单位)。它是从文件开头(文件的起始地址,即文件偏移0x0)到该节数据在文件中的开始位置的偏移量,通过该字段结合SizeOfRawData我们可以找到该节的原始数据。

PE文件结构:节表

此时样例程序的.text节的SizeOfRawData0005 8600PointerToRawData的值为0000 0400,这个时候我们就在010 Editor上定位.text节的原始数据:

①按下Ctrl + G(或者在菜单中选择Edit > Goto Offset)。

②数据起点为0x400,终点为0x58600 + 0x400 = 0x58A00(不含终点)。

PE文件结构:节表

那么这个区间的数据也就是.text节了。

在这四个字段介绍完后,接着我们可以从样例程序中将所有节的这四个字段都提取出来提取出来的VirtualSizeVirtualAddressSizeOfRawDataPointerToRawData

VirtualSize VirtualAddress SizeOfRawData PointerToRawData
.textbss 27298 1000 0 0
.text 583C8 29000 58400 400
.rdata B9DC 82000 BA00 58800
.data 24B0 8E000 1000 64200
.idata 0BA7 91000 C00 65200
.msvcjmc 0142 92900 200 65E00
.00cfg 104 93000 200 66000
.rsrc 43C 94000 600 66200
.reloc 29B0 95000 2A00 66800

在表中我们可以看到一个奇怪的现象,也就是.textbss这个节它VirtualAddressVirtualSize

字段有值,但是SizeOfRawDataPointerToRawData字段却为0;这就以为这这个节在内存中有对应的地址空间,但是在磁盘文件中却没有实际存储数据。这实际上是因为这个节是一个空节

空节通常指的就是 BSS(Block Started by Symbol)节,空节类似于 C 语言中的 BSS 段,这类节用于存储未初始化的全局变量或静态变量。在 PE 文件中,这些节通常在文件中没有实际数据(即 SizeOfRawData 为 0),但在加载时会占用内存(即 VirtualSize 和 VirtualAddress 有效)。

此外VirtualAddress是与SectionAlignment对齐、PointerToRawData是与FileAlignment(200)对齐的。

如此时我们要在内存中定位数据,由于第一个节是空节,那么接着就以第二个节

text为例子在内存中对该节的进行定位;.text在PE文件中的地址为PointerToRawData0x400,我们可以在010Editor中定位到该节,节的内容如下:

PE文件结构:节表

这个时候将样例程序载入x32dbg进行调试,计算出该节在内存中的地址:

内存中地址 = VirtualAddress + ImageBase
 = 29000 + 400000
 = 42 9000

x32dbg的内存窗口中键入ctrl+G,接着输入地址42 9000,即可定位到.text节:

PE文件结构:节表
6.PointerToRelocationsPointerToLinenumbersNumberOfRelocationsNumberOfLinenumbers

PointerToRelocations:表示节中重定位表的偏移地址(以字节为单位),从文件起始位置算起。如果节包含重定位条目,该字段会指向文件中重定位数据的位置。通常在可执行文件(如.exe文件)中不会使用此字段,其值通常为 0,在目标文件(.obj文件)中有意义,用于链接器处理符号的重定位。

PointerToLinenumbers:表示行号表的偏移地址,从文件起始位置算起。行号表用于调试,提供了源代码中行号和节中指令地址之间的映射关系。在 PE 文件中,该字段的值通常为0,因为行号信息更多地存在于外部调试信息(如 PDB 文件)中。

NumberOfRelocations:表示重定位表中的条目数,对于.exe文件,通常为0,因为重定位信息已经被加载器处理。

**NumberOfLinenumbers**:表示行号表中的条目数,常见于目标文件(.obj文件),在最终生成的 PE 文件中通常为0

事实上这几个字段在exe文件中基本上不用,可以随意修改,我们这边以.text节为例子:修改这三个字段后发现程序依旧可以正常运行。

PE文件结构:节表
7.Characteristics(DWORD)

Characteristics字段定义了节的属性。它以位掩码(bitmask)的形式表示,可以指示节的存储类型、是否可执行、是否可写等信息。该字段的具体取值如下:

//
// Section characteristics.
//
//     IMAGE_SCN_TYPE_REG                   0x00000000 // Reserved.
//     IMAGE_SCN_TYPE_DSECT                 0x00000001 // Reserved.
//     IMAGE_SCN_TYPE_NOLOAD               0x00000002 // Reserved.
//     IMAGE_SCN_TYPE_GROUP                 0x00000004 // Reserved.
#define IMAGE_SCN_TYPE_NO_PAD               0x00000008  // Reserved.
//     IMAGE_SCN_TYPE_COPY                 0x00000010 // Reserved.

#define IMAGE_SCN_CNT_CODE                   0x00000020  // 节包含代码。用于 .text 节等存储程序指令的部分。
#define IMAGE_SCN_CNT_INITIALIZED_DATA       0x00000040  // 节包含已初始化数据。用于 .data 节等存储已初始化全局变量的部分。
#define IMAGE_SCN_CNT_UNINITIALIZED_DATA     0x00000080  // 节包含未初始化数据。用于 .bss 节等存储未初始化全局变量的部分。

#define IMAGE_SCN_LNK_OTHER                 0x00000100  // Reserved.
#define IMAGE_SCN_LNK_INFO                   0x00000200  // 节包含一些链接器信息(非代码或数据)。
//     IMAGE_SCN_TYPE_OVER                 0x00000400 // Reserved.
#define IMAGE_SCN_LNK_REMOVE                 0x00000800  // 节在生成最终可执行文件时应被移除,仅在目标文件(.obj)中存在。
#define IMAGE_SCN_LNK_COMDAT                 0x00001000  // 节是 COMDAT 数据(用于消除重复的节内容)。
//                                           0x00002000 // Reserved.
//     IMAGE_SCN_MEM_PROTECTED - Obsolete   0x00004000
#define IMAGE_SCN_NO_DEFER_SPEC_EXC         0x00004000  // 节不支持延迟调试。
#define IMAGE_SCN_GPREL                     0x00008000  // 节内容是与全局指针(GP)相对的。
#define IMAGE_SCN_MEM_FARDATA               0x00008000
//     IMAGE_SCN_MEM_SYSHEAP - Obsolete   0x00010000
#define IMAGE_SCN_MEM_PURGEABLE             0x00020000
#define IMAGE_SCN_MEM_16BIT                 0x00020000
#define IMAGE_SCN_MEM_LOCKED                 0x00040000
#define IMAGE_SCN_MEM_PRELOAD               0x00080000

#define IMAGE_SCN_ALIGN_1BYTES               0x00100000  //
#define IMAGE_SCN_ALIGN_2BYTES               0x00200000  //
#define IMAGE_SCN_ALIGN_4BYTES               0x00300000  //
#define IMAGE_SCN_ALIGN_8BYTES               0x00400000  //
#define IMAGE_SCN_ALIGN_16BYTES             0x00500000  // Default alignment if no others are specified.
#define IMAGE_SCN_ALIGN_32BYTES             0x00600000  //
#define IMAGE_SCN_ALIGN_64BYTES             0x00700000  //
#define IMAGE_SCN_ALIGN_128BYTES             0x00800000  //
#define IMAGE_SCN_ALIGN_256BYTES             0x00900000  //
#define IMAGE_SCN_ALIGN_512BYTES             0x00A00000  //
#define IMAGE_SCN_ALIGN_1024BYTES           0x00B00000  //
#define IMAGE_SCN_ALIGN_2048BYTES           0x00C00000  //
#define IMAGE_SCN_ALIGN_4096BYTES           0x00D00000  //
#define IMAGE_SCN_ALIGN_8192BYTES           0x00E00000  //
// Unused                                   0x00F00000
#define IMAGE_SCN_ALIGN_MASK                 0x00F00000

#define IMAGE_SCN_LNK_NRELOC_OVFL           0x01000000  // Section contains extended relocations.
#define IMAGE_SCN_MEM_DISCARDABLE           0x02000000  // 节是可丢弃的。例如,初始化完成后,.idata 中的导入表可以被丢弃。
#define IMAGE_SCN_MEM_NOT_CACHED             0x04000000  // 节不可被缓存。通常用于某些硬件相关数据。
#define IMAGE_SCN_MEM_NOT_PAGED             0x08000000  // 节不可被分页到硬盘上。
#define IMAGE_SCN_MEM_SHARED                 0x10000000  // 节是可共享的,多个进程可以映射到同一物理内存区域。
#define IMAGE_SCN_MEM_EXECUTE               0x20000000  // 节是可执行的,通常用于存储代码。
#define IMAGE_SCN_MEM_READ                   0x40000000  // 节是可读的,存储代码或数据。
#define IMAGE_SCN_MEM_WRITE                 0x80000000  // 节是可写的,通常用于存储可修改数据。

这个时候在010 Eidtor中查看样例程序.text节的Characteristics字段:

PE文件结构:节表

该字段取值为:6000 0020,通过对照上述字段取值可知.text是可读、可执行的,并且在该节中包含代码。

0x60000020= 0x40000000(可读) + 0x20000000(可执行) + 0x00000020(节包含代码)

原文始发于微信公众号(风铃Sec):PE文件结构:节表

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年12月16日15:30:35
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   PE文件结构:节表https://cn-sec.com/archives/3514110.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息