4

我通过在 x86 架构中静态链接 libc 库为一个简单的程序构建了一个可执行文件。该可执行文件的重定位表如预期的那样为空:

$ readelf -r 测试
此文件中没有重定位。
$

当我为同一个程序构建一个可执行文件时,通过静态链接 libc 库,在 x86_64 架构中,重定位表不为空:

$ readelf -r 测试

偏移 0x1d8 处的重定位节“.rela.plt”包含 12 个条目:

  偏移信息类型 Sym。价值符号。姓名+加号
0000006c2058 000000000025 R_X86_64_IRELATIV 000000000042de70
0000006c2050 000000000025 R_X86_64_IRELATIV 00000000004829d0
0000006c2048 000000000025 R_X86_64_IRELATIV 000000000042dfe0
0000006c2040 000000000025 R_X86_64_IRELATIV 000000000040a330
0000006c2038 000000000025 R_X86_64_IRELATIV 0000000000432520
0000006c2030 000000000025 R_X86_64_IRELATIV 0000000000409ef0
0000006c2028 000000000025 R_X86_64_IRELATIV 0000000000445ca0
0000006c2020 000000000025 R_X86_64_IRELATIV 0000000000437f40
0000006c2018 000000000025 R_X86_64_IRELATIV 00000000004323b0
0000006c2010 000000000025 R_X86_64_IRELATIV 0000000000430540
0000006c2008 000000000025 R_X86_64_IRELATIV 0000000000430210
0000006c2000 000000000025 R_X86_64_IRELATIV 0000000000432400
$

我用谷歌搜索了重定位类型“R_X86_64_IRELATIV”,但我可以找到有关它的任何信息。那么有人可以告诉我这是什么意思吗?

我想如果我用 gdb 调试可执行文件,我可能会找到答案。但实际上它带来了很多问题:) 这是我的一点分析:

上表中的 Sym.Name 字段列出了一些 libc 函数的虚拟地址。当我 objdump'd 可执行“测试”时,我发现虚拟地址 0x430210 包含 strcpy 函数。在加载在位置 0x6c2008 找到的相应 PLT 条目时,从 0x400326(下一条指令的虚拟地址,即设置解析器)更改为 0x0x443cc0(名为 __strcpy_sse2_unaligned 的 libc 函数的虚拟地址)我不知道为什么它会被解析为不同的函数而不是strcpy?我假设它是 strcpy 的不同变体。

完成此分析后,我意识到我错过了前面的基本点“加载静态可执行文件时动态链接器如何出现?” 我没有找到 .interp 部分,因此肯定不涉及动态链接器。然后我观察到,libc 函数“__libc_csu_irel()”修改了 PLT 条目而不是动态链接器。

如果我的分析对任何人都更有意义,请让我知道它的全部内容。我很高兴知道这背后的原因。

非常感谢!!!

4

3 回答 3

4

TL;DR

You are right. Those relocations just trying to find out what implementation of (not only) libc functions should be used. They are resolved before the main is executed by the function __libc_start_main inserted in the binary at the linking time.


I will try to explain how this relocation type works.

The example

I am using this code as reference

//test.c
#include <stdio.h>
#include <string.h>

int main(void)
{
    char tmp[10];
    char target[10];
    fgets(tmp, 10, stdin);
    strcpy(target, tmp);
}

compiled with GCC 7.3.1

gcc -O0 -g -no-pie -fno-pie -o test -static test.c

The shorten output of relocation table (readelf -r test):

Relocation section '.rela.plt' at offset 0x1d8 contains 21 entries:
  Offset          Info           Type           Sym. Value    Sym. Name + Addend
...
00000069bfd8  000000000025 R_X86_64_IRELATIV                    415fe0
00000069c018  000000000025 R_X86_64_IRELATIV                    416060

The shorten output of the section headers (readelf -S test):

[Nr] Name              Type             Address           Offset
     Size              EntSize          Flags  Link  Info  Align
...
[19] .got.plt          PROGBITS         000000000069c000  0009c000
     0000000000000020  0000000000000008  WA       0     0     8
...

It says that .got.plt section is on the address 0x69c000.

How is R_X86_64_IRELATIV relocation resolved

Every record in the relocation table contains two important information offset and addend. In the words the addend is pointer to function (also called indirect function) which takes no arguments and returns pointer to function. The returned pointer is placed on the offset from the relocation record.

Simple realocation resolver implementation:

void reolve_reloc(uintptr_t* offset, void* (*addend)())
{
    //addend is pointer to function
    *offset = addend();
}

From the example at the start of this answer. The last addend from the relocation table points to the address 0x416060 which is function strcpy_ifunc. See the output from disassembly:

