我知道,剥离后,符号表将被删除,但始终需要一些信息来链接动态库。因此,我想无论是否剥离了可执行文件,都会保留其他符号。
例如,动态符号表似乎总是被保留(实际上这是我的问题的一部分)。它包含了该程序中使用的来自动态库的所有函数名称。
实际上,获取一个已剥离的二进制文件并查看
readelf
的输出,将为您提供以下输出:我的问题的另一部分也将涉及如何使用这些动态符号。因为它们都指向零而不是有效地址。您会像
objdump
一样识别它们各自指向存储在PLT中的代码的链接。例如,在下面我从objdump -D
获得的转储中,我们可以看到.plt
部分已拆分,我认为这是由于符号的缘故,将其划分为与每个动态函数对应的小节,我想知道这是否来自另一个动态函数。我不知道的符号表,或者objdump
是否重建此信息(然后,我想知道如何):Symbol table '.dynsym' contains 5 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 FUNC GLOBAL DEFAULT UND puts@GLIBC_2.2.5 (2)
2: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __libc_start_main@GLIBC_2.2.5 (2)
3: 0000000000000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__
4: 0000000000000000 0 FUNC GLOBAL DEFAULT UND perror@GLIBC_2.2.5 (2)
编辑:感谢Igor的评论,我发现了不同的偏移量,可以重建
.rela.plt
中的信息(但是.rela.dyn
用于什么?)。Disassembly of section .plt:
0000000000400400 <puts@plt-0x10>:
400400: ff 35 6a 05 20 00 pushq 0x20056a(%rip)
400406: ff 25 6c 05 20 00 jmpq *0x20056c(%rip)
40040c: 0f 1f 40 00 nopl 0x0(%rax)
0000000000400410 <puts@plt>:
400410: ff 25 6a 05 20 00 jmpq *0x20056a(%rip)
400416: 68 00 00 00 00 pushq Relocation section '.rela.dyn' at offset 0x368 contains 1 entries:
Offset Info Type Sym. Value Sym. Name + Addend
000000600960 000300000006 R_X86_64_GLOB_DAT 0000000000000000 __gmon_start__ + 0
Relocation section '.rela.plt' at offset 0x380 contains 4 entries:
Offset Info Type Sym. Value Sym. Name + Addend
000000600980 000100000007 R_X86_64_JUMP_SLO 0000000000000000 puts + 0
000000600988 000200000007 R_X86_64_JUMP_SLO 0000000000000000 __libc_start_main + 0
000000600990 000300000007 R_X86_64_JUMP_SLO 0000000000000000 __gmon_start__ + 0
000000600998 000400000007 R_X86_64_JUMP_SLO 0000000000000000 perror + 0
x0
40041b: e9 e0 ff ff ff jmpq 400400 <puts@plt-0x10>
0000000000400420 <__libc_start_main@plt>:
400420: ff 25 62 05 20 00 jmpq *0x200562(%rip)
400426: 68 01 00 00 00 pushq q4312078qx1
40042b: e9 d0 ff ff ff jmpq 400400 <puts@plt-0x10>
0000000000400430 <__gmon_start__@plt>:
400430: ff 25 5a 05 20 00 jmpq *0x20055a(%rip)
400436: 68 02 00 00 00 pushq q4312078qx2
40043b: e9 c0 ff ff ff jmpq 400400 <puts@plt-0x10>
0000000000400440 <perror@plt>:
400440: ff 25 52 05 20 00 jmpq *0x200552(%rip)
400446: 68 03 00 00 00 pushq q4312078qx3
40044b: e9 b0 ff ff ff jmpq 400400 <puts@plt-0x10>
#1 楼
为了回答这个问题,我们首先要重新表述一下。真正的问题可以这样表示:什么是不能从ELF二进制文件中删除的符号?
确实,
strip
从ELF文件中删除了很多信息,但是可以做更多的事情(有关此信息,请参阅--strip-unneeded
中的选项strip
或程序sstrip
)。因此,我最初的问题更多是关于在ELF文件上进行了任何修改后都可以假定可执行文件中包含哪些符号。实际上,您只需要一种类型的符号为了保持所有发生的情况,我们将其称为动态符号(与静态符号相对)。它们与静态变量有些不同,因为我们永远不会事先知道它们将在内存中指向何处。确实,由于它们应该指向外部二进制对象(库,插件),因此二进制blob在进程运行时会动态加载到内存中,我们无法预测它将位于什么地址。
如果静态符号存储在
.symbtab
部分中,则动态符号有自己的部分称为.dynsym
。它们被分开保存以简化重定位操作(该操作将为每个动态符号提供精确的地址)。重定位操作还依赖于两个额外的表,它们是:.rela.dyn
:如果不使用PLT,则动态链接对象(数据或过程)的重定位。 /> .rela.plt
:PLT(过程链接表)中的元素列表,这些元素在动态链接期间可能会发生重定位(如果使用了PLT)。以某种方式,将所有元素放在一起,
.dynsym
,.rela.dyn
和.rela.plt
将允许修补初始内存(即,映射到ELF二进制文件中),以便动态符号指向正确的对象(数据或过程)。为了进一步说明动态符号的重定位过程,我在i386和amd64体系结构中构建了示例。
i386
Symbol table '.dynsym' contains 6 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 00000000 0 NOTYPE LOCAL DEFAULT UND
1: 00000000 0 FUNC GLOBAL DEFAULT UND perror@GLIBC_2.0 (2)
2: 00000000 0 FUNC GLOBAL DEFAULT UND puts@GLIBC_2.0 (2)
3: 00000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__
4: 00000000 0 FUNC GLOBAL DEFAULT UND __libc_start_main@GLIBC_2.0 (2)
5: 080484fc 4 OBJECT GLOBAL DEFAULT 15 _IO_stdin_used
Relocation section '.rel.dyn' at offset 0x28c contains 1 entries:
Offset Info Type Sym.Value Sym. Name
08049714 00000306 R_386_GLOB_DAT 00000000 __gmon_start__
Relocation section '.rel.plt' at offset 0x294 contains 4 entries:
Offset Info Type Sym.Value Sym. Name
08049724 00000107 R_386_JUMP_SLOT 00000000 perror
08049728 00000207 R_386_JUMP_SLOT 00000000 puts
0804972c 00000307 R_386_JUMP_SLOT 00000000 __gmon_start__
08049730 00000407 R_386_JUMP_SLOT 00000000 __libc_start_main
amd64
Symbol table '.dynsym' contains 5 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 FUNC GLOBAL DEFAULT UND puts@GLIBC_2.2.5 (2)
2: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __libc_start_main@GLIBC_2.2.5 (2)
3: 0000000000000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__
4: 0000000000000000 0 FUNC GLOBAL DEFAULT UND perror@GLIBC_2.2.5 (2)
Relocation section '.rela.dyn' at offset 0x368 contains 1 entries:
Offset Info Type Sym. Value Sym. Name + Addend
000000600960 000300000006 R_X86_64_GLOB_DAT 0000000000000000 __gmon_start__ + 0
Relocation section '.rela.plt' at offset 0x380 contains 4 entries:
Offset Info Type Sym. Value Sym. Name + Addend
000000600980 000100000007 R_X86_64_JUMP_SLOT 0000000000000000 puts + 0
000000600988 000200000007 R_X86_64_JUMP_SLOT 0000000000000000 __libc_start_main + 0
000000600990 000300000007 R_X86_64_JUMP_SLOT 0000000000000000 __gmon_start__ + 0
000000600998 000400000007 R_X86_64_JUMP_SLOT 0000000000000000 perror + 0
有关动态链接的一些有趣的网页和文章:
PLT和GOT-关键代码共享和动态库;
剥离共享库;
重定向共享ELF库中的函数;
ELF的艺术:分析和开发;
全局偏移表;
评论
感谢您的来信。但是,严格来说,这不取决于您是否链接到任何东西吗?我意识到,使用GCC,您必须克服困难,以实现这一目标,但这仍然是可能的。就我所知,您可以创建一个ELF文件,该文件实际上仅使用syscall而不是任何类型的外部库。这是否意味着您可以省去更多您必须始终使用的功能?
– 0xC0000022L♦
13年8月14日在19:58
是的,你是对的。但是,实际上,即使您没有在代码中调用libc函数,也将获得__libc_start_main函数和所有常规框架。要实现所需的功能,您需要将-nostdlib选项传递给编译器。然后,您将获得没有外部调用libc的原始ELF二进制文件。
–恐怖
13年8月14日在21:51
您在关于动态符号表的回答中说:“它们应该指向外部二进制对象”。但是,它们还可以定义文件中函数或数据的位置,这些位置将被导出以供您链接或链接的代码使用。通常,这在动态库中可见,但是您也可以在顶级可执行文件中看到它-例如,许多C ++程序将重新定义“ operator new”和“ operator delete”,它们将出现在动态符号表中,从而覆盖标准C ++库提供的内容。
– sheltond
16 Mar 16 '16 at 10:10
@sheltond:你是对的。实际上,我在日常使用中不会大量操作C ++二进制文件,因此我倾向于忘记它们的特殊性,但是,是的,我同意,它们显然会这样做。
–恐怖
16-3-16在11:53
评论
也打印重定位。啊,我没有首先注意到它!我想你的意思是.rela.dyn和.rela.plt。
.rela.dyn可能与动态链接有关-您会对uclibc.org/docs/SysV-ABI.pdf
的第4/5部分感兴趣