如何将目标文件链接到可执行文件/编译的二进制文件?

问题

我希望将一个对象文件注入到一个现有的二进制文件中 作为一个具体的例子,考虑一个源Hello.c

 #include <stdlib.h> int main(void) { return EXIT_SUCCESS; } 

它可以通过gcc -std=gnu99 -Wall Hello.c -o HelloHello的可执行文件。 此外,现在考虑Embed.c

 func1(void) { } 

一个对象文件Embed.o可以通过gcc -c Embed.c 。 我的问题是如何一般插入Embed.oHello ,以便执行必要的重定位,并适当地修补适当的ELF内部表(如符号表,PLT等)?


假设

可以假设要embedded的目标文件已经将其相关性静态链接在一起。 任何dynamic的依赖关系,比如C运行时,都可以假定也存在于目标可执行文件中。


目前的尝试/想法

  • 使用libbfd将对象文件中的部分复制到二进制文件中。 我所取得的进展是,我可以用原始二进制文件的部分和目标文件的部分创build一个新的对象。 问题是由于目标文件是可重定位的,因此无法将其部分正确地复制到输出中,而无需首先执行重定位。
  • 将二进制文件转换回一个目标文件并用ld重新链接。 到目前为止,我尝试使用objcopy来执行转换objcopy --input elf64-x86-64 --output elf64-x86-64 Hello Hello.o 。 显然,这是行不通的,因为我打算因为ld -o Hello2 Embed.o Hello.o将导致ld: error: Hello.o: unsupported ELF file type 2 。 我猜这应该是预期的,因为Hello不是一个对象文件。
  • find一个现有的工具,执行这种插入?

理由(可选阅读)

我正在制作一个静态的可执行文件编辑器,其目的是允许将任意用户定义的例程插入到现有的二进制文件中。 这将分两步进行:

  1. 将一个目标文件(包含用户定义的例程)注入到二进制文件中。 这是一个强制性的步骤,不能通过诸如注入共享对象等替代方法来解决。
  2. 对新的二进制文件执行静态分析,并使用它来静态地将例程从原始代码绕到新添加的代码。

我大部分已经完成了第2步所需的工作,但是我在注入目标文件时遇到了麻烦。 这个问题肯定是可以解决的,因为其他工具使用相同的对象注入方法(例如EEL )。

如果是我,我会把Embed.c创建成一个共享对象, libembed.so ,就像这样:

 gcc -Wall -shared -fPIC -o libembed.so Embed.c 

这应该从Embed.c创建一个可重定位的共享对象。 通过这个,你可以通过在运行时设置环境变量LD_PRELOAD来强制你的目标二进制文件加载这个共享对象(参见这里的更多信息):

 LD_PRELOAD=/path/to/libembed.so Hello 

这里的“诀窍”是弄清楚如何做你的仪器,特别是考虑到它是一个静态的可执行文件。 在那里,我不能帮助你,但是这是在进程的内存空间中存在代码的一种方法。 你可能想在构造函数中做一些初始化,你可以用一个属性来完成初始化(如果你至少使用gcc ):

 void __attribute__ ((constructor)) my_init() { // put code here! } 

你不能以任何可行的方式做到这一点。 预期的解决方案是使该对象成为共享库,然后调用dlopen。

问题在于.o's还没有完全链接,大多数引用仍然是象征性的。 二进制文件(共享库和可执行文件)距离最终链接的代码更近了一步。

执行链接步骤到一个共享库,并不意味着你必须通过动态lib加载器加载它。 建议更多的是一个二进制或共享库自己的加载器可能比.o更简单。

另一种可能是自己定制链接过程,并调用链接器并将其链接到某个固定地址上。 你也可以看看如bootloader的准备,这也涉及到一个基本的链接步骤来做到这一点(将一段代码固定到一个已知的加载地址)。

如果你没有链接到一个固定的地址,并且想要重新定位运行时,你将不得不编写一个基本的链接器来获取目标文件,通过执行相应的修改将其重定位到目标地址。

我假设你已经拥有它了,看到这是你的硕士论文,但是这本书: http : //www.iecc.com/linker/是关于这个的标准介绍。

你看过DyninstAPI吗? 看来最近增加了一个支持,用于将.o链接到一个静态可执行文件。

从发布网站:

二进制重写器支持x86和x86_64平台上的静态链接二进制文件

您必须通过扩展可执行文本段才能使可重定位代码适应可执行文件,就像病毒感染一样。 然后在将可重定位代码写入该空间之后,通过为该可重定位对象中的任何内容添加符号来更新符号表,然后应用必要的重定位计算。 我已经写了32位ELF的代码。

有趣的线程。 我有另一个具体的例子,为什么这是有道理的。

我正在构建一个二进制运行时加密工具,应该在已经编译好的程序上工作。 我想要做的是这样的:

1)加密精灵的某些部分(.text等)

2)用我的解密例程和__attribute__((constructor))函数重新链接精灵,调用加密部分的解密

这样,这将适用于任何不知道的节目。

我还没有找到一个简单的方法来做到这一点,所以我可能不得不分开精灵,并自己添加的东西。

假设第一个可执行文件的源代码可用,并且使用为后面的目标文件分配空间的链接器脚本编译,则有一个相对简单的解决方案。 由于我目前正在开发一个ARM项目,下面的例子是用GNU ARM交叉编译器编译的。

主要源代码文件hello.c

 #include <stdio.h> int main () { return 0; } 

是用一个简单的链接脚本为以后嵌入的对象分配空间来构建的:

 SECTIONS { .text : { KEEP (*(embed)) ; *(.text .text*) ; } } 

喜欢:

 arm-none-eabi-gcc -nostartfiles -Ttest.ld -o hello hello.c readelf -s hello Num: Value Size Type Bind Vis Ndx Name 0: 00000000 0 NOTYPE LOCAL DEFAULT UND 1: 00000000 0 SECTION LOCAL DEFAULT 1 2: 00000000 0 SECTION LOCAL DEFAULT 2 3: 00000000 0 SECTION LOCAL DEFAULT 3 4: 00000000 0 FILE LOCAL DEFAULT ABS hello.c 5: 00000000 0 NOTYPE LOCAL DEFAULT 1 $a 6: 00000000 0 FILE LOCAL DEFAULT ABS 7: 00000000 28 FUNC GLOBAL DEFAULT 1 main 

现在让我们编译嵌入的对象,其源代码位于embed.c中

 void func1() { /* Something useful here */ } 

使用相同的链接器脚本重新编译,插入新的符号:

 arm-none-eabi-gcc -c embed.c arm-none-eabi-gcc -nostartfiles -Ttest.ld -o new_hello hello embed.o 

查看结果:

 readelf -s new_hello Num: Value Size Type Bind Vis Ndx Name 0: 00000000 0 NOTYPE LOCAL DEFAULT UND 1: 00000000 0 SECTION LOCAL DEFAULT 1 2: 00000000 0 SECTION LOCAL DEFAULT 2 3: 00000000 0 SECTION LOCAL DEFAULT 3 4: 00000000 0 FILE LOCAL DEFAULT ABS hello.c 5: 00000000 0 NOTYPE LOCAL DEFAULT 1 $a 6: 00000000 0 FILE LOCAL DEFAULT ABS 7: 00000000 0 FILE LOCAL DEFAULT ABS embed.c 8: 0000001c 0 NOTYPE LOCAL DEFAULT 1 $a 9: 00000000 0 FILE LOCAL DEFAULT ABS 10: 0000001c 20 FUNC GLOBAL DEFAULT 1 func1 11: 00000000 28 FUNC GLOBAL DEFAULT 1 main