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 的。

基本示例 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
    910     *(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域
   1112     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域),下面是一张参考图:

当前chunk结构

执行 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。

对 inuse 的 smallbin 进行 extend


Chunk Extend and Overlapping
https://zer0ptr.github.io/2026/02/09/chunk-extend-overlapping/
Author
zer0ptr
Posted on
February 9, 2026
Licensed under