简介
chunk extend (堆块扩展)是堆漏洞一种常见利用手法, 通过 extend 可以实现 chunk overlapping (堆块重叠)
- 程序中存在基于堆块的漏洞
- 漏洞可以控制 chunk header 中的数据
原理
chunk extend 技术能够产生的原因在于 ptmalloc 在堆 chunk 进行操作时使用的各种宏
获取 chunk 块大小
在 ptmalloc 中, 获取 chunk 块大小的操作如下
#define chunksize(p) (chunksize_nomask(p) & ~(SIZE_BITS))
#define chunksize_nomask(p) ((p)->mchunk_size)
|
一种是直接获取 chunk 的大小, 不忽略掩码部分, 另一种是忽略掩码部分
获取下一 chunk 块地址
在 ptmalloc 中, 获取下一 chunk 块的地址的操作如下:
#define next_chunk(p) ((mchunkptr)(((char *) (p)) + chunksize(p)))
|
利用了隐式链表的技术, 就是当前块指针加上当前块的大小
获取前一个 chunk 信息
在 ptmalloc 中, 获取前一个 chunk 信息的操作如下:
#define prev_size(p) ((p)->mchunk_prev_size)
#define prev_chunk(p) ((mchunkptr)(((char *) (p)) - prev_size(p)))
|
即通过 malloc_chunk->pre_size 获取前一个块大小, 然后利用本 chunk 地址减去所得大小
判断当前 chunk 是否 inuse
#define inuse(p) ((((mchunkptr)(((char *) (p)) + chunksize(p)))->mchunk_size) & PREV_INUSE)
|
即查看下一 chunk 的 prev_inuse 域, 而下一块的地址又如我们前面所述是根据当前 chunk 的 size 计算得出的
chunk 的判断 就是依赖于malloc_chunk结构体里面的内容, 简单来说就是chunk_header
, 那么利用思路就自然而然的出来了, 我们通过堆漏洞(heap overflow之类的)可以改变chunk_header进而实现漏洞利用
小结
通过上面几个宏可以看出, ptmalloc 通过 chunk header 的数据判断 chunk 的使用情况和对 chunk 的前后块进行定位。那么我们可以通过堆漏洞堆 chunk header 进行修改, 从而改变 chunk header 进而实现漏洞利用
示例
示例都是在64位下进行, 如果想在32位下进行, 应把8字节偏移改为4字节
对 inuse 的 fastbin 进行 extend
更改第一个块的大小来控制第二个块的内容
libc version: 2.23
#include <stdlib.h> #include <stdio.h>
int main() { void *ptr1, *ptr2;
ptr1 = malloc(0x10);
ptr2 = malloc(0x10);
*(long long *)((long long)ptr1 - 0x8) = 0x41;
free(ptr1);
ptr1 = malloc(0x30); return 0; }
|
malloc 两块 chunk 后堆分布如下
pwndbg> x/10gx 0x602000 0x602000: 0x0000000000000000 0x0000000000000021 <=== chunk1 0x602010: 0x0000316b6e756863 0x0000000000000000 0x602020: 0x0000000000000000 0x0000000000000021 <=== chunk2 0x602030: 0x0000326b6e756863 0x0000000000000000 0x602040: 0x0000000000000000 0x0000000000020fc1 <=== top chunk
|
之后, 我们把 chunk1 的 size 域更改为 0x41, 0x41 是因为 chunk 的 size 域包含了用户控制的大小和 header 的大小。如上所示正好大小为0x40。在题目中这一步可以由堆溢出得到。
pwndbg> x/10gx 0x602000 0x602000: 0x0000000000000000 0x0000000000000041 <=== 篡改大小 0x602010: 0x0000000000000000 0x0000000000000000 0x602020: 0x0000000000000000 0x0000000000000021 0x602030: 0x0000000000000000 0x0000000000000000 0x602040: 0x0000000000000000 0x0000000000020fc1
|
执行 free 之后, 我们可以看到 chunk2 与 chunk1 合成一个 0x40 大小的 chunk, 一起释放了。
pwndbg> heap Free chunk (fastbins) | PREV_INUSE Addr: 0x602000 Size: 0x41 fd: 0x00
Top chunk | PREV_INUSE Addr: 0x602040 Size: 0x20fc1
|
之后我们通过 malloc(0x30) 得到 chunk1+chunk2 的块, 此时就可以直接控制 chunk2 中的内容, 我们也把这种状态称为 overlapping chunk。
pwndbg> chunkinfo 0x602000 ================================== Chunk info ================================== Status : Used Freeable : True prev_size : 0x0 size : 0x40 prev_inused : 1 is_mmap : 0 non_mainarea : 0
|
对 inuse 的 smallbin 进行 extend
通过之前深入理解堆的实现部分的内容, 我们得知处于 fastbin 范围的 chunk 释放后会被置入 fastbin 链表中, 而不处于这个范围的 chunk 被释放后会被置于unsorted bin链表中。 以下这个示例中, 我们使用 0x80 这个大小来分配堆(作为对比, fastbin 默认的最大的 chunk 可使用范围是0x70)
#include<stdlib.h>
int main() { void *ptr, *ptr1;
ptr=malloc(0x80); malloc(0x10); malloc(0x10);
*(int *)((int)ptr-0x8)=0xb1; free(ptr); ptr1=malloc(0xa0); }
|
在这个例子中, 因为分配的 size 不处于 fastbin 的范围, 因此在释放时如果与 top chunk 相连会导致和top chunk合并。所以我们需要额外分配一个chunk, 把释放的块与top chunk隔开。
malloc 三块 chunk 以后
pwndbg> heap Allocated chunk | PREV_INUSE <=== chunk1 Addr: 0x405000 Size: 0x91
Allocated chunk | PREV_INUSE <=== chunk2 Addr: 0x405090 Size: 0x21
Allocated chunk | PREV_INUSE <=== chunk3 Addr: 0x4050b0 Size: 0x21
Top chunk | PREV_INUSE <=== top chunk Addr: 0x4050d0 Size: 0x20f31
|
修改 chunk1 的 size 域为0xb1
pwndbg> heap Allocated chunk | PREV_INUSE Addr: 0x405000 Size: 0xb1
Allocated chunk | PREV_INUSE Addr: 0x4050b0 Size: 0x21
Top chunk | PREV_INUSE Addr: 0x4050d0 Size: 0x20f31
pwndbg> x/32gx 0x405000 0x405000: 0x0000000000000000 0x00000000000000b1 <=== chunk1 0x405010: 0x0000000000000000 0x0000000000000000 0x405020: 0x0000000000000000 0x0000000000000000 0x405030: 0x0000000000000000 0x0000000000000000 0x405040: 0x0000000000000000 0x0000000000000000 0x405050: 0x0000000000000000 0x0000000000000000 0x405060: 0x0000000000000000 0x0000000000000000 0x405070: 0x0000000000000000 0x0000000000000000 0x405080: 0x0000000000000000 0x0000000000000000 0x405090: 0x0000000000000000 0x0000000000000021 <=== chunk2 0x4050a0: 0x0000000000000000 0x0000000000000000 0x4050b0: 0x0000000000000000 0x0000000000000021 <=== chunk3 0x4050c0: 0x0000000000000000 0x0000000000000000 0x4050d0: 0x0000000000000000 0x0000000000020f31 <=== top chunk 0x4050e0: 0x0000000000000000 0x0000000000000000 0x4050f0: 0x0000000000000000 0x0000000000000000
|
释放后, chunk1 把 chunk2 的内容吞并掉并一起置入 unsorted bin
pwndbg> heap Free chunk (unsortedbin) | PREV_INUSE Addr: 0x405000 Size: 0xb1 fd: 0x7ffff7dd4b78 bk: 0x7ffff7dd4b78
Allocated chunk Addr: 0x4050b0 Size: 0x20
Top chunk | PREV_INUSE Addr: 0x4050d0 Size: 0x20f31
|
此时 unsorted bin
pwndbg> unsorted unsortedbin all: 0x405000 —▸ 0x7ffff7dd4b78 (main_arena+88) ◂— 0x405000
|
再次进行分配的时候就会取回 chunk1 和 chunk2 的空间, 此时我们就可以控制 chunk2 中的内容
pwndbg> heap Allocated chunk | PREV_INUSE Addr: 0x405000 Size: 0xb1
Allocated chunk | PREV_INUSE Addr: 0x4050b0 Size: 0x21
Top chunk | PREV_INUSE Addr: 0x4050d0 Size: 0x20f31
|
对 free 的 smallbin 进行 extend
在上一代码基础上进行的, 这次先释放 chunk1, 然后再修改处于 unsorted bin 中的 chunk1 的 size 域
#include <stdlib.h>
int main() { void *ptr, *ptr1;
ptr = malloc(0x80); malloc(0x10);
free(ptr);
*(long long*)((long long)ptr - 0x8) = 0xb1; ptr1 = malloc(0xa0); }
|
两次 malloc 之后的结果如下
pwndbg> heap Allocated chunk | PREV_INUSE <=== chunk1 Addr: 0x405000 Size: 0x91
Allocated chunk | PREV_INUSE <=== chunk2 Addr: 0x405090 Size: 0x21
Top chunk | PREV_INUSE <=== top chunk Addr: 0x4050b0 Size: 0x20f51
|
我们首先释放 chunk1 使它进入 unsorted bin
pwndbg> unsortedbin unsortedbin all: 0x405000 —▸ 0x7ffff7dd4b78 (main_arena+88) ◂— 0x405000
|
然后篡改 chunk1 的 size 域
pwndbg> heap Free chunk (unsortedbin) | PREV_INUSE Addr: 0x405000 Size: 0xb1 fd: 0x7ffff7dd4b78 bk: 0x7ffff7dd4b78
Top chunk | PREV_INUSE Addr: 0x4050b0 Size: 0x20f51
pwndbg> x/8gx 0x405000 0x405000: 0x0000000000000000 0x00000000000000b1 <=== size域被篡改 0x405010: 0x00007ffff7dd4b78 0x00007ffff7dd4b78 0x405020: 0x0000000000000000 0x0000000000000000 0x405030: 0x0000000000000000 0x0000000000000000
|
此时再进行 malloc 分配就可以得到 chunk1+chunk2 的堆块, 从而控制了chunk2 的内容
Chunk Extend/Shrink 可以做什么
一般来说, 这种技术并不能直接控制程序的执行流程, 但是可以控制 chunk 中的内容。如果 chunk 存在字符串指针、函数指针等, 就可以利用这些指针来进行信息泄漏和控制执行流程。
此外通过 extend 可以实现 chunk overlapping, 通过 overlapping 可以控制 chunk 的 fd/bk 指针从而可以实现 fastbin attack 等利用
通过 extend 后向 overlapping
#include <stdlib.h>
int main() { void *ptr, *ptr1;
ptr = malloc(0x10); malloc(0x10); malloc(0x10); malloc(0x10); *(long long *)((long long)ptr-0x8)=0x61; free(ptr); ptr1 = malloc(0x50); }
|
在 malloc(0x50) 对 extend 区域重新占位后, 其中 0x10 的 fastbin 块依然可以正常的分配和释放, 此时已经构成 overlapping, 通过对overlapping 的进行操作可以实现 fastbin attack。
通过 extend 前向 overlapping
这里展示通过修改 pre_inuse 域和 pre_size 域实现合并前面的块
#include <stdlib.h>
int main(void) { void *ptr1, *ptr2, *ptr3, *ptr4; ptr1 = malloc(128); ptr2 = malloc(0x10); ptr3 = malloc(0x10); ptr4 = malloc(128); malloc(0x10); free(ptr1); *(long long *)((long long)ptr4 - 0x8) = 0x90; *(long long *)((long long)ptr4 - 0x10) = 0xd0; free(ptr4); malloc(0x150);
}
|
前向 extend 利用了 smallbin 的 unlink 机制, 通过修改 pre_size 域可以跨越多个 chunk 进行合并实现 overlapping
例题
HITCON Trainging lab13、2015 hacklu bookstore、2016 Nuit du Hack CTF Quals : night deamonic heap
参考
CTF Wiki Chunk Extend and Overlapping