ret2_dl_resolve

ret2_dl_resolve

GOT & PLT

动态链接需要考虑的两点

  • 需要存放外部函数的数据段(GOT)
  • 获取数据段存放函数地址的一小段额外代码(PLT)

PLT

PLT内容是一段代码,所以PLT位于代码段,不可更改

GOT

GOT是一个表,表里面存的是函数的地址。

GOT表项中还保存了三个公共表项

got[0]: 本ELF动态段.dynamic的装载地址
got[1]: 本ELF的link_map数据结构描述符地址
got[2]: dl_rumtime_resolve函数的地址

流程

pic

延迟重定向

在函数第一次调用前,got表项的内容都是链接器生成的,他的值指向对应的pltjmp代码的下一条指令。

1
2
3
4
1
2
3
4
jmp func@got
; ↓got指向的位置
push 0x0 ;或者其他数字
jmp <common@plt>

最后都会跳到<common@plt>【也就是plt[0]】中去执行代码。
这是动态链接做符号解析和重定位的公共入口。

  • common@plt内容
1
2
3
pushl 0x080496f0 ;将某个函数的got表地址压栈,调用完成后就把真正的函数地址写入这里
jmp *GOT[2] ;跳入能够解析动态链接库函数地址的函数
; 也就是dl_runtime_resolve函数

所有动态链接库函数在第一次调用时都会通过
xxx@plt ===> common@plt ===> dl_runtime_resolve() 完成调用。

dl_runtime_resolve

调用流程

pic

两个参数

  1. link_map的指针

包含了.dynamic的指针,使dl_runtime_resolve可以访问到这个段。

  1. offset
1
2
3
4
1
2
3
4
jmp func@got
; ↓got指向的位置
push 0x0 ;或者其他数字
jmp <common@plt>

这里push的数字其实是函数的ID,这个ID就是offset。

这也就解释了,dl_runtime_resolve如何知道是查找哪个函数。

攻击手段

改写.dynstr节的指针

让这个指针指向我们需要的函数名字符串,例如system字符串

  • 缺陷 只有在NO RELRO时候才有权限更改.dynstr

伪造结构体

通过栈溢出,让函数返回到<common@plt>中,利用伪造的参数达到调用函数的目的。

因为函数的执行过程就是不断地寻找结构体,从结构体中拿数据,而在函数源码中,并不会检查offset是否会过界,所以我们可以在可控制的地方伪造结构体来达到目的。

  1. 计算fake_rel和.rel.plt的距离偏移量作为offset,注意64位需要除以sizeof(ELF32_REL)

  2. 伪造一fake_rel,使他的rel_info字段成为0x******07,这个07是导入函数的参数。暂时不知道干嘛的,但最好不要改。

  3. ******是指fake_sym距离.dynsym偏移/sizeof(ELF32_SYM)

  4. fake_symst_name是fake_string距离.dynstr的偏移量。