GOT & PLT 易于理解的个人笔记
为什么我们用动态链接和GOT表
- 我们知道静态链接就没那么多事,直接把全部要用的函数都绑定在一起,各个变量和函数之间的偏移量当然能算出来。
- 但是这也恰恰是静态链接的缺点,相同的代码段反复调用真是太臃肿了!
- 于是我们决定把常用的库单独拿出来给大家用,我们还知道,.text是不可修改的,也就是运行的时候可不能再把引用的地址像链接器一样“填”在预留的“坑”里,所以我们还是得装模作样的告诉程序应该跳转到哪里,比如我要call puts,但是我确实不知道puts在哪里(因为动态链接发生在程序运行时),我也留个坑,叫做puts@got。
- got表记录全局偏移,也就是真的存放的位置,放在.data节中所以可以编辑。你只需要知道前三个表项分别是:got[0]保存了.dynamic段的地址,这其中描述了本模块动态链接的相关信息;got[1]保存了该模块的id; got【2】就是真正帮你找你要的函数在哪个地址的,比如他找到了puts在地址0x12345678处,它就填在got[3]。那么以后看到puts@got就直接跳到got[3]的地址(这个id的分配是链接器规划好的)。
为什么我们要延迟绑定和plt表
看起来已经完全解决问题了呀!通过got解决了.text不能编辑的问题,还可以“位置无关”,多棒!
但是请你想象一下,当你执行一个程序,他引用了100000个函数,现在他正在靠got[2]的查找函数一个一个查找......
简直是灾难!很明显正常人都能想到,为什么不“用到的时候”再找呢?你一开头找那么久干什么?万一有个if-else,其中一个分支引用了99%的外部函数,结果我根本就没进入这个分支......
好!现在让我们看下plt的逻辑:
- 当我call puts@plt的时候,我跑到plt代码段执行这些动作:跳到got表,如果第一次调用没人帮我找,那么got表此时的地址就是plt刚刚跳的位置,又回到了plt!
- 那么就继续吧!先压入我要找的函数id,再jmp到plt的公共代码段。
- 公共代码段对于所有plt操作都是一样的,那就是呼唤我们熟悉的got[2]来帮我们找函数。
- got[2]找好以后,它会填入got表中,那么下次再执行步骤1的时候,got表此时的位置就是puts的位置!
- 现在又要用到puts了!我call puts@plt,跳到puts@plt代码段,然后又跳到got表,然后发现居然有人帮我们找好了!
以上内容我相信能帮你轻松的了解plt got的机制,一些专业名词你可以自己搜索。原理懂了看起来方便很多的!
_dl_runtime_resolve的流程
基础知识
_dl_runtime_resolve(*link_map,reloc_index)
- GOT[0]:.dynamic节。保存elf文件依赖哪些动态库运行?包含动态符号节的信息。
- GOT[1]:link_map的地址。
- GOT[2]:_dl_runtime_resolve地址
- .dynsym:动态链接符号表。可以通过
objdump -s -j .dynsym ./test
查看。 - .dynstr:用于动态链接的字符串,这些字符串代表与符号表相关的名称。可以通过
objdump -s -j .dynstr ./test
查看。 - .rel.plt:重定位表。每个项对应一个导入函数。
各个segment之间的关系:
.dynamic地址+0x44是.dynstr
.dynamic+0x4c是.dynsym
.dynamic+0x84是.rel.plt
具体流程
- 第一次调用
_dl_runtime_resolve(*link_map,reloc_index)
- 用link_map查找
.dynstr
,.dynsym
,.rel.plt
的地址 .rel.plt+reloc_index
即可求出当前函数重定位表项Elf32_Rel的指针,记作rel- rel->r_info>>8=n,以n为下标在.dynsym中找到Elf32_Sym指针,记作sym
- dynstr+sym->st_name得到符号名字符串指针
- 在动态链接库找到这个函数的地址,把地址赋给*rel->r_offset,即GOT表
- 调用函数
终于弄懂了dl_runtime_resolve函数的整个流程
pwn_dl_runtime_resolve文章
写的很详细
学了好久,因为涉及的名词太多了。我将像上面一样形象的讲解,然后再使用gdb自己跟踪一遍就很快理解了:
这次我们自顶向下地讲解:
首先,我们的目标是找到puts函数的地址,但是怎么找到这个地址呢?那么我们就需要该函数的名字,我们要用名字去找地址。好,现在我们的目标变成了,我们要找到这个函数的名字。
我们在下文将dl_runtime_resolve函数称为“搜索函数”:
- 调用搜索函数时压入的参数代表是.rel.plt的第几项。
- .rel.plt的offset告诉搜索函数届时把最终puts的地址放在哪儿;info节告诉搜索函数自己拜托它找的函数位于.dynsym中第几个Elf_Sym结构体,同时info节还能告诉你这是个什么type的东西(32位和64位不一样,我的64位是高32位是id)
- 找到的Elf_Sym结构体的第一项叫做st_name,这个是.strtab中的偏移,查看这个是不是你找的东西。第二项叫做st_value,就对应着库地址的偏移,也就是说库地址基址加上该偏移,就是绝对地址。
- 然后我们的搜索函数帮你把这个绝对地址填在最开始的.rel.plt的offset处存放,其实这里就是got表的位置。
本文来自博客园,作者:muyiGin,转载请注明原文链接:https://www.cnblogs.com/muyiGin/p/18353677