ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Unsortedbin attack 实战解析:从堆布局到 TaoToken 配置验证

Unsortedbin attack 实战解析:从堆布局到 TaoToken 配置验证 1. 从一次本地堆调试说起Unsortedbin attack 到底在打什么Unsortedbin attack 是 glibc 堆利用里一个很经典的手法它的核心目标不是直接拿到任意地址写而是把main_arena里的unsorted_chunks链表指针也就是bk写到一个你指定的目标地址上。听起来有点抽象换个说法你通过伪造一个 unsorted bin 中的 chunk让_int_malloc在把这块 chunk 从 unsorted bin 摘下来的时候顺手把main_arena88这个地址写到chunk-bk 0x10的位置。这个写入值本身不可控但写入位置可控所以它常被用来覆盖global_max_fast、_IO_list_all之类的全局变量为后续的 house of orange 或者 FSOP 铺路。我这次复现的环境是 Ubuntu 20.04 glibc 2.31关闭了 tcache 的干扰或者先把 tcache 填满因为 tcache 会优先接管 small chunk 的分配导致 unsorted bin 根本走不到。很多新手第一次调 Unsortedbin attack 失败八成就是 tcache 没填满chunk 直接进了 tcache压根没进 unsorted bin。这篇内容我会带你从堆布局构造开始一步步用 gdb 确认 unsorted bin 的状态然后给出可复制的调试配置片段。同时因为现在很多人会用 AI 辅助分析堆内存和反汇编我也会把 TaoToken 的统一 Key/API 通道接入 AI 分析工具的settings.json骨架给出来让你在本地环境里既能手动调堆也能让 AI 帮你读malloc_state和 chunk 结构。整个链路是先手动确认堆状态再用 AI 工具做辅助验证最后对比攻击前后的内存差异。适合谁看已经了解 malloc/free 基本流程、知道 chunk 结构prev_size、size、fd、bk但还没亲手打过 Unsortedbin attack 的读者。如果你连 fastbin 和 smallbin 的区别都还不清楚建议先把堆的基础分配流程过一遍再回来。2. 前置准备调试环境与 TaoToken 统一通道2.1 本地调试环境清单我用的组合是 pwntools pwndbg gdb这套在堆调试里几乎是标配。pwndbg 能直接把 chunk 的 fd/bk 用箭头画出来看 unsorted bin 链表非常直观。编译目标程序时记得关掉 PIE 和 canary方便我们固定地址观察gcc -g -no-pie -fno-stack-protector unsorted_demo.c -o unsorted_demounsorted_demo.c里我故意留了一个可以指定 free 顺序和写入位置的逻辑方便构造堆布局。实际打 CTF 时你面对的是题目给的二进制但调试思路是一样的先看 free 之后 chunk 进了哪个 bin再看下一次 malloc 时_int_malloc怎么摘链。2.2 为什么要在堆调试里接入 TaoToken手动调堆有个痛点gdb 里p main_arena出来的结构体嵌套很深bins数组里几十个指针肉眼找unsorted_chunks的bk指向哪里很费劲。这时候如果有个 AI 工具能帮你把malloc_state的结构和当前 chunk 的 fd/bk 关系解释清楚效率会高很多。TaoToken 在这里的作用是提供一个统一的 Key 和 API 通道让你不用在多个 AI 工具之间反复切换配置。它的接入方式很直接官网在 https://taotoken.net API 入口是 https://taotoken.net/api 。你注册后在控制台生成一个 Key然后把这个 Key 填到 AI 分析工具的配置里就行。对于堆调试这种需要反复问“这个 bk 指向哪”“为什么 chunk 没进 unsorted bin”的场景统一通道能省掉不少重复配置的时间。注意TaoToken 只是提供模型调用的统一入口不替代 gdb/pwndbg 本身。堆状态的真值永远以调试器里看到的内存为准AI 的输出只作为辅助理解。3. 可复制配置settings.json 骨架与堆布局构造3.1 AI 分析工具的 settings.json 骨架如果你用的 AI 辅助工具支持通过配置文件接入自定义 API下面这个骨架可以直接改。核心是把base_url指向 TaoToken 的 API 地址api_key填你在控制台生成的 Key{ ai: { provider: taotoken, base_url: https://taotoken.net/api, api_key: sk-你的TaoToken密钥, model: claude-sonnet-4-20250514, max_tokens: 4096, temperature: 0.2 }, debug: { gdb_path: /usr/bin/gdb, pwndbg_enabled: true, auto_attach: false }, workspace: { binary: ./unsorted_demo, libc: /lib/x86_64-linux-gnu/libc.so.6 } }temperature我设成 0.2因为分析堆内存结构需要稳定输出不希望模型自由发挥。model字段按你实际能用的模型填TaoToken 的模型列表在模型对话页面能看到。如果你要做长期的编码或 Agent 类任务可以考虑 Coding Plan它在连续对话和代码分析场景下更划算。3.2 堆布局构造让 chunk 正确进入 unsorted binUnsortedbin attack 的前提是目标 chunk 被 free 之后进入了 unsorted bin而不是 tcache 或 fastbin。对于 small chunksize 在 0x20 到 0x3f0 之间glibc 2.26 之后会优先看 tcache。所以构造步骤是第一步先把 tcache 对应的 bin 填满。tcache 每个 size 默认最多 7 个 chunk所以你要先 free 7 个同样大小的 chunk 把 tcache 占满。第二步再 free 一个同样大小的 chunk这时候它才会进入 unsorted bin。// 伪代码示意 void *chunks[8]; for (int i 0; i 7; i) { chunks[i] malloc(0x90); // 实际 chunk size 0xa0 } void *victim malloc(0x90); for (int i 0; i 7; i) { free(chunks[i]); // 填满 tcache } free(victim); // 这个进入 unsorted binfree 之后victim的fd和bk都会被设置成main_arena88也就是 unsorted bin 的头。这时候如果你用 gdb 看gdb ./unsorted_demo pwndbg b *main pwndbg run pwndbg binsbins命令会列出所有 bin 的状态unsorted bin 那一行会显示victim的地址并且fd和bk都指向main_arena88。3.3 覆盖 bk 指针构造攻击攻击的关键一步是把victim-bk改成一个我们想要写入的目标地址减去 0x10。因为_int_malloc在摘链时执行的是bck victim-bk; unsorted_chunks(av)-bk bck; bck-fd unsorted_chunks(av);最后一行bck-fd unsorted_chunks(av)就是写入动作写入地址是bck 0x10写入值是main_arena88。所以如果你想让main_arena88写到target就要让victim-bk target - 0x10。假设我们想覆盖global_max_fast先找到它的地址pwndbg p global_max_fast $1 (size_t *) 0x404080那么victim-bk要设成0x404080 - 0x10 0x404070。修改 bk 的方式取决于你的漏洞原语常见的是 UAF 或者 off-by-one 溢出。假设你能直接写victim的 bk 字段from pwn import * p process(./unsorted_demo) # ... 构造堆布局 ... # 假设 victim 地址是 0x4052a0 victim 0x4052a0 target 0x404080 p.send(p64(target - 0x10)) # 覆盖 victim-bk然后触发一次 malloc让_int_malloc去 unsorted bin 里找 chunkp.sendline(bmalloc) # 触发 malloc(0x90)这时候global_max_fast就会被写成main_arena88的低 8 字节。你可以用 gdb 验证pwndbg x/gx 0x404080 0x404080 global_max_fast: 0x00007ffff7dd1b78这个值就是main_arena88的地址。到这里Unsortedbin attack 的写入就完成了。4. 验证请求确认堆状态与攻击效果4.1 攻击前的 unsorted bin 状态在覆盖 bk 之前先确认 victim 确实在 unsorted bin 里。用 pwndbg 的bins命令pwndbg bins fastbins empty unsortedbin all: 0x4052a0 —▸ 0x7ffff7dd1b78 (main_arena88) ◂— 0x4052a0 smallbins empty largebins empty这里0x4052a0就是 victim它的 fd 和 bk 都指向main_arena88。注意看箭头方向all这一行显示的是 unsorted bin 的链表结构。4.2 覆盖 bk 后的内存变化覆盖victim-bk之后再用bins看pwndbg bins unsortedbin all: 0x4052a0 —▸ 0x7ffff7dd1b78 (main_arena88) ◂— 0x404070这时候 bk 已经变成了0x404070也就是target - 0x10。但注意main_arena那边的bk还是指向 victim链表看起来“断”了但这正是攻击的意图——我们不需要链表完整只需要_int_malloc摘链时执行那行写入。4.3 触发 malloc 后的写入验证触发 malloc 之后直接看目标地址pwndbg x/gx 0x404080 0x404080 global_max_fast: 0x00007ffff7dd1b78对比攻击前的值通常是 0x80 或 0现在变成了main_arena88的地址。这就是 Unsortedbin attack 的“成功结果”一个不可控的值被写到了可控的地址。如果你用 AI 工具辅助分析可以把这段 gdb 输出贴给模型让它帮你确认main_arena88这个偏移在 glibc 2.31 里对应的是哪个字段。TaoToken 的模型对话入口可以直接做这件事不用额外配置。4.4 用 AI 辅助读 malloc_statemain_arena的结构体在 glibc 源码里是malloc_state字段很多。你可以让 AI 帮你解释请解释 glibc 2.31 中 malloc_state 结构体的 bins 数组布局 以及 main_arena88 对应的是哪个 bin 的哪个字段。模型会告诉你main_arena88是bins[0]的fd或者bk具体取决于偏移计算。这种问题手动翻源码要花几分钟AI 几秒就能给出结构化的解释。但记住最终还是要用 gdb 的p main_arena和ptype来确认。5. 本篇常见错排查5.1 chunk 没进 unsorted bin进了 tcache这是最常见的失败原因。现象是bins命令里 unsorted bin 是空的但 tcachebins 里有东西。解决办法就是先把对应 size 的 tcache 填满 7 个。如果你不确定 tcache 的 count 是多少用pwndbg tcachebins看每个 size 的 count 值。count 到 7 之后再 free 的 chunk 才会进 unsorted bin。5.2 bk 被覆盖后malloc 直接崩溃如果你覆盖victim-bk之后一触发 malloc 就 segfault大概率是因为_int_malloc在摘链时还做了其他检查。glibc 2.31 里有if (__glibc_unlikely (bck-fd ! victim)) malloc_printerr(malloc(): corrupted unsorted chunks 3);也就是说bck-fd必须等于 victim。但我们的bck是target - 0x10它的fd字段在target位置那个位置的值通常不是 victim所以这个检查会失败。绕过方法是确保target位置的值恰好等于 victim 地址或者选择在检查之前就完成写入的路径。实际 CTF 里常用global_max_fast是因为它的初始值恰好能满足某些条件或者利用 house of orange 的特定流程。5.3 偏移算错写入位置偏了 0x10bck-fd unsorted_chunks(av)这行代码里bck-fd的地址是bck 0x10因为 fd 在 chunk 结构里偏移 0x10。所以如果你想让写入发生在targetbck要设成target - 0x10。很多人直接设成target结果写到了target 0x10覆盖了相邻的变量。用 gdb 的x/gx在攻击前后对比target - 0x10、target、target 0x10三个位置就能确认偏移对不对。5.4 glibc 版本差异导致行为不同glibc 2.26 引入 tcache2.29 加了bck-fd ! victim检查2.31 又调整了一些细节。你在 2.23 上能打的 payload到 2.31 可能直接崩。确认版本ldd --version或者pwndbg p (char*)main_arena看地址附近的符号。不同版本的main_arena偏移不一样main_arena88这个值在 2.23 和 2.31 里对应的字段可能不同。用 AI 工具查版本差异很快但最终还是要以你本地的 libc 为准。5.5 AI 工具返回的地址和 gdb 不一致如果你用 AI 分析时发现它给出的main_arena偏移和 gdb 里看到的不一样先检查你喂给模型的 libc 版本对不对。TaoToken 的 API 通道本身不改变模型输出但如果你在settings.json里配的libc路径指向了错误的版本模型就会基于错误信息推理。解决办法是在提问时把ldd --version的输出和p main_arena的结果一起贴进去。6. 把调试链路固定下来从手动验证到 AI 辅助Unsortedbin attack 的调试链路其实可以固定成一套流程先确认 tcache 状态再确认 unsorted bin 链表然后覆盖 bk触发 malloc最后对比目标地址的值。这套流程在 glibc 2.23 到 2.31 之间大同小异差异主要在检查逻辑和偏移上。我自己的习惯是每次打一个新的堆题先把bins、tcachebins、p main_arena三个输出存下来然后用 AI 工具做一次结构解释确认自己对当前堆状态的理解没有偏差。TaoToken 在这里的价值是让你用一个 Key 就能在模型对话、代码分析、Agent 任务之间切换不用每个工具都重新配一遍。如果你只是偶尔查一下堆结构用模型对话就够了如果你要连续分析多个二进制或者写自动化脚本Coding Plan 会更顺手。接入文档在 https://taotoken.net/doc API Keys 在 https://taotoken.net/api-keys 生成。配置的时候注意base_url用https://taotoken.net/api不要多加路径否则会 404。堆调试的真值永远在 gdb 里AI 只是帮你更快地读懂那些嵌套结构体。
返回列表