0000000000416060 <strcpy_ifunc>:
  416060:       f6 05 05 8d 28 00 10    testb  $0x10,0x288d05(%rip)        # 69ed6c <_dl_x86_cpu_features+0x4c>
  416067:       75 27                   jne    416090 <strcpy_ifunc+0x30>
  416069:       f6 05 c1 8c 28 00 02    testb  $0x2,0x288cc1(%rip)        # 69ed31 <_dl_x86_cpu_features+0x11>
  416070:       75 0e                   jne    416080 <strcpy_ifunc+0x20>
  416072:       48 c7 c0 70 dd 42 00    mov    $0x42dd70,%rax
  416079:       c3                      retq   
  41607a:       66 0f 1f 44 00 00       nopw   0x0(%rax,%rax,1)
  416080:       48 c7 c0 30 df 42 00    mov    $0x42df30,%rax
  416087:       c3                      retq   
  416088:       0f 1f 84 00 00 00 00    nopl   0x0(%rax,%rax,1)
  41608f:       00 
  416090:       48 c7 c0 f0 0e 43 00    mov    $0x430ef0,%rax
  416097:       c3                      retq   
  416098:       0f 1f 84 00 00 00 00    nopl   0x0(%rax,%rax,1)
  41609f:       00 

The strcpy_ifunc pick the best alternative of all strcpy implementations adn returns pointer on it. In my case it return address 0x430ef0 which is __strcpy_sse2_unaligned. This address is ten put at 0x69c018 which is at .glob.plt + 0x18

Who and when resolve it

Usually the first thought with reallocation is that all this stuff handles dynamic interpreter (ldd). But in this case the program is statically linked and the .interp section is empty. In this case it resolved in the function __libc_start_main which is part of the GLIBC. Except solving relocation this function also take care of passing command line argument to your main and do some other stuff.

Access to the relocation table

When I figure it out i had last question, how the __libc_start_main access the relocation table saved in the ELF headers? The first thought was it somehow opens the running binary for reading and process it. Of course this is totally wrong. If you look at the program header of the executable you will see something like this (readlef -l test):

Type           Offset             VirtAddr           PhysAddr
               FileSiz            MemSiz              Flags  Align
LOAD           0x0000000000000000 0x0000000000400000 0x0000000000400000
               0x0000000000098451 0x0000000000098451  R E    0x200000
...

The offset in this header is offset from the first byte of the executable file. So what the first item in the program header says is copy first 0x98451 bytes of the test file into memory. But on the offset 0x0 is ELF header. So with code segment it will also load ELF headers into memory and __libc_start_main can easily access it.

于 2018-04-05T07:32:25.943 回答
2

你可以看看“System V Application Binary Interface AMD64 Architecture Processor Supplement”——我在https://software.intel.com/sites/default/files/article/402129/mpx-linux64-abi.pdf下找到的

如果您转到重定位部分 (4.4),您将找到此 RLD 类型的文档以及计算方法的说明

R_X86_64_IRELATIVE 37 词类间接(B + A)

在哪里

  • wordclass 为 LP64 指定 word64,为 ILP32 指定 word32。
  • A 表示用于计算可重定位字段值的加数。
  • B 表示在执行期间共享对象被加载到内存中的基地址。一般共享对象是用0基虚拟地址构建的,但是执行地址会有所不同。

祝你好运-顺便说一句,感谢您在 sploitfun 的精彩帖子;-)

于 2015-02-11T11:00:56.230 回答
0

我不为什么它被解析为不同的函数而不是 strcpy?我假设它是 strcpy 的不同变体。

glibc 使用动态链接器在运行时为主机 CPU 选择最佳版本的 strcpy、strlen、memcpy 等。

实际strcpy功能是检查 CPU 功能并进行设置的调度程序/选择器,以便将来的调用直接转到最适合您的 CPU 的版本。我不确定这种机制中有多少仍然适用于静态链接,这表明确实如此。

对于strcpy,我认为__strcpy_sse2_unaligned在现代 CPU 上可能仍然是最佳选择(如果没有 AVX2 版本)。

__strcpy_ssse3使用 SSSE3palignr进行对齐加载和对齐存储,即使 src 和 dst 相对于彼此未对齐。(它有 16 个不同的循环,用于所有 16 种可能的相对对齐,因为palignr将移位计数作为立即数,所以它有点臃肿。)它在 Core2 上可能很好,但后来的 CPU 在硬件中具有更高效的未对齐加载/存储可能是最好的与__strcpy_sse2_unaligned实施。

于 2018-06-20T14:25:49.230 回答