06|Chunk Extend:改一个 size,造出两块重叠内存

发布时间:2026/10/28 1:43:23
06|Chunk Extend:改一个 size,造出两块重叠内存 06Chunk Extend改一个 size造出两块重叠内存堆漏洞里有一类特别讨巧的东西明明只改了一个字节最后却能让两个指针指向同一块内存。第一次见的时候我不理解——为什么改 size 就能吞掉后面那块后来去翻了 ptmalloc 的几个宏才想通它根本没有全局的分配表全靠块头自己算。它凭什么相信 sizeptmalloc 里没有哪块分配了、哪块空着的总表。要找下一块它就直接拿当前地址加上当前块的大小#define next_chunk(p) ((mchunkptr)(((char *)(p)) chunksize(p)))所以正常p 在 0x1000size 0x20 → 下一块在 0x1020 改后p 在 0x1000size 0x40 → 下一块在 0x1040它不核对直接用。这就是 Extend 的全部原理——你把 size 写大它眼里的相邻块就跟着往后跳。另外两个宏也建立在同一个前提上/* 当前块空不空闲看的是下一块里那个 PREV_INUSE 位 */ #define inuse(p) (chunksize(next_chunk(p)) PREV_INUSE) /* 前一块的位置用当前块的 prev_size 倒推 */ #define prev_chunk(p) ((mchunkptr)(((char *)(p)) - prev_size(p)))三个宏三条互相信任的推理链。改掉 size等于把链子抽走一截。最小的 Extend四步X0x00 A 的 chunk 头 X0x10 A 的 user 区 X0x20 B 的 chunk 头 ← A 溢出能碰到 B 的 size X0x30 B 的 user 区 X0x40 C 的 chunk 头第一步申请 A、B、C、D 四块0x20 一档保证物理相邻。第二步溢出 A改掉 B 的 size。原来0x21改成0x410x20 → 前一块 A 在用PREV_INUSE1所以是 0x21 0x40 → B 现在声称自己有 0x40把 C 也包进去了第三步free(B)。分配器按新 size 把 0x40 回收——C 在程序眼里还是在用的但在分配器眼里已经属于这块空闲内存了。第四步malloc(0x38)拿回这块 0x40。现在你手上同时有两个指针新块 D和旧的 C。两个指针指向同一片内存这就是 overlapping。一个能跑的 demo#include stdio.h #include stdlib.h int main(void) { char *a malloc(0x18); /* chunk 0x20 */ char *b malloc(0x18); /* chunk 0x20 */ char *c malloc(0x18); /* chunk 0x20 ← 要被吞掉的那块 */ (void)malloc(0x18); /* guard挡住 top */ /* A 的 user 起点 chunk0x10B 的 size 在 A_user 0x18 */ a[0x18] 0x41; /* 把 B 的 size 从 0x21 改成 0x41 */ free(b); /* 按 0x40 回收B C 一起进 bin */ char *d malloc(0x38); /* 拿回 0x40 的块 */ printf(d %p\n, (void *)d); printf(c %p\n, (void *)c); printf(d 0x10 %p ← C 的 user 区就在这\n, (void *)(d 0x10)); return 0; }跑起来会看到d 0x10 c。注意a[0x18]这个偏移A 的 user 起点是 chunk0x10B 的 size 在 A_chunk0x28所以相对 A_user 是 0x18。这个数每个人第一次都会算错。这段在 2.23 / 2.27 上能跑通。2.29 之后会崩报corrupted size vs. prev_size——因为它会拿 B 的新 size 去和 D 的prev_size对账对不上就退出。要过这关得顺手把 D 的 prev_size 也改成 0x40。五个变体本质是同一个我把见过的用法归了归类其实都是改 size 之后分配器的视野变了。① 向后吞并最基础A 溢出改 B 的 size把 C 吞进来free(B)之后再 malloc 回来。② 先释放再改UAF Extend先把块 free 进 bin再用 UAF 改它的 size。这时候改完不用 free 第二次分配器在下次遍历 bin 的时候就会按新 size 处理。这里有个容易踩的点0x80 的块正好卡在 fastbin 上限你以为是进了 unsorted其实进的是 fastbin。要演示 unsorted块得 ≥0x902.27 上还得 0x410 才绕过 tcache。③ 向前合并Off-by-null 的经典形态溢出只写进去一个\x00把下一块 size 的最低字节抹掉PREV_INUSE 位就没了0x91 → 0x90 奇数变偶数PREV_INUSE 从 1 变 0分配器看到前一块是空闲的就去读prev_size找它然后合并。关键在 prev_size它得等于你要跨过的总距离。比如 A(0x90) B(0x30)那 B 的 user 区最后 8 字节就要写0xc0。④ 高版本的伪造2.29 之后free会顺带检查下一块的下一块。你只改 size 不伪造后续的头一样崩。所以要提前在 B新size 的位置摆一个合法的 chunk 头至少 size 字段要像个东西比如0x21标记位要匹配。⑤ 反向操作把 size 改小Chunk Shrink这个思路很多人没想到。0x91 改成 0x31free只回收前 0x30后面 0x60 就变成了分配器眼里已经不存在、但程序还在用的内存。接下来malloc(0x28)拿回 0x30你从这块里越界写就能改到那 0x60 里的东西——而且不会立刻被发现因为分配器压根不知道那片区域还在。现代 glibc 卡在哪检查版本你要多做一步corrupted size vs. prev_size2.28把下一块的 prev_size 一起改成新 size下一块 size 的合法性≤ system_mem一直有伪造的 chunk 头要像样tcache 抢先2.26尺寸要么 0x410要么先填满 tcache核心一句话以前改一个 size 就够现在要连带把它跳过去看见的那个头也伪造好。最后Extend 和 Overlapping 其实是一件事的两面Extend 是手段Overlapping 是结果。真正要记住的不是改哪个字节而是那三个宏的信任关系——next_chunk用 size 算位置inuse看下一块的标志位prev_chunk用 prev_size 倒推。改掉任何一个分配器眼里的物理空间就变形了。 相关阅读· 上一篇《05Use-After-Free钥匙还在你手里柜子已经不是你的了》· 下一篇《07Off-by-one一个字节能改出什么》· 实战配套《Chunk Extend 实战越界 1 字节改 sizeheapcreator》全部 exp 与 gdb 脚本GitHub 链接

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询