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

时间:2012-02-26 01:52:34

标签: c linux linker

问题

我希望将一个目标文件注入现有的二进制文件中。作为一个具体示例,请考虑来源Hello.c

#include <stdlib.h>

int main(void)
{
    return EXIT_SUCCESS;
}

可以将其编译为名为Hellogcc -std=gnu99 -Wall Hello.c -o Hello的可执行文件。此外,现在考虑Embed.c

func1(void)
{
}

可以通过Embed.o从此创建目标文件gcc -c Embed.c。我的问题是如何一般地将Embed.o插入到Hello中,以便执行必要的重定位,并且正确修补适当的ELF内部表(例如符号表,PLT等)?< / p>


假设

可以假设要嵌入的目标文件已经静态链接了它的依赖关系。可以假定任何动态依赖项(例如C运行时)也存在于目标可执行文件中。


目前的尝试/想法

  • 使用libbfd将目标文件中的部分复制到二进制文件中。我对此所取得的进展是,我可以使用原始二进制文件中的部分和目标文件中的部分创建一个新对象。问题是,由于目标文件是可重定位的,因此无法在不执行重定位的情况下将其部分正确复制到输出中。
  • 将二进制文件转换回目标文件,并使用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不是目标文件。
  • 找到执行此类插入的现有工具?

基本原理(可选阅读)

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

  1. 将对象文件(包含用户定义的例程)注入二进制文件。 这是一个强制性步骤,不能通过注入共享对象等替代方法来解决。
  2. 对新二进制文件执行静态分析,并使用它来静态地将例程从原始代码转移到新添加的代码。
  3. 在大多数情况下,我已经完成了第2步所需的工作,但是我在注入目标文件时遇到了问题。鉴于其他工具使用相同的对象注入方法(例如EEL),问题肯定是可以解决的。

7 个答案:

答案 0 :(得分:5)

如果是我,我希望将Embed.c创建到共享对象libembed.so中,如下所示:

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

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

LD_PRELOAD=/path/to/libembed.so Hello

这里的“技巧”是弄清楚如何进行检测,特别是考虑到它是静态可执行文件。在那里,我无法帮助你,但这是让代码存在于进程内存空间中的一种方法。您可能希望在构造函数中进行某种初始化,您可以使用属性(如果您至少使用gcc):

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

答案 1 :(得分:2)

假设第一个可执行文件的源代码可用并且使用为后续目标文件分配空间的链接描述文件进行编译,则有一个相对简单的解决方案。由于我目前正在研究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

答案 2 :(得分:1)

有趣的主题。我有另一个具体的例子说明为什么这是有道理的。

我正在玩构建二进制运行时加密工具,它应该适用于已编译的程序。我想做的是:

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

2)使用我的解密例程和一个__attribute__((constructor))函数重新链接elf,该函数调用加密部分的解密

这样就可以在没有他们知道的情况下使用任何程序。

我还没有找到一种简单的方法,所以我可能需要将精灵分开并自己添加东西。

答案 3 :(得分:0)

你不能以任何实际的方式做到这一点。预期的解决方案是将该对象变为共享库,然后在其上调用dlopen。

答案 4 :(得分:0)

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

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

更简单

另一种可能性是自己定制链接过程并调用链接器并链接它以加载到某个固定地址。您也可以查看例如bootloaders,它还涉及一个基本的链接步骤来完成这个(将一段代码固定到已知的加载地址)。

如果您没有链接到固定地址,并且想要重新定位运行时,则必须编写一个基本链接器来获取目标文件,通过执行适当的修正将其重定位到目标地址。

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

答案 5 :(得分:0)

你看过DyninstAPI了吗?最近添加了支持将.o链接到静态可执行文件。

从发布网站:

  

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

答案 6 :(得分:0)

您必须通过扩展可执行文本段来为可重定位代码腾出空间以适应可执行文件,就像病毒感染一样。然后在将可重定位代码写入该空间后,通过为该可重定位目标文件中的任何内容添加符号来更新符号表,然后应用必要的重定位计算。我编写的代码可以很好地处理32位ELF。