【问题标题】:How do I merge two binary executables?如何合并两个二进制可执行文件?
【发布时间】:2012-04-01 02:15:31
【问题描述】:

这个问题来自我之前问过的another question。简而言之,这是我将两个完全链接的可执行文件合并为一个完全链接的可执行文件的尝试之一。不同之处在于上一个问题涉及将目标文件合并到完整链接的可执行文件中,这更加困难,因为这意味着我需要手动处理重定位。

我有以下文件:

example-target.c:

#include <stdlib.h>
#include <stdio.h>

int main(void)
{
    puts("1234");
    return EXIT_SUCCESS;
}

example-embed.c:

#include <stdlib.h>
#include <stdio.h>

/*
 * Fake main. Never used, just there so we can perform a full link.
 */
int main(void)
{
    return EXIT_SUCCESS;
}

void func1(void)
{
    puts("asdf");
}

我的目标是合并这两个可执行文件以生成与example-target 相同的最终可执行文件,但另外还有另一个mainfunc1

从 BFD 库的角度来看,每个二进制文件都由(除其他外)一组部分组成。我遇到的第一个问题是这些部分有冲突的加载地址(这样如果我要合并它们,这些部分就会重叠)。

我为解决这个问题所做的是以编程方式分析example-target,以获取加载地址列表及其每个部分的大小。然后我对example-embed 做了同样的事情,并使用此信息为example-embed.c 动态生成linker command,以确保其所有部分都链接到不与example-target 中的任何部分重叠的地址。因此example-embed实际上在这个过程中被完全链接了两次:一次是为了确定有多少节以及它们的大小,再一次是为了保证没有与example-target的节冲突。

在我的系统上,生成的链接器命令是:

-Wl,--section-start=.new.interp=0x1004238,--section-start=.new.note.ABI-tag=0x1004254,
--section-start=.new.note.gnu.build-id=0x1004274,--section-start=.new.gnu.hash=0x1004298,
--section-start=.new.dynsym=0x10042B8,--section-start=.new.dynstr=0x1004318,
--section-start=.new.gnu.version=0x1004356,--section-start=.new.gnu.version_r=0x1004360,
--section-start=.new.rela.dyn=0x1004380,--section-start=.new.rela.plt=0x1004398,
--section-start=.new.init=0x10043C8,--section-start=.new.plt=0x10043E0,
--section-start=.new.text=0x1004410,--section-start=.new.fini=0x10045E8,
--section-start=.new.rodata=0x10045F8,--section-start=.new.eh_frame_hdr=0x1004604,
--section-start=.new.eh_frame=0x1004638,--section-start=.new.ctors=0x1204E28,
--section-start=.new.dtors=0x1204E38,--section-start=.new.jcr=0x1204E48,
--section-start=.new.dynamic=0x1204E50,--section-start=.new.got=0x1204FE0,
--section-start=.new.got.plt=0x1204FE8,--section-start=.new.data=0x1205010,
--section-start=.new.bss=0x1205020,--section-start=.new.comment=0xC04000

(请注意,我使用objcopy --prefix-sections=.new example-embedobj 为部分名称添加了.new 前缀以避免部分名称冲突。)

然后我编写了一些代码来生成一个新的可执行文件(从objcopySecurity Warrior 书中借用了一些代码)。新的可执行文件应该有:

  • example-target的所有部分和example-embed的所有部分
  • 包含example-target 的所有符号和example-embed 的所有符号的符号表

我写的代码是:

#include <stdlib.h>
#include <stdio.h>
#include <stdbool.h>
#include <bfd.h>
#include <libiberty.h>

struct COPYSECTION_DATA {
    bfd *      obfd;
    asymbol ** syms;
    int        symsize;
    int        symcount;
};

void copy_section(bfd * ibfd, asection * section, PTR data)
{
    struct COPYSECTION_DATA * csd  = data;
    bfd *             obfd = csd->obfd;
    asection *        s;
    long              size, count, sz_reloc;

    if((bfd_get_section_flags(ibfd, section) & SEC_GROUP) != 0) {
        return;
    }

    /* get output section from input section struct */
    s        = section->output_section;
    /* get sizes for copy */
    size     = bfd_get_section_size(section);
    sz_reloc = bfd_get_reloc_upper_bound(ibfd, section);

    if(!sz_reloc) {
        /* no relocations */
        bfd_set_reloc(obfd, s, NULL, 0);
    } else if(sz_reloc > 0) {
        arelent ** buf;

        /* build relocations */
        buf   = xmalloc(sz_reloc);
        count = bfd_canonicalize_reloc(ibfd, section, buf, csd->syms);
        /* set relocations for the output section */
        bfd_set_reloc(obfd, s, count ? buf : NULL, count);
        free(buf);
    }

    /* get input section contents, set output section contents */
    if(section->flags & SEC_HAS_CONTENTS) {
        bfd_byte * memhunk = NULL;
        bfd_get_full_section_contents(ibfd, section, &memhunk);
        bfd_set_section_contents(obfd, s, memhunk, 0, size);
        free(memhunk);
    }
}

