跳到主要内容

Off-by-null 的利用方法

· 阅读需 20 分钟
Muel - Nova
Anime Would PWN This WORLD into 2D

不知道为什么以前都不把这些记录下来,然后每次做到的时候都会忘了从零开始学。

本文将主要介绍 2.23 和 2.31 版本下的利用,2.27 和 2.23 差不多,多填一个 tcache 即可。2.29 和 2.31 也差不多,多了一个 key。

因此,阅读本文你需要对 Heap 的分配有一定的基础,本文将更多涉及方法而非原理。

本文涉及的挑战主要有以下几个特征

  • 存在 off-by-null
  • 分配次数几乎不受限
  • 分配大小几乎不受限或是能分配 largebin 范围
  • 不存在 edit 函数或只能 edit 一次
  • 只能 show 一次(按理来说不能 show 也可以,但是堆块分配太麻烦了,做这种题不如睡觉)

off-by-null

一般来说 off by null 有以下几种可能

  • strcpy 之类会往末尾额外添加 \x00 的函数
  • 一个循环中 read(0, buf, 1) 然后判断 *buf = 0xA ? break,在循环外 *buf = 0 的情况

而它仅仅能造成 1 字节的越界写,且只能填写 \x00,因此这种攻击原语能力小于 off-by-one。

一般而言,我们都会用这个攻击原语修改 chunk 的 prev_inuse 位,或是修改 fdbk 指针等使其指向一个最低字节为 \x00 的堆块,进而利用 malloc_consolidate 造成堆块重叠 (chunk overlapping) 或是利用 fd 指针使得空闲链表指向一个已分配的堆块,从而达到 UAF 的效果。

源码分析

malloc_consolidate

在 free 堆块大于 get_max_fast() 时,就会进入 consolidate 过程,在这里,我们的 backward 指的是低地址的堆块,forward 则是高地址的堆块。

backward_consolidate

可以看到,对于当前 free 的堆块 p,它首先检查自己的 prev_inuse 位,如果为 0,那么就根据 prev_size 找到上一个堆块,然后对上一个堆块进行 unlink 操作。

malloc/malloc.c
/* consolidate backward */
if (!prev_inuse(p)) {
prevsize = p->prev_size;
size += prevsize;
p = chunk_at_offset(p, -((long) prevsize));
unlink(av, p, bck, fwd);
}

if (nextchunk != av->top) {
/* get and clear inuse bit */
nextinuse = inuse_bit_at_offset(nextchunk, nextsize);

/* consolidate forward */
if (!nextinuse) {
unlink(av, nextchunk, bck, fwd);
size += nextsize;
} else
clear_inuse_bit_at_offset(nextchunk, 0);

forward_consolidate

接着,在下一个 chunk 不是 topchunk 的情况下,它检查下一个 chunk 的再下一个 chunk 的 prev_inuse 位,来确定下一个 chunk 是否被使用,同样的,它对下一个 chunk 进行 unlink 操作。

不难发现,在 2.23 下,我们的 consolidate 过程完全没有保护。

在 2.23 下,我们注意到 2.23 仅有一个 check 需要绕过。

也就是对于 unlink 的堆块 P,我们要求 P->fd->bk == P == P->bk->fd

malloc/malloc.c
#define unlink(AV, P, BK, FD) {                                            \
FD = P->fd; \
BK = P->bk; \
if (__builtin_expect (FD->bk != P || BK->fd != P, 0)) \
malloc_printerr (check_action, "corrupted double-linked list", P, AV); \
else { \
FD->bk = BK; \
BK->fd = FD; \
if (!in_smallbin_range (P->size) \
&& __builtin_expect (P->fd_nextsize != NULL, 0)) { \
if (__builtin_expect (P->fd_nextsize->bk_nextsize != P, 0) \
|| __builtin_expect (P->bk_nextsize->fd_nextsize != P, 0)) \
malloc_printerr (check_action, \
"corrupted double-linked list (not small)", \
P, AV); \
if (FD->fd_nextsize == NULL) { \
if (P->fd_nextsize == P) \
FD->fd_nextsize = FD->bk_nextsize = FD; \
else { \
FD->fd_nextsize = P->fd_nextsize; \
FD->bk_nextsize = P->bk_nextsize; \
P->fd_nextsize->bk_nextsize = FD; \
P->bk_nextsize->fd_nextsize = FD; \
} \
} else { \
P->fd_nextsize->bk_nextsize = P->bk_nextsize; \
P->bk_nextsize->fd_nextsize = P->fd_nextsize; \
} \
} \
} \
}

利用思路

在 off-by-null 的情况下,我们一个非常直观的想法就是去覆写一个 chunk 的 prev_inuse 位,使其变为 0,因此,chunk 大小应该在 0x100 以上。

紧接着,如果我们对这个修改后的堆块进行 free 操作,那么它便会进入 consolidate backward 的环节,取出 prev_size 的堆块进行 unlink

此时,由于我们可以设置 prev_inuse,因此,我们也就可以控制这个堆块,使其指向一个较为低地址的堆块,包裹我们已经分配的堆块,造成堆块重叠。

2.23 下我们仅需要绕过 unlink 的检查。这是极其容易的,我们可以利用 unsortedbinfdbk,初始情况下,这个双链就是这么链接的。

image-20240913162609542

/*
┌──────────────────────────────────────────────────────────────────────┐
│ │
│ │
│ ┌────────────────┐ ┌─────────────────┐ │
│ │ │ │ │ │
└─────────► fd ─────────────────────► fd ─────┘
│ │ │ │
│ unsorted │ │ chunk │
│ │ │ │
┌────────── bk ◄───────────────────── bk ◄────┐
│ │ │ └─────────────────┘ │
│ └────────────────┘ │
│ │
│ │
└──────────────────────────────────────────────────────────────────────┘
*/

用图画一下大概就是这个感觉

利用手法

那么在 2.23 下,我们可以这样构造

分配四个堆块 A: 0x90, B: 0x20, C: 0x100, D: 0x20

  1. 首先释放 Aunsorted bin
  2. 此时我们在 B 上做 obn,修改 Cprev_size0x90+0x20,并且利用 null 修改 Cprev_inuse0
  3. 释放堆块 C,触发 backward consolidate,此时有 P = C - prev_size = A,触发 unlink(P)unlink 会判断 P->fd->bk == P == P->bk->fd,由于 Aunsorted bin 里,所以此时这个是满足的,因此 A size 被修改为 0x100+0x20+0x90,造成 overlap
add(0x80)  # A
add(0x18) # B
add(0xf0) # C
add(0x10) # D

free(B) # prepare for later obn
free(A) # A <-> unsorted
add(0x18, b'A*0x10' + p64(0x90+0x20) + b'\n') # obn
free(C) # A->size = 0x100+0x20+0x90
add(0x80) # malloc remains
add(0x18) # same as B, E
Loading Comments...