6_各种区块的描述和对齐值, RVA 详解

6 各种区块的描述和对齐值, RVA 详解

各种区块的描述

通常, 区块中的数据在逻辑上是关联的. PE 文件一般至少都会有两个区块 : 一个是代码块, 一个是数据块.

每一个区块都需要有一个截然不同的名字, 这个名字主要是用来表达区块的用途.

例如有一个区块叫 .rdata, 表名它是一个只读区块. 注意 : 区块在映像中是按起始地址 (RVA) 来排列的, 而不是按字母表顺序.

另外, 使用区块名字只是人们为了认识和编程的方便, 这些名字对于操作系统来说都是无关紧要的. 微软给这些区块取了个有特色的名字, 但这不是必须的.

当我们要编程从 PE 文件中读取需要的内容时, 比如输入表, 输出表, 我们不能以区块名字作为参考, 而是应该按照数据目录表中的字段进行定位.

下面是区块的名称和意义

名称 描述
.text 默认的区块代码, 它的内容全是指令代码. 链接器把所有目标文件的 .text 块链接成一个大的 .text 块. 如果用的是 Borland C++, 其编译器把产生的代码存在叫作 CODE 的区域中.
.data 默认的读/写数据区块. 全局变量和静态变量一般放在这里.
.rdata 默认的只读数据区块. 至少有两种情况下要用到 .rdata. 一个是在 Microsoft 的链接器产生的 exe 文件中, 用于存放调试目录; 另一个是用于存放说明字符串.
.idata 包含其他外来 dll 的函数以及数据信息, 也就是我们说的输入表. 将 .idata 区块合并到另外一个区块已经成了现在的惯例, 典型的就是 .rdata 区块.
.edata 输出表. 当创建一个输出 API 或者数据的可执行文件时, 链接器会创建一个 .exp 文件, 这个文件就包含了一个 .edata 区块, 这个区块会被加入到最后的可执行文件中. 与 .idata 区块一样, .edata 区块也经常被发现合并到了 .text 或者 .tdata 区块中.
.rsrc 资源, 包含模块的全部资源, 比如图标, 菜单, 位图等等. 这个区块是只读的, 无论如何它不应该命名为 .rsrc 以外的其它任何名字, 也不能被合并到其它区块里.
.bss 未初始化数据. 很少用了, 取而代之的是执行文件的 .data 区块的 Vitual Size 被扩展到足够大的空间来装下未初始化数据.
.tls TLS的意思就是线程局部存储器, 用于支持通过 __declspec(thread) 声明的线程局部存储变量的数据. 这包括数据的初始化值, 也包括运行时所需要的额外变量.
.reloc 可执行文件的基址重定位. 基址重定位一般仅仅是 dll 文件才需要的. 在 Release 模式, 链接器并不给 exe 加上基址重定位, 重定位可以在链接的时候通过 /FIXED 开关去掉.
.sdata 可通过全局指针相对寻址的 "短" 可读/写数据. 用于 IA-64 和其它使用全局指针寄存器的体系上. IA-64 上的正常大小的全局变量位于这个区块中.
.srcdata 可通过全局指针相对寻址的 "短" 只读数据. 用于 IA-64 和其它使用全局指针寄存器的体系上.
.pdata 异常表. 包含一个 IMAGE_RUNTIME_FUNCTION_ENTRY 类型的结构数组, 这个结构是特定于 CPU 的. 数据目录中的 IMAGE_DIRECTORY_ENTRY_EXCEPTION 指向它. 用于使用基于表的异常处理的体系, 比如 IA-64. 唯一一个不使用基于表的异常处理的体系是 x86.
.debug$S obj 文件中 Codeview 格式的符号. 这是一个可变长的 Codeview 格式符号记录流.
.debug$T obj 文件中 Codeview 格式的类型记录. 这是一个可变长的 Codeview 格式类型记录流.
.debug$P 当使用预编译头时会出现在 obj 文件中.
.drectve 只用于 obj 文件, 包含一些链接器指令. 这些指令是一些能被传递到链接器命令行的 ASCII 字符串. 例如 : -defaultlib:LIBC 多个指令之间用空格隔开.
.didat 延迟加载的导入数据. 可在用非 Release 模式创建的可执行文件中找到它. 而在 Release 模式, 延迟加载数据被合并到其它区块中.

当然我们在 Vitual C++ 中也可以命名我们自己的区块, 用 #pragma 来声明, 告诉编译器插入数据到一个区块内, 格式如下 :

#pragma data_msg("FC_data")

大家应该知道, # 是宏处理符号. 啥是宏 ? 就是编译的时候由编译器直接先进行编译, 或者说按照指定格式机械替换.

以上语句告诉编译器, 叫它把数据都放进一个叫 "FC_data" 的区块内, 而不是默认的 .data 区块.

