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

时间:2013-05-30 23:56:01

标签: reverse-engineering elf opcode relocation symbol-table

TL; DR

我试图将这个问题作为一个简短的问题,但这是一个复杂的问题所以它最终会变长。如果您可以回答这方面的任何部分或提供任何建议或提示或资源或任何内容,那将非常有帮助(即使您不直接解决我的所有问题)。我现在正撞在墙上。 :)

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

  • 我正在寻找有关如何处理重定位条目和更新部分数据中未解析符号的指导。我根本不知道如何处理我从重新安置和部分等中提取的所有信息。
  • 我也希望了解链接器遇到重定位时发生了什么。试图正确实现重定位方程并以正确的方式使用所有正确的值是非常具有挑战性的。
  • 当我遇到操作码,地址和符号等时,我需要了解如何处理它们。我觉得我错过了一些步骤。
  • 我觉得我不太清楚符号表条目如何与重定位相互作用。我应该如何使用符号的绑定,可见性,值和大小信息?
  • 最后,当我输出包含已解析数据和可执行文件使用的新重定位条目的文件时,数据全部不正确。我不确定如何跟踪所有重定位并提供所需的所有信息。可执行文件对我的期望是什么?

到目前为止我的方法

我正在尝试以特定的[未记录的]专有格式创建一个重定位文件,该格式主要基于ELF。我编写了一个工具,它接受一个ELF文件和一个部分链接文件(PLF)并处理它们以输出完全解析的rel文件。此rel文件用于根据需要加载/卸载数据以节省内存。该平台是一个32位PPC。一个问题是该工具是用c#编写的,但数据是针对PPC的,因此需要注意有趣的endian问题等。

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

所以发生的事情是:

  1. 从PLF复制部分数据以用于rel文件。我只对.init(无数据),。text,.ctors,.dtors,.rodata,.data,.bss(无数据)以及我们正在使用的其他自定义部分感兴趣。
  2. 迭代PLF中的.rela部分并读入Elf32_Rela条目。
  3. 对于每个条目,我提取r_offset,r_info和r_addend字段,并从r_info(符号和reloc类型)中提取相关信息。
  4. 从PLF的符号表中,我可以得到symbolOffset,symbolSection和symbolValue。
  5. 从ELF中,我得到了symbolSection的加载地址。
  6. 我计算int localAddress =(。relaSection.Offset + r_offset)。
  7. 我从r_offset的symbolSection内容中获取了uint relocValue。
  8. 现在我拥有了我需要的所有信息,因此我对reloc类型进行了切换并处理数据。这些是我支持的类型:
        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
  9. 现在是什么?我需要更新节数据并构建随播重定位条目。但我不明白该做什么以及如何做。
  10. 我这样做的全部原因是因为有一个旧的过时不支持的工具不支持使用自定义部分,这是该项目的关键要求(出于内存原因)。我们有一个自定义部分,其中包含一堆初始化代码(总计大约一个兆字节),我们要在启动后卸载它们。现有工具只是忽略该部分中的所有数据。

    因此,虽然制作支持自定义部分的自己的工具是理想的,但如果有任何明智的想法以实现这一目标的另一种方式,我全都耳朵!我们已经提出了将.dtor部分用于我们的数据的想法,因为它几乎是空的。但这很麻烦,如果它阻止干净关闭,可能无论如何都不会起作用。


    重新定位加示例代码

    当我处理重定位时,我正在处理ABI文档HERE中的方程式和信息(第4.13节,第80页)以及其他一些代码示例和博客我挖出的帖子。但这一切都让人感到困惑,而且并没有真正拼写出来,我发现的所有代码都有所不同。

    例如,

    • 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;
    

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


    遵循符号/数据/重定位等

    当我在hexeditor中查看数据时,我看到了一堆&#34; 48 00 00 01&#34;遍。我已经发现这是一个操作码,需要使用重定位信息进行更新(这特别适用于&#39; bl&#39;分支和链接),但我的工具并不适用于广大的操作其中大部分和我更新的内容都有错误的值(与过时工具的示例相比)。很明显,我错过了这个过程的某些部分。

    除了节数据外,还需要将其他重定位条目添加到rel文件的末尾。这些包括内部和外部的重新安置,但我还没有完全了解这些。 (两者之间的差异以及何时使用其中一种?)

    如果您在函数RuntimeDyldELF::processRelocationRef处查看this file的末尾附近,则会看到正在创建的某些重定位条目。他们还制作存根功能。我怀疑这对我来说是缺失的环节,但是它和泥浆一样清晰,我甚至没有跟踪它。

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

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


    部分资源:

    1. 有关重新定位的博客:http://eli.thegreenplace.net/2011/08/25/load-time-relocation-of-shared-libraries/
    2. 最后提到操作码:http://wiki.netbsd.org/examples/elf_executables_for_powerpc/
    3. 我的相关未回答的问题:ELF Relocation reverse engineering

    4. 呼!这是一个问题的野兽。恭喜你,如果你做到这一点。 :)提前感谢您提供的任何帮助。

3 个答案:

答案 0 :(得分:14)

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

有elf.h方便。你可以在互联网上找到它。

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

你从r_info获得的'symbol'实际上是一个驻留在另一个部分的符号表的索引。在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;

假设我们正在处理重新安置号码。

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-&gt; r_offset)。

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

r_addend只是A.有时编译器需要向reloc添加一个静态常量。

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似乎足够合理。

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

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

答案 1 :(得分:1)

尝试进入ELF规范。它大约需要60页,并且可以大大澄清事情。特别是第2部分,关于链接。

答案 2 :(得分:1)

只需回答以下问题:什么是搬迁?

编写汇编程序时,如果它是位置相关的,则假定该程序已装入内存的特定部分。并且,当您将程序加载到另一个内存地址时,所有与内存相关的寻址数据都必须更新,以便可以从新的内存区域正确加载。

例如:

加载A,B

有时B可以是一个地址,有时它只是纯常数数据。因此,只有编译器才会知道何时生成程序集。编译器将构造一个表,其中包含偏移量及其原始加载相对地址的所有条目,并假定将其固定为固定的全局地址。这称为重定位表。

它已链接到符号表,PLT表和GOT等:

enter image description here

http://blog.k3170makan.com/2018/10/introduction-to-elf-format-part-vi_18.html

https://reverseengineering.stackexchange.com/questions/1992/what-is-plt-got

What does the concept of relocation mean?

How does C++ linking work in practice?