我在x86 arch中通过静态链接libc库为简单程序构建了一个可执行文件。该可执行文件的重定位表按预期为空:
$ readelf -r test There are no relocations in this file. $
当我为同一个程序构建可执行文件时,通过静态链接libc库,在x86_64 arch中,重定位表不为空:
$ readelf -r test Relocation section '.rela.plt' at offset 0x1d8 contains 12 entries: Offset Info Type Sym. Value Sym. Name + Addend 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可执行'test'时,我发现虚拟地址0x430210包含strcpy函数。在加载时,在位置0x6c2008找到的相应PLT条目从0x400326(下一条指令的虚拟地址,即设置解析器)更改为0x0x443cc0(一个名为__strcpy_sse2_unaligned的libc函数的虚拟地址)我不知道为什么它会被解析为另一个函数而不是strcpy?我假设它是strcpy的另一种变体。
完成这项分析之后,我意识到我错过了基本的观点“加载静态可执行文件时,动态链接器怎么会出现?”我没有找到.interp部分,所以动态链接器不能确定。然后我观察到,libc函数“__libc_csu_irel()”修改了PLT条目而不是动态链接器。
如果我的分析对任何人都更有意义,请告诉我这是什么意思。我很高兴知道背后的原因。
非常感谢!!!
答案 0 :(得分:2)
你是对的。这些重定位只是试图找出应该使用(不仅仅是)libc函数的实现。它们在链接时插入二进制文件的函数main
执行__libc_start_main
之前解析。
我将尝试解释此重定位类型的工作原理。
我使用此代码作为参考
//test.c
#include <stdio.h>
#include <string.h>
int main(void)
{
char tmp[10];
char target[10];
fgets(tmp, 10, stdin);
strcpy(target, tmp);
}
使用GCC 7.3.1编译
gcc -O0 -g -no-pie -fno-pie -o test -static test.c
重定位表(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
节标题(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
...
它表示.got.plt
部分位于地址0x69c000
。
重定位表中的每条记录都包含两个重要的信息offset和addend。加法是指向函数的指针(也称为间接函数),它不带参数并返回指向函数的指针。返回的指针位于重定位记录的偏移量上。
简单的重新定位解析器实现:
void reolve_reloc(uintptr_t* offset, void* (*addend)())
{
//addend is pointer to function
*offset = addend();
}
从这个答案开头的例子。重定位表中的最后一个加数指向地址0x416060
,即函数strcpy_ifunc
。请参阅反汇编的输出:
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
strcpy_ifunc
选择所有strcpy
实现的最佳替代方法,adn会在其上返回指针。在我的情况下,它返回地址0x430ef0
__strcpy_sse2_unaligned
。这个地址是0x69c018
的10 .glob.plt + 0x18
通常重新分配的第一个想法是所有这些东西都处理动态解释器(ldd
)。但在这种情况下,程序是静态链接的,.interp
部分是空的。在这种情况下,它在函数__libc_start_main
中解析,它是GLIBC的一部分。除了解决重定位问题之外,此函数还负责将命令行参数传递给main
并执行其他操作。
当我弄清楚我有最后一个问题时,__libc_start_main
如何访问重定位表保存在ELF标题中?第一个想法是它以某种方式打开正在运行的二进制文件来读取和处理它。当然这是完全错误的。如果查看可执行文件的程序头,您将看到类似这样的内容(readlef -l test
):
Type Offset VirtAddr PhysAddr
FileSiz MemSiz Flags Align
LOAD 0x0000000000000000 0x0000000000400000 0x0000000000400000
0x0000000000098451 0x0000000000098451 R E 0x200000
...
此标头中的偏移量偏离可执行文件的第一个字节。那么程序头中的第一项是将test
文件的第一个0x98451字节复制到内存中。但是在偏移量0x0上是ELF头。因此,对于代码段,它还会将ELF标头加载到内存中,__libc_start_main
可以轻松访问它。
答案 1 :(得分:1)
您可以查看“System V Application Binary Interface” AMD64架构处理器补充资料“ - 我在https://software.intel.com/sites/default/files/article/402129/mpx-linux64-abi.pdf
下找到了它如果您转到重定位部分(4.4),您将找到此RLD类型的文档以及计算方法的说明
R_X86_64_IRELATIVE 37个字类间接(B + A)
其中
goodluck - 顺便说一句,感谢sploitfun的精彩帖子; - )
答案 2 :(得分:0)
我不为什么将它解析为其他函数而不是strcpy?我认为它是strcpy的另一个变体。
glibc使用动态链接器在运行时为主机CPU选择最佳版本的strcpy,strlen,memcpy等。
实际的strcpy
函数是调度程序/选择器,用于检查CPU功能并进行设置,以便将来的调用直接达到适用于CPU的最佳版本。
我不确定静态链接仍然可以使用这种机制。
对于strcpy
,我认为__strcpy_sse2_unaligned
在现代CPU上可能仍然是最佳选择(如果没有AVX2版本)。
__strcpy_ssse3
使用SSSE3 palignr
进行对齐的加载和对齐的存储,即使src和dst相对于彼此未对齐。 (它有16个不同的循环,用于所有16种可能的相对对齐,因为palignr
将移位计数作为立即数,因此有点膨胀。)在Core2上可能不错,但后来CPU的未对齐装载/存储效率更高最好使用__strcpy_sse2_unaligned
实现。