区块一般是从 obj 文件开始, 被编译器放置的. 链接器的工作就是合并左右 obj 和库中需要的块, 使其成为一个最终合适的区块. 链接器会遵循一套相当完整的规则, 它会判断哪些区块将被合并以及如何被合并.

合并区块 :

链接器的一个有趣特征就是能够合并区块. 如果两个区块有相似, 一致性的属性, 那么它们在链接的时候能够被合并成一个单一的区块. 这取决于是否开启编译器的 /merge 开关. 事实上合并区块有一个好处就是可以节省磁盘的内存空间...... 注意, 我们不应该将 .rsrc, .reloc, .pdata 合并到其他的区块里.

各种区块的文件对齐值

磁盘上的对齐值

之前我们简单了解过区块是要对齐的, 无论是在内存中存放还是在磁盘中存放. 但它们的对齐值一般是不同的.

PE 文件头里边的 FileAlignment 定义了磁盘区块的对齐值. 每一个区块从对齐值的倍数的偏移位置开始存放. 而区块的实际代码或数据的大小不一定刚好是这么多, 所以在多余的地方一般以 00H 来填充, 这就是区块之间的间隙.

例如, 在 PE 文件中, 一个典型的对齐值就是 200H, 这样, 每个区块都将从 200H 的倍数的文件偏移位置开始. 假设第一个区块在 400H 处, 长度为 90H, 那么从文件 400H ~ 490H 为这一个区块的内容, 而由于文件的对齐值是 200H, 所以为了是这一个区块的长度为 FileAlignment 的整数倍, 490H ~ 600H 这一个区间都会被 00H 填充, 这段空间称为区块间隙, 下一个区块的起始地址为 600H.

这些填充进去的 00H, 也就是空白间隙这里, 你可以在这里瞎改, 只要你不影响程序正常运行.

有些病毒程序就是这样来做免杀的.

内存中的对齐值

PE 文件头里边的 SectionAlignment 定义了内存中的区块的对齐值. PE 文件被映射到内存中时, 区块总是至少从一个页边界开始.

一般在 X86 系列的 CPU 中, 页是按 4KB (1000H) 来排列的; 在 IA-64 上, 是按 8KB(2000H) 来排列的. 所以在 X86 系统中, PE 文件区块的内存对齐值一般等于 1000H, 每个区块按 1000H 的倍数在内存中存放. 也就是第一个区块一般就是从 1000H 开始的.

RVA 详解

在前面我们探讨过 RVA 这个词, 现在我们深入探讨这个概念.

RVA 是相对虚拟地址 (Relative Virtual Address) 的缩写, 顾名思义, 它是一个 :相对地址. PE 文件的各种数据结构中涉及地址的字段大部分都是以 RVA 来表示的.

更为准确的说, RVA 是当 PE 文件被装载到内存中后, 某个数据位置相对于文件头的偏移量. 举个例子, 如果 windows 装载器将一个 PE 文件装入到 00400000H 处的内存中, 而某个区块中的某个数据被装入 0040xxxxH 处, 那么这个数据的 RVA 就是 0040xxxxH - 00400000H = 00008xxxxH, 反过来说, 将 RVA 的值加上文件被装载的基地址 (Image Base), 就可以得到数据在内存中的真实地址.

很明显, 我们发现, DOS 文件头, PE 文件头和区块表的偏移位置与大小均没有发生变化.

RVA 使得文件装入内存后的数据定位变得方便, 然而却给我们要定位放在硬盘上的静态 PE 文件带来了麻烦.

如何换算 RVA 和文件偏移

当处理 PE 文件的时候, 任何的 RVA 必须经过到文件偏移的换算, 才能用来定位并访问文件中的数据, 但是换算无法用一个简单的公式来完成. 事实上, 唯一可用的方法就是最土最笨的方法 :

  • 步骤一 :

循环扫描区块表得出每个区块在内存中的起始 RVA (根据 IMAGE_SECTION_HEADER 中的 VirtualAddress 字段), 并根据区块的大小 (根据 IMAGE_SECTION_HEADER 中的 SizeOfRawData 段) 算出区块的结束 RVA (二者相加即可), 最后判断我们要计算的目标 RVA 是否落在这个区块内.

  • 步骤二 :

我们通过步骤一定位了目标 RVA 在哪一个具体的区块, 然后我们用目标 RVA 减去该区块的起始 RVA, 这样就能得到目标 RVA 相对于起始地址的偏移量 RVA2.

  • 步骤三 :

在区块表中获取该区块在文件中所处的偏移地址 (根据 IMAGE_SECTION_HEADER 中的 PointerToRawData 字段), 将这个偏移值加上步骤二得到的 RVA2 值, 就得到了真正的文件偏移地址.