GOT & PLT 易于理解的个人笔记

为什么我们用动态链接和GOT表

  1. 我们知道静态链接就没那么多事,直接把全部要用的函数都绑定在一起,各个变量和函数之间的偏移量当然能算出来。
  2. 但是这也恰恰是静态链接的缺点,相同的代码段反复调用真是太臃肿了!
  3. 于是我们决定把常用的库单独拿出来给大家用,我们还知道,.text是不可修改的,也就是运行的时候可不能再把引用的地址像链接器一样“填”在预留的“坑”里,所以我们还是得装模作样的告诉程序应该跳转到哪里,比如我要call puts,但是我确实不知道puts在哪里(因为动态链接发生在程序运行时),我也留个坑,叫做puts@got。
  4. 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的逻辑:

  1. 当我call puts@plt的时候,我跑到plt代码段执行这些动作:跳到got表,如果第一次调用没人帮我找,那么got表此时的地址就是plt刚刚跳的位置,又回到了plt!
  2. 那么就继续吧!先压入我要找的函数id,再jmp到plt的公共代码段。
  3. 公共代码段对于所有plt操作都是一样的,那就是呼唤我们熟悉的got[2]来帮我们找函数。
  4. got[2]找好以后,它会填入got表中,那么下次再执行步骤1的时候,got表此时的位置就是puts的位置!
  5. 现在又要用到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

具体流程

  1. 第一次调用_dl_runtime_resolve(*link_map,reloc_index)
  2. 用link_map查找.dynstr,.dynsym,.rel.plt的地址
  3. .rel.plt+reloc_index即可求出当前函数重定位表项Elf32_Rel的指针,记作rel
  4. rel->r_info>>8=n,以n为下标在.dynsym中找到Elf32_Sym指针,记作sym
  5. dynstr+sym->st_name得到符号名字符串指针
  6. 在动态链接库找到这个函数的地址,把地址赋给*rel->r_offset,即GOT表
  7. 调用函数

终于弄懂了dl_runtime_resolve函数的整个流程

pwn_dl_runtime_resolve文章
写的很详细

学了好久,因为涉及的名词太多了。我将像上面一样形象的讲解,然后再使用gdb自己跟踪一遍就很快理解了:
这次我们自顶向下地讲解:
首先,我们的目标是找到puts函数的地址,但是怎么找到这个地址呢?那么我们就需要该函数的名字,我们要用名字去找地址。好,现在我们的目标变成了,我们要找到这个函数的名字。
我们在下文将dl_runtime_resolve函数称为“搜索函数”:

  1. 调用搜索函数时压入的参数代表是.rel.plt的第几项。
  2. .rel.plt的offset告诉搜索函数届时把最终puts的地址放在哪儿;info节告诉搜索函数自己拜托它找的函数位于.dynsym中第几个Elf_Sym结构体,同时info节还能告诉你这是个什么type的东西(32位和64位不一样,我的64位是高32位是id)
  3. 找到的Elf_Sym结构体的第一项叫做st_name,这个是.strtab中的偏移,查看这个是不是你找的东西。第二项叫做st_value,就对应着库地址的偏移,也就是说库地址基址加上该偏移,就是绝对地址。
  4. 然后我们的搜索函数帮你把这个绝对地址填在最开始的.rel.plt的offset处存放,其实这里就是got表的位置。
posted @ 2024-08-11 17:42  muyiGin  阅读(23)  评论(0编辑  收藏  举报