reverse-engineering - 处理ELF重定位-了解重定位,符号,剖面数据以及它们如何协同工作

标签 reverse-engineering elf opcode relocation symbol-table

TL; DR

我试图将这个问题简短化,但是这是一个复杂的问题,因此最终导致问题冗长。如果您可以回答其中的任何部分,或者给出任何建议,技巧,资源或任何其他内容,那将非常有帮助(即使您没有直接解决我的所有问题)。我现在把头撞在墙上。 :)

这是我遇到的具体问题。请在下面阅读以获取更多信息。

  • 我正在寻找有关如何处理重定位条目和更新节数据中未解析符号的指南。我只是不明白该如何处理从重定位和各节等中获取的所有信息。
  • 我也希望了解链接器遇到重定位时发生的情况。试图正确实现重定位方程并以正确的方式使用所有正确的值是极具挑战性的。
  • 当我遇到操作码,地址和符号等时,我需要了解如何处理它们。我觉得我缺少一些步骤。
  • 我觉得我对符号表条目与重定位的交互方式不太了解。我应该如何使用符号的绑定(bind),可见性,值和大小信息?
  • 最后,当我输出包含已解析数据和可执行文件使用的新重定位条目的文件时,数据全部不正确。我不确定如何跟踪所有重定位并提供所有必要的信息。我对可执行文件有什么期望?


  • 我到目前为止的方法

    我正在尝试以很大程度上基于ELF的特定[未记录]专有格式创建重定位文件。我编写了一个工具,该工具接受ELF文件和部分链接的文件(PLF),并对其进行处理以输出完全解析的rel文件。该rel文件用于根据需要加载/卸载数据,以节省内存。该平台是32位PPC。一种麻烦之处在于该工具是使用C#为Windows编写的,但是数据是为PPC设计的,因此需要注意有趣的字节序问题等。

    我一直试图了解当用于解析未解析的符号等时如何处理重定位。到目前为止,我所做的是从PLF复制相关的节,然后为每个对应的.rela节,我解析条目并尝试修正节数据并根据需要生成新的重定位条目。但这就是我的困难所在。我不在这里,这种事情似乎通常是由链接器和加载器完成的,因此没有很多好的例子可以借鉴。但是我发现一些有用的东西,包括THIS ONE

    因此,正在发生的是:
  • 从PLF复制节数据以用于rel文件。我只对.init(无数据)、. text,.ctors,.dtors,.rodata,.data,.bss(无数据)以及我们正在使用的另一个自定义部分感兴趣。
  • 遍历PLF中的.rela部分,并读取Elf32_Rela条目。
  • 对于每个条目,我都拉出r_offset,r_info和r_addend字段,并从r_info中提取相关信息(符号和重定位类型)。
  • 从PLF的符号表中,我可以获取symbolOffset,symbolSection和symbolValue。
  • 从ELF中,我得到了symbolSection的加载地址。
  • 我计算int localAddress =(.relaSection.Offset + r_offset)。
  • 我从r_offset处symbolSection的内容中获得了uint relocValue。
  • 现在我有了所需的所有信息,因此我对重定位类型进行了切换并处理了数据。这些是我支持的类型:
    R_PPC_NONE
    R_PPC_ADDR32
    R_PPC_ADDR24
    R_PPC_ADDR16
    R_PPC_ADDR16_LO
    R_PPC_ADDR16_HI
    R_PPC_ADDR16_HA
    R_PPC_ADDR14
    R_PPC_ADDR14_BRTAKEN
    R_PPC_ADDR14_BRNTAKEN
    R_PPC_REL24
    R_PPC_REL14
    R_PPC_REL14_BRTAKEN
    R_PPC_REL14_BRNTAKEN
  • 现在呢??我需要更新节数据并建立伴随重定位条目。但是我不知道该做什么和怎么做。

  • 我这样做的全部原因是因为有一个旧的过时的不受支持的工具,该工具不支持使用自定义节,这是该项目的关键要求(出于内存原因)。我们有一个自定义部分,其中包含一堆初始化代码(总计约一兆),我们希望在启动后将其卸载。现有工具仅忽略该部分中的所有数据。

    因此,尽管制作自己的工具来支持自定义部分是理想的选择,但是如果有其他聪明的想法可以实现该目标,那么我非常高兴!我们已经浮现了使用.dtor节作为数据的想法,因为它几乎是空的。但这是一团糟,并且如果阻止彻底关闭,则可能无法正常工作。

    迁移以及示例代码

    当我处理重定位时,我正在使用ABI文档HERE(在第4.13节,第80ish页左右)以及我挖掘出的许多其他代码示例和博客文章中找到的方程式和信息。但这一切都令人困惑,而且没有真正阐明,我发现的所有代码在功能上都有些不同。

    例如,
  • R_PPC_ADDR16_LO-> half16:#lo(S + A)
  • R_PPC_ADDR14_BRTAKEN-> low14 *:(S + A)>> 2

  • 因此,当我看到这种代码时,该如何解密呢?

    这是一个示例(来自this source)
    case ELF::R_PPC64_ADDR14 : {
        assert(((Value + Addend) & 3) == 0);
        // Preserve the AA/LK bits in the branch instruction
        uint8_t aalk = *(LocalAddress+3);
        writeInt16BE(LocalAddress + 2, (aalk & 3) | ((Value + Addend) & 0xfffc));
    } break;
    
    case ELF::R_PPC64_REL24 : {
        uint64_t FinalAddress = (Section.LoadAddress + Offset);
        int32_t delta = static_cast<int32_t>(Value - FinalAddress + Addend);
        if (SignExtend32<24>(delta) != delta)
            llvm_unreachable("Relocation R_PPC64_REL24 overflow");
        // Generates a 'bl <address>' instruction
        writeInt32BE(LocalAddress, 0x48000001 | (delta & 0x03FFFFFC));
    } break;
    

    这是另一个示例(here)中的一些
    case R_PPC_ADDR32: /* word32 S + A */
        addr = elf_lookup(lf, symidx, 1);
        if (addr == 0)
            return -1;
        addr += addend;
        *where = addr;
        break;
    
    case R_PPC_ADDR16_LO: /* #lo(S) */
        if (addend != 0) {
            addr = relocbase + addend;
        } else {
            addr = elf_lookup(lf, symidx, 1);
            if (addr == 0)
                return -1;
        }
        *hwhere = addr & 0xffff;
        break;
    
    case R_PPC_ADDR16_HA: /* #ha(S) */
        if (addend != 0) {
            addr = relocbase + addend;
        } else {
            addr = elf_lookup(lf, symidx, 1);
            if (addr == 0)
                return -1;
        }
        *hwhere = ((addr >> 16) + ((addr & 0x8000) ? 1 : 0)) & 0xffff;
        break;
    

    还有另一个示例(from here)
    case R_PPC_ADDR16_HA:
        write_be16 (dso, rela->r_offset, (value + 0x8000) >> 16);
        break;
    case R_PPC_ADDR24:
        write_be32 (dso, rela->r_offset, (value & 0x03fffffc) | (read_ube32 (dso, rela->r_offset) & 0xfc000003));
        break;
    case R_PPC_ADDR14:
        write_be32 (dso, rela->r_offset, (value & 0xfffc) | (read_ube32 (dso, rela->r_offset) & 0xffff0003));
        break;
    case R_PPC_ADDR14_BRTAKEN:
    case R_PPC_ADDR14_BRNTAKEN:
        write_be32 (dso, rela->r_offset, (value & 0xfffc)
                                        | (read_ube32 (dso, rela->r_offset) & 0xffdf0003)
                                        | ((((GELF_R_TYPE (rela->r_info) == R_PPC_ADDR14_BRTAKEN) << 21)
                                        ^ (value >> 10)) & 0x00200000));
        break;
    case R_PPC_REL24:
        write_be32 (dso, rela->r_offset, ((value - rela->r_offset) & 0x03fffffc) | (read_ube32 (dso, rela->r_offset) & 0xfc000003));
        break;
    case R_PPC_REL32:
        write_be32 (dso, rela->r_offset, value - rela->r_offset);
        break;
    

    我真的很想了解这些人在这里做的魔术,以及为什么他们的代码看起来并不总是一样。我认为某些代码假设数据已经被正确屏蔽(对于分支等),而某些代码则没有。但是我一点都不了解。

    跟随符号/数据/重定位等

    当我查看十六进制编辑器中的数据时,我看到一堆“48 00 00 01”。我发现这是一个操作码,需要使用重定位信息进行更新(这专门针对“bl”分支和链接),但是我的工具无法在绝大多数工具和我执行的工具上运行更新中的值有误(与过时的工具示例相比)。显然,我缺少流程的某些部分。

    除了节数据外,还需要将其他重定位条目添加到rel文件的末尾。这些由内部和外部重定位组成,但是我对这些都还没有弄清楚。 (两者之间有什么区别,何时使用一种或另一种?)

    如果您在RuntimeDyldELF::processRelocationRef函数的this file末尾附近查看,则会看到一些正在创建的重定位条目。它们还具有存根功能。我怀疑这是我所缺少的链接,但它像泥泞一样清晰,我什至没有一点关注。

    当我在每个重定位条目中输出符号时,它们每个都有一个绑定(bind)/可见性[Global / Weak / Local] [Function / Object]以及一个值,一个大小和一个部分。我知道该部分位于符号所在的位置,值是该部分中符号的偏移量(或者它是虚拟地址吗?)。大小是符号的大小,但这重要吗?也许全局/弱/本地对于确定内部还是外部重定位很有用?

    也许我正在谈论创建的重定位表实际上是rel文件的符号表?也许此表将符号值从虚拟地址更新为节偏移量(因为这是可重定位文件中的值,而PLF中的符号表基本上在可执行文件中)?

    一些资源:
  • 关于重定位的博客:http://eli.thegreenplace.net/2011/08/25/load-time-relocation-of-shared-libraries/
  • 在结尾处提到操作码:http://wiki.netbsd.org/examples/elf_executables_for_powerpc/
  • 我的相关未解决问题:ELF Relocation reverse engineering


  • ew!那真是个问题。恭喜您取得了如此远的成就。 :)预先感谢您可以给我的任何帮助。

    最佳答案

    我偶然发现了这个问题,并认为它应该得到答案。

    方便使用elf.h。您可以在互联网上找到它。

    如您所知,每个RELA部分都包含Elf32_Rela条目的数组,但也与其他特定部分绑定(bind)。 r_offset是该其他部分的偏移量(在本例中为it works differently for shared libraries)。您会发现节标题具有一个名为sh_info的成员。这告诉您那是哪个部分。 (正如您所期望的那样,它是节标题表的索引。)

    您从r_info获得的“符号”实际上是另一个部分中符号表的索引。在您的RELA部分的标题中查找成员sh_link。

    符号表以Elf32_Sym的st_name成员的形式告诉您要查找的符号的名称。 st_name是字符串部分的偏移量。您从符号表的节标题的sh_link成员获得哪个节。抱歉,这会引起混淆。

    Elf32_Shdr *sh_table = elf_image + ((Elf32_Ehdr *)elf_image)->e_shoff;
    Elf32_Rela *relocs = elf_image + sh_table[relocation_section_index]->sh_offset;
    
    unsigned section_to_modify_index = sh_table[relocation_section_index].sh_info;
    char *to_modify = elf_image + sh_table[section_to_modify_index].sh_offset;
    
    unsigned symbol_table_index = sh_table[relocation_section_index].sh_link;
    Elf32_Sym *symbol_table = elf_image + sh_table[symbol_table_index].sh_offset;
    
    unsigned string_table_index = sh_table[symbol_table].sh_link;
    char *string_table = elf_image + sh_table[string_table_index].sh_offset;
    

    假设我们正在使用搬迁号码i。
    Elf32_Rela *rel = &relocs[i];
    Elf32_Sym *sym = &symbol_table[ELF32_R_SYM(rel->r_info)];
    char *symbol_name = string_table + sym->st_name;
    

    查找该符号的地址(假设symbol_name ==“printf”)。最终值将输入(to_modify + rel-> r_offset)。

    至于您链接的pdf的第79-83页上的表格,它告诉我们在该地址放置什么以及要写入多少字节。显然,我们刚刚获得的地址(在这种情况下为printf的地址)是其中大多数的一部分。它对应于表达式中的S。

    r_addend只是A。我猜有时候编译器需要向重定位添加静态常量。

    B是共享库的基地址,对于可执行程序,则为0,因为它们不会移动。

    因此,如果ELF32_R_TYPE(rel-> r_info)== R_PPC_ADDR32,我们得到S + A,并且字长为word32,那么我们将得到:
    *(uint32_t *)(to_modify + rel->r_offset) = address_of_printf + rel->r_addend;
    

    ...并且我们已成功执行了迁移。

    对于#lo,#hi等以及像low14这样的单词大小,我帮不上忙。我对PPC一无所知,但链接的pdf似乎足够合理。

    我也不知道存根函数。通常,链接时(至少动态地)不需要了解这些内容。

    我不确定是否已经回答了您所有的问题,但是您至少应该能够看到示例代码现在所做的事情。

    关于reverse-engineering - 处理ELF重定位-了解重定位,符号,剖面数据以及它们如何协同工作,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/16847741/

    相关文章:

    c++ - 编译时字符串加密

    c - 将程序集 'shl' 、 'OR' 、 'AND' 、 'SHR' 操作转换为 C 的引用?

    c - Hook 用户调用功能?

    编译 install.packages 时出现 R 错误的 ELF 类错误

    opcode - PPC 的 rdtsc 操作码的等价物是什么?

    assembly - NASM 生成的列表文件中操作码中括号的含义是什么?

    java - HibernateTools 逆向工程工具没有为生成器添加 Annotation

    gcc 忽略 -Wl,--dynamic-linker 开关

    linux - readelf 与 objdump : why are both needed

    assembly -/4在FF/4中是什么意思?