xo.01 延迟绑定原理
延迟绑定(Lazy Binding)是动态链接器用来减少程序启动时间的一种技术。延迟绑定就是在函数第一次被调用的时候再和函数地。
xo.02 plt表和got表
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表里面存放的是代码(jmp,push,jmp ),还有的说存放的是got表项的地址,还有说 PLT[0] 存放指令, PLT[1] 之后存放got表项地址。
plt表是为了实现延迟绑定出现的一层间接跳转,延迟绑定提高程序启动的效率,延迟绑定减少进程启动开销的原理则要通过延迟绑定整个机制来解释。
xo.03延迟绑定
首次调用
第一次调用 func 函数时,首先会跳转到 PLT 执行 jmp *(func@got)
,由于该函数没被调用过,func@got 中的值不是 func 函数的地址,而是 PLT 中的 “preapre resolver” 指令的地址,所以会跳转到 “preapre resolver” 执行,接着会调用 _dl_runtime_resolve 解析 func 函数的地址,并将该函数真正的地址填充到 func@got,最后跳转到 func 函数继续执行代码。
非首次调用
当再次调用 func 函数时,由于 func@got 中已填充正确的函数地址,此时执行 PLT 中的 jmp *(func@got)
即可成功跳转到 func 函数中执行。
0x04 攻击思路及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.
例题
一位大神老师写的解题思路
当我们程序中缺少一个关键参数(字符串)时,我们可以在栈溢出的基础上调用输入函数,手动向程序中输入这个字符串(比如说’/bin/sh’)
一个题目中没有system函数以及没有’/bin/sh’字符串的情况下,往往就要考虑到ret2libc
第一步:泄漏,跳回到溢出函数
第二步:再次触发溢出,跳转到libc中执行system(‘/bin/sh’)
调用输出函数泄漏got表:
32位下:payload = padding + p32(elf.plt[‘write’]) + p32(overflorw_addr) + p32(1) + p32(elf.got[‘write’]) + p32(4)
(注意32位程序传参在栈上)
64位下:payload = padding + p64(pop_rdi_ret) + p64(1) + p64(pop_rsi_r15_ret) + p64(elf.got[‘write’]) + p64(0) + p64(elf.plt[‘write’]) + p64(overflow_addr)
以上两个payload实现的都是调用——write(1,elf.got[‘write’],?),并且跳回到了溢出函数的地址
跳回到溢出函数是因为要再次触发栈溢出,调用system(‘/bin/sh’)
接收泄漏的数据:
64位下:write_libc_addr = u64(p.recv(6).ljust(8,‘’\x00’))
32位下:write_libc_addr = u32(p.recv(4))
计算libc基地址:
libc_base_addr = write_libc_addr - libc.symbols[‘write’]
system_addr = libc_base_addr + libc.symbols[‘system’]
binsh = libc_base_addr + libc.search(‘/bin/sh\x00’).next()