void define_section(bfd * ibfd, asection * section, PTR data)
{
    bfd *      obfd = data;
    asection * s    = bfd_make_section_anyway_with_flags(obfd,
            section->name, bfd_get_section_flags(ibfd, section));
    /* set size to same as ibfd section */
    bfd_set_section_size(obfd, s, bfd_section_size(ibfd, section));

    /* set vma */
    bfd_set_section_vma(obfd, s, bfd_section_vma(ibfd, section));
    /* set load address */
    s->lma = section->lma;
    /* set alignment -- the power 2 will be raised to */
    bfd_set_section_alignment(obfd, s,
            bfd_section_alignment(ibfd, section));
    s->alignment_power = section->alignment_power;
    /* link the output section to the input section */
    section->output_section = s;
    section->output_offset  = 0;

    /* copy merge entity size */
    s->entsize = section->entsize;

    /* copy private BFD data from ibfd section to obfd section */
    bfd_copy_private_section_data(ibfd, section, obfd, s);
}

void merge_symtable(bfd * ibfd, bfd * embedbfd, bfd * obfd,
        struct COPYSECTION_DATA * csd)
{
    /* set obfd */
    csd->obfd     = obfd;

    /* get required size for both symbol tables and allocate memory */
    csd->symsize  = bfd_get_symtab_upper_bound(ibfd) /********+
            bfd_get_symtab_upper_bound(embedbfd) */;
    csd->syms     = xmalloc(csd->symsize);

    csd->symcount =  bfd_canonicalize_symtab (ibfd, csd->syms);
    /******** csd->symcount += bfd_canonicalize_symtab (embedbfd,
            csd->syms + csd->symcount); */

    /* copy merged symbol table to obfd */
    bfd_set_symtab(obfd, csd->syms, csd->symcount);
}

bool merge_object(bfd * ibfd, bfd * embedbfd, bfd * obfd)
{
    struct COPYSECTION_DATA csd = {0};

    if(!ibfd || !embedbfd || !obfd) {
        return FALSE;
    }

    /* set output parameters to ibfd settings */
    bfd_set_format(obfd, bfd_get_format(ibfd));
    bfd_set_arch_mach(obfd, bfd_get_arch(ibfd), bfd_get_mach(ibfd));
    bfd_set_file_flags(obfd, bfd_get_file_flags(ibfd) &
            bfd_applicable_file_flags(obfd));

    /* set the entry point of obfd */
    bfd_set_start_address(obfd, bfd_get_start_address(ibfd));

    /* define sections for output file */
    bfd_map_over_sections(ibfd, define_section, obfd);
    /******** bfd_map_over_sections(embedbfd, define_section, obfd); */

    /* merge private data into obfd */
    bfd_merge_private_bfd_data(ibfd, obfd);
    /******** bfd_merge_private_bfd_data(embedbfd, obfd); */

    merge_symtable(ibfd, embedbfd, obfd, &csd);

    bfd_map_over_sections(ibfd, copy_section, &csd);
    /******** bfd_map_over_sections(embedbfd, copy_section, &csd); */

    free(csd.syms);
    return TRUE;
}

int main(int argc, char **argv)
{
    bfd * ibfd;
    bfd * embedbfd;
    bfd * obfd;

    if(argc != 4) {
        perror("Usage: infile embedfile outfile\n");
        xexit(-1);
    }

    bfd_init();
    ibfd     = bfd_openr(argv[1], NULL);
    embedbfd = bfd_openr(argv[2], NULL);

    if(ibfd == NULL || embedbfd == NULL) {
        perror("asdfasdf");
        xexit(-1);
    }

    if(!bfd_check_format(ibfd, bfd_object) ||
            !bfd_check_format(embedbfd, bfd_object)) {
        perror("File format error");
        xexit(-1);
    }

    obfd = bfd_openw(argv[3], NULL);
    bfd_set_format(obfd, bfd_object);

    if(!(merge_object(ibfd, embedbfd, obfd))) {
        perror("Error merging input/obj");
        xexit(-1);
    }

    bfd_close(ibfd);
    bfd_close(embedbfd);
    bfd_close(obfd);
    return EXIT_SUCCESS;
}

