我正在查看静态链接的 linux x86 剥离二进制文件。我注意到有.got
和.plt
部分。
我想知道静态链接的二进制文件需要什么got
和plt
部分?任何人 ?
我正在查看静态链接的 linux x86 剥离二进制文件。我注意到有.got
和.plt
部分。
我想知道静态链接的二进制文件需要什么got
和plt
部分?任何人 ?
关于ELF
二进制文件在内部是如何工作的,程序员有很多不知道的事情。而且,不幸的是,除了广泛涵盖该主题的两三个之外,几乎没有可靠的参考资料。许多工具(链接器、加载器、汇编器、调试器等)对你们大多数人来说仍然是个谜。当涉及到连接体和装载机,主要参考是链接器和加载由John R.莱文(http://linker.iecc.com/)。另一个可靠的信息来源是官方的ELF
二进制格式文档。但这些只是对某些或大多数技术如何工作的介绍。
现在,这是您问题的答案(为什么GOT
和PLT
部分仍包含在静态ELF
二进制文件中?):PERFORMANCE。
更多解释...假设你有这个 C 代码:
#include <stdio.h>
#include <string.h>
int main(int argc, char **argv)
{
char str[1024];
strcpy(str, argv[1]);
printf("%s\n", str);
return 0;
}
无需成为天才就可以弄清楚它所做的只是将命令行参数复制到字符串中并打印出来。这是main
汇编中的函数:
000000000040105e <main>:
40105e: 55 push rbp
40105f: 48 89 e5 mov rbp,rsp
401062: 48 81 ec 10 04 00 00 sub rsp,0x410
401069: 89 bd fc fb ff ff mov DWORD PTR [rbp-0x404],edi
40106f: 48 89 b5 f0 fb ff ff mov QWORD PTR [rbp-0x410],rsi
401076: 48 8b 85 f0 fb ff ff mov rax,QWORD PTR [rbp-0x410]
40107d 48 83 c0 08 add rax,0x8
401081: 48 8b 10 mov rdx,QWORD PTR [rax]
401084: 48 8d 85 00 fc ff ff lea rax,[rbp-0x400]
40108b: 48 89 d6 mov rsi,rdx
40108e: 48 89 c7 mov rdi,rax
401091: e8 3a f2 ff ff call 4002d0 <__rela_iplt_end+0x38>
401096: 48 8d 85 00 fc ff ff lea rax,[rbp-0x400]
40109d: 48 89 c7 mov rdi,rax
4010a0: e8 fb 09 00 00 call 401aa0 <_IO_puts>
4010a5: b8 00 00 00 00 mov eax,0x0
4010aa: c9 leave
4010ab: c3 ret
4010ac: 0f 1f 40 00 nop DWORD PTR [rax+0x0]
请注意,在地址处,401091
您调用了存储在 中的函数PLT
(标签更具表现力)。令人惊讶的是,在这个地址4002d0
你会发现一个跳转到存储在GOT
(见下文)中的内容。
4002d0: ff 25 f2 2f 2c 00 jmp QWORD PTR [rip+0x2c2ff2] # 6c32c8 <_GLOBAL_OFFSET_TABLE_+0x20>
在 中的那个确切位置GOT
,您会发现对存储在以下部分中的函数的调用:
00000000004187d0 <handle_amd>:
4187d0: 53 push rbx
4187d1: b8 00 00 00 80 mov eax,0x80000000
4187d6: 0f a2 cpuid
4187d8: 81 ff c4 00 00 00 cmp edi,0xc4
4187de: 7f 40 jg 418820 <handle_amd+0x50>
4187e0: 31 d2 xor edx,edx
4187e2: 81 ff bf 00 00 00 cmp edi,0xbf
4187e8: 0f 9d c2 setge dl
4187eb: 81 ea fb ff ff 7f sub edx,0x7ffffffb
4187f1: 39 c2 cmp edx,eax
4187f3: 77 2b ja 418820 <handle_amd+0x50>
4187f5: 89 d0 mov eax,edx
4187f7: 0f a2 cpuid
4187f9: 81 ff bb 00 00 00 cmp edi,0xbb
4187ff: 7e 27 jle 418828 <handle_amd+0x58>
418801: 81 ef bc 00 00 00 sub edi,0xbc
418807: 83 ff 08 cmp edi,0x8
41880a: 0f 87 48 01 00 00 ja 418958 <handle_amd+0x188>
418810: 48 8d 35 c9 0b 08 00 lea rsi,[rip+0x80bc9] # 4993e0 <__PRETTY_FUNCTION__.4767+0x20>
418817: 48 63 04 be movsxd rax,DWORD PTR [rsi+rdi*4]
41881b: 48 01 c6 add rsi,rax
41881e: ff e6 jmp rsi
418820: 31 c0 xor eax,eax
418822: 5b pop rbx
418823: c3 ret
首先,查看分区的名称。其次,如果您仔细查看代码,您会注意到该函数识别 CPU - 通过剖析cpuid
指令 ( 4187d6
and 4187f7
)的返回值- (更准确地说是微架构和其他功能,例如缓存大小,...)您正在运行ELF
二进制文件,然后决定哪种实现套件最适合该配置。这样strcpy
,无论您使用何种架构(英特尔:Nehalem、Sandy Bridge、Ivy Bridge、Haswell、...;AMD:Phenom、Opteron、... ; ...) 请记住,这些快速实现已经针对每个可能的目标架构进行了手动优化和微调。
这就是静态二进制文件中使用PLT
和GOT
部分的目的ELF
。
现在,如果您想自己调查这个问题,您应该C
使用-static
和-g3
(调试符号)标志使用 GCC 4.9 版(这是我使用的版本)编译上面的代码。然后,使用objdump
和-D
开关反汇编二进制文件以获得所有ELF
部分。然后,您可以浏览所有部分并探索汇编代码。您还可以使用运行二进制文件gdb
并在关键位置设置断点并逐步运行程序。
@yaspr 的回答很好,因为这个问题得到了一些“从可信和/或官方来源寻找答案”的赏金,让我在这里尝试提供一些参考。
一般来说,在我的理解,.PLT
并.GOT
表要求在这里由于性能问题。
BinCFI发表于去年排名前 2 的计算机安全会议。
由于 PLT 存根的目的是调度跨模块调用,因此目标似乎只能从其他模块导出符号。然而,最近版本的 gcc 支持一种新的函数类型,称为 gnu 间接函数,它允许一个函数有许多不同的实现,在运行时根据 CPU 类型等因素选择最合适的一个。目前,许多 glibc 低级函数如 memcpy、strcmp和strlen 都使用此功能。为了支持此功能,库导出了一个选择器函数,该函数在运行时选择将使用众多实现中的哪一个。这些实现函数可能根本不导出。
此处列出了有关如何利用此功能的其他一些参考资料。