预备知识
一段代码,从 .c
源文件到可执行文件经历了4个步骤:
- 预处理(Preprocessing):主要处理源代码中以
#
开始的预编译指令,比如#include
、#define
等等。预处理完得到的是.i
文件。 - 编译(Compilation):编译完成后,得到的是:汇编文件(
.s
) - 汇编(Assembly):将汇编代码转换成机器可以执行的指令,每一条汇编指令都会对应一条机器指令。汇编完成后,得到的是:可重定向目标文件(
.o
)。 - 链接(Linking):通常一份文件会调用其他文件中编写的函数,但当前文件怎么知道去哪里寻找它想调用的函数呢?这就需要链接。链接完成后,得到的是:可执行目标文件。
其中:可重定向目标文件、可执行目标文件都必须符合ELF格式(Executable and Linkable Format),因此也可称为:ELF文件。
ELF文件包含一下三个部分:
- ELF header
- Sections
- Section header table
下图是一个 Hello World 可执行程序的 ELF 格式实例。
我们可以通过命令行工具readelf
查看 elf 文件。
1 | $ readelf -h demo #查看elf header |
1 | $ readelf -S demo #查看各个section信息 |
这里容易混淆的是:ELF Header、Program Header、Section Header:
- ELF Header 是 ELF 文件的文件头,它是文件的起始部分,为加载器和链接器提供必要的信息来处理文件。
- Program Header 是一个数据结构,它定义了程序如何加载到内存中。每个 Program Header 都包含了一个段(Segment)的加载信息,比如它的类型、在文件中的偏移量、在内存中的地址、大小等。
- Program Header Table 是一个由多个 Program Header 组成的数组,它按照程序头的顺序排列。Program Header Table 通常位于 ELF Header 之后,通常在文件的开始部分,这样加载器可以快速地找到并读取它。
- Section Header 定义了文件中的节(Section)的属性和信息。每个节头条目(Section Header Entry)都包含了关于一个特定节的元数据。
注意:
Sections 中的第一个 Section(索引为0的section)就是 Program Header Table。
以之前图中 ELF 文件为例。
**ELF Header **占据最开始的64个字节。
紧接着是Program Header Table,总共占据 56 * 13 = 728个字节。
所以真正的第一个section(.interp)开始地址应该是:64 + 728 = 792 = 0x318。
另外一个容易混淆的是:节(Section)、段(Segment)。
Section 称为节,是指在汇编源码中经由关键字section或segment修饰、逻辑划分的指令或数据区域,汇编器会将这两个关键字修饰的区域在目标文件中编译成节,也就是说”节”最初诞生于目标文件中。
Segment 称为段,是,链接器把目标文件链接成可执行文件,因此段最终诞生于可执行文件中。我们平时所说的可执行程序内存空间中的代码段和数据段就是指的Segment。
- 左边是ELF的链接视图,可以理解为是目标文件的内容布局。
- 右边是ELF的执行视图,可以理解为可执行文件的内容布局。
注意:目标代码文件的内容是由 Section 组成的,而可执行文件的内容是由 Segment 组成的。
我们查看汇编程序时,
.text
,.bss
,.data
都指的是 Section。目标代码文件中的 Section 和 Section Header Table 中的条目是一一对应的。Section 的信息用于链接器对代码重定位。而文件载入内存执行时,是以 Segment 组织的,每个 Segment 对应 ELF 文件中 Program Header Table 中的一个条目,用来建立可执行文件的进程映像。链接器将目标文件中属性相同的多个 Section 合并,从而形成新的可执行文件;合并后的 Section 的集合,称为 Segment。例如:所有
.text
Section 会被合并成一个新的.text
Segment。
注意:在目标文件中,Program Header 不是必须的,我们用 gcc 生成的目标文件也不包含 Program Header。
在所有Sections中,有一个section比较特殊:.shstrtab
。
在之前查看 ELF Header 的时候,ELF Header 就提供了
.shstrtab
Section 的索引是多少。
节头字符串表(Section Header String Table,简称SHSTRTAB)是 ELF 文件中的一个特殊 section。它包含所有 section 名称的字符串。
每个 Section Header 中的
sh_name
字段是一个索引,指向节头字符串表中的一个位置,该位置存储了该 section 的名称。节头字符串表本质上是一个以空字符(
\0
)分隔的字符串集合。每个section的名称在这个表中都有一个唯一的偏移量,该偏移量存储在对应section头部的sh_name
字段中。1
2
3
40x00: "\0"
0x01: ".text\0"
0x07: ".data\0"
0x0D: ".bss\0"
技术路线
我们希望采用跳板技术实现静态插桩。
- 跳板技术:简单来说,就是通过设置一个中间点(跳板),让程序先跳到这个中间点,然后由这个中间点再跳到最终的目标函数去执行。这个过程就像是设置了一个中转站,让程序先到中转站,再由中转站引导到目的地。
- 我们给目标程序新增加一个section作为跳板(trampoline)。
具体分为两个函数:
- 创造trampoline空间的函数:Crt_Trpline
- 进行二进制插桩的函数: Sinsert
- 复原函数:recover
代码实现
在BFD(Binary File Descriptor)库中,bfd_make_section
及其相关函数用于创建新的节(section)并将其附加到BFD(Binary File Descriptor)结构中。这些函数提供了不同的选项和功能,以适应不同的使用场景。以下是一些常见的 make section
函数及其区别和功能:
bfd_make_section
- 功能:创建一个新的空节,并将其附加到BFD的节链表中。
- 参数:只需要提供BFD和节的名称。
- 特点:如果同名节已存在,则不会创建新的节,而是返回现有的节。
bfd_make_section_anyway
- 功能:创建一个新的空节,即使同名节已存在,也会创建新的节,并附加到BFD的节链表中。
- 参数:提供BFD和节的名称。
- 特点:不检查是否已存在同名节,总是创建新的节。
bfd_make_section_with_flags
- 功能:创建一个新的节,并为其设置特定的标志(flags)。
- 参数:提供BFD、节的名称和要设置的标志。
- 特点:允许在创建节时指定节的属性,如是否可加载、是否可执行等。
bfd_make_section_anyway_with_flags
- 功能:创建一个新的节,并为其设置特定的标志,即使同名节已存在。
- 参数:提供BFD、节的名称和要设置的标志。
- 特点:结合了
bfd_make_section_with_flags
和bfd_make_section_anyway
的功能。
bfd_make_section_old_way
- 功能:使用旧方法创建一个新的节。
- 参数:只需要提供BFD和节的名称。
- 特点:如果同名节已存在,则不会创建新的节,而是返回现有的节。这是较老的API,可能在新版本的BFD中不再推荐使用。
bfd_set_section_flags
- 功能:设置现有节的标志。
- 参数:提供节和要设置的标志。
- 特点:不创建新节,只修改现有节的属性。
bfd_set_section_size
- 功能:设置现有节的大小。
- 参数:提供节和新的大小。
- 特点:不创建新节,只修改现有节的大小。
bfd_set_section_contents
- 功能:设置现有节的内容。
- 参数:提供BFD、节、数据缓冲区、偏移量和数据大小。
- 特点:不创建新节,只修改现有节的内容。
这些函数提供了灵活的方式来创建和管理BFD中的节。选择哪个函数取决于你的具体需求,比如是否需要检查同名节的存在、是否需要设置特定的节属性等。在实际应用中,应根据目标文件格式和操作需求选择合适的函数。
1 |
如何新增section
我们主要通过c语言标准库中提供的工具来编辑 elf header。
1 |