延迟绑定机制
延迟绑定(Lazy Binding)是动态链接器用来减少程序启动时间的一种技术。延迟绑定就是在函数第一次被调用的时候再和函数地址绑定。
延迟绑定涉及到plt表和got表,
plt表和got表
got表和plt表都是程序调用外部函数时,定位该函数需要使用到的表。
如果一个可执行文件需要调用定义在共享库中的任何函数,那么它就有自己的GOT和PLT。
got表(Global Offset Table)是全局偏移量表,这是链接器在执行链接时需要填充的部分,保存了所有外部符号的地址信息。
GOT表的四项内容:
- GOT[0] 是.dynamic段的装载地址,.dynamic段包含了动态链接器用来绑定过程地址的信息,比如符号的位置和重定位信息;
- GOT[1] 是动态链接器的标识link_map的地址;
- GOT[2] 包含动态链接器的延迟绑定代码_dl_runtime_resolve的入口点,用于得到真正的函数地址,回写到对应的got表中;
- 从 GOT[3] 开始就是函数的地址。
PLT表称为过程链接表(procedure linkage table)。
PLT表中存储的是GOT表中的地址。
PLT[0]是一个函数,这个函数的作用是通过GOT[1]和GOT[2]来正确绑定一个函数的正式地址到GOT表中来。
plt表只在程序调用函数之前有用,调用函数之后第二次执行这个函数就不会经过plt表。
为什么需要plt表?
plt表是为了实现延迟绑定出现的一层间接跳转,延迟绑定提高程序启动的效率,延迟绑定减少进程启动开销的原理则要通过延迟绑定整个机制来解释。
延时绑定机制
延迟绑定的调用过程如上图(这张图原出处不知道是哪里,这里是从GOT表和PLT表扒下来的)。
函数第一次被调用时,先进入plt表,plt中是三条指令:
jmp
push
jmp
1、第一个jmp指令跳往对应函数的.got.plt的入口,但是这个时候got表中还没有填充函数的真实地址。
2、所以从got表跳回到plt表中,继续执行push;jmp指令,跳回后需要进行push的操作,push是的值是对应函数在.got.plt入口的偏移;
3、第二个jmp指令跳回plt的开头,是一个push指令和一个jmp指令
4、plt头部的push指令压入栈的其实是 GOT[1] 的地址即linkmap的地址,jmp到 GOT[2] 即dl_runtime_resolve相关的函数,对动态函数进行地址解析和重定位,并且把函数真实地址回填到got表中
5、执行函数
运行展示
1、首次调用
第一次调用 func 函数时,首先会跳转到 PLT 执行 jmp *(func@got),由于该函数没被调用过,func@got 中的值不是 func 函数的地址,而是 PLT 中的 “preapre resolver” 指令的地址,所以会跳转到 “preapre resolver” 执行,接着会调用 _dl_runtime_resolve 解析 func 函数的地址,并将该函数真正的地址填充到 func@got,最后跳转到 func 函数继续执行代码。
2、 非首次调用
当再次调用 func 函数时,由于 func@got 中已填充正确的函数地址,此时执行 PLT 中的 jmp *(func@got) 即可成功跳转到 func 函数中执行。
ret2libc
0x01 原理及使用条件
ret2libc 即控制函数的执行 libc 库中的函数,通常是返回至某个函数的 plt 处或者函数的具体位置 (即函数对应的 got 表项的内容)。一般情况下,我们会选择执行 system(“/bin/sh”),故而此时我们需要知道 system 函数的地址。
0x02 攻击思路及payload
-
ret2libc攻击思路:
通过栈溢出漏洞泄露函数的真实地址,通过Libcsearcher或者现有的libc库计算出libc基址。
然后,构造合适的ROP链,通过相对偏移量大小不变的原理来调用libc中的system函数,并
传递"/bin/sh"字符串作为参数,从而获取shell权限。 -
payload构造思路:
栈溢出 ==> 调用puts、printf……函数(fun_plt_addr) ==> 返回main函数(根据被调用函数决
定带几个参数) ==> 参数中包括泄露got表地址(fun_got_addr) ==> 接收got表中的内容(该函
数的真实地址) ==> libc寻址找到后门函数地址 ==> 之前返回main函数了,再栈溢出一次
==> 调用后门函数提权
总结
通过漏洞泄露puts函数的真实地址,并计算出ibc基址。然后,构造合适的ROP链来调用system函数,并传递"/bin/sh"字符串作为参数,从而获取shell权限。
1.找到函数地址:首先,使用ELF模块获取目标二进制文件中函数的地址。在脚本中,通过e1f.p1t和e1f.got来获取puts函数的p1t和got表项的地址,以及encrypt和main函数的地址。
2.构造漏洞触发payload:在脚本中,通过构造一系列的字节串来构造payload。首先,用b’a’*(0x50+0x08)填充到缓冲区,然后利用pop_rdi_ret和puts_got地址,以及puts_pTt函数的地址,构造ROP链。最后,使用encrypt函
数的地址,将payload发送给目标程序。
3.泄露bc基址和获取系统函数地址:通过接收目标程序返回的数据,获取泄露的puts函数的地址。然后,使用Libcsearcher模块根据泄露的puts函数地址来搜索libc库,并计算出libc基址。最后,通过libc基址计算出system函数的地址和/bin/sh字符串的地址。
4.构造第二个payload:使用泄露的libc基址、ret指令地址、pop_rdi_ret指令地址和/bin/sh字符串地址,构造第二个payload。ROP链的顺序为:ret、pop_rdi_ret、/bin/sh、system、main。
5.发送第二个payload获取shell:将第二个payload发送给目标程序,成功获取到shell.