总结这段代码的作用,它需要 2 个输入文件(ibfdembedbfd)来生成一个输出文件(obfd)。

  • ibfd复制格式/arch/mach/文件标志和起始地址到obfd
  • 定义从ibfdembedbfdobfd 的部分。部分的填充是单独进行的,因为 BFD 要求在开始填充之前创建所有部分。
  • 将两个输入 BFD 的私有数据合并到输出 BFD。由于 BFD 是多种文件格式之上的通用抽象,因此不一定能够全面封装底层文件格式所需的一切。
  • 创建由ibfdembedbfd的符号表组成的组合符号表,并将其设置为obfd的符号表。此符号表已保存,以便以后可用于构建重定位信息。
  • 将部分从ibfd 复制到obfd。除了复制部分内容外,此步骤还涉及构建和设置重定位表。

在上面的代码中,有些行用/******** */ 注释掉了。这些行处理example-embed 的合并。如果它们被注释掉,就会发生obfd 只是作为ibfd 的副本构建的。我已经对此进行了测试,并且效果很好。但是,一旦我将这些行评论回来,问题就开始出现了。

对于完全合并的未注释版本,它仍然会生成一个输出文件。可以使用objdump 检查此输出文件,发现其中包含两个输入的所有部分、代码和符号表。但是,objdump 抱怨:

BFD: BFD (GNU Binutils for Ubuntu) 2.21.53.20110810 assertion fail ../../bfd/elf.c:1708
BFD: BFD (GNU Binutils for Ubuntu) 2.21.53.20110810 assertion fail ../../bfd/elf.c:1708

在我的系统上,1708elf.c 是:

BFD_ASSERT (elf_dynsymtab (abfd) == 0);

elf_dynsymtabelf-bfd.h 中的一个宏,用于:

#define elf_dynsymtab(bfd)  (elf_tdata(bfd) -> dynsymtab_section)

我不熟悉 ELF 层,但我相信这是读取动态符号表的问题(或者说它不存在)。目前,除非必要,否则我尽量避免直接进入 ELF 层。有没有人能告诉我我在代码中或概念上做错了什么?

如果有帮助,我还可以发布链接器命令生成的代码或示例二进制文件的编译版本。


我意识到这是一个非常大的问题,因此,我想适当地奖励任何能够帮助我解决这个问题的人。如果我能够在某人的帮助下解决这个问题,我很乐意奖励 500+ 奖金。

【问题讨论】:

  • 你为什么要这样做?动机是什么?你有这两个二进制文件的源代码吗?恕我直言,似乎相当愚蠢。
  • @EdHeal 查看顶部的链接问题到他的另一个问题,这有一些理由。
  • @EdHeal:我正在制作一个静态可执行编辑器,它可以获取一个目标,将用户定义的例程注入其中(example-embed 的作用),然后静态绕过新二进制文件的代码将原始代码链接到注入代码(我已经编写了一个反汇编程序/CFG 分析引擎,我还可以编辑任意指令,所以这个注入是最后一块拼图)。对于我需要关心的用例,可以假设我们可以访问用户定义例程的源代码,但不能访问目标。
  • @DanFego - 谢谢 - 我会的。
  • 您的最终可执行文件是否有.dynsymtab 部分? (readelf -WS exename)

标签: c linux linker elf bfd


【解决方案1】:

为什么要手动进行所有这些操作?鉴于您拥有所有符号信息(如果您想以理智的方式编辑二进制文件,则必须),将可执行文件拆分为单独的目标文件(例如,每个函数一个目标文件)不是更容易吗?编辑,然后重新链接?

【讨论】:

  • 如何在没有源代码的情况下将可执行文件拆分为目标文件?我可以假设符号信息可用于嵌入对象,但不适用于目标(尽管如果我可以首先使用这个假设,那很好)。
  • ELF 可执行文件可以保留重定位信息和符号表。当这两个部分都存在时,将可执行文件拆分为目标文件相对简单,因为符号表还说明了符号是数据还是代码。另外,你为什么要合并可执行文件?注入目标文件会更容易。
  • 我试图合并可执行文件,因为我认为这会更容易,因为我不再需要处理重定位。当我尝试这样做时,我无法找到注入目标文件的方法。我将如何将可执行文件拆分为目标文件?顺便说一句,感谢您的关注。
  • 如果没有重定位信息,您将无法有意义地调整跳转指令和数据引用。而且你不能仅仅依赖 BFD——你最终必须了解 ELF 的细节。简而言之,重新拆分:对于每个函数符号(地址+长度),输出从指向函数代码的重定位递归可到达的函数 + 代码。您还必须复制数据重定位。完整的数据段可以放在自己的文件中。
  • 我不熟悉目标文件格式,但我会继续阅读。我不太明白您所说的“可从重定位递归访问的代码”是什么意思。您是否建议生成以符号为根的函数的 CFG?你建议的事情以前做过吗?或者是否有该过程的名称,以便我可以阅读更多内容?
猜你喜欢
  • 2010-09-12
  • 2020-09-14
  • 1970-01-01
  • 2013-12-02
  • 2019-06-27
  • 1970-01-01
  • 1970-01-01
  • 2012-11-01
  • 1970-01-01
相关资源
最近更新 更多