堆之*bin理解

在程序运行中,使用bins结构对释放的堆块进行管理,以减少向系统申请内存的开销,提高效率。

chunk数据结构

从内存申请的所有堆块,都使用相同的数据结构——malloc_chunk,但在inuse和free状态,表现形式上略有差别。

chunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
        |             Size of previous chunk, if unallocated (P clear)  |
        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
        |             Size of chunk, in bytes                     |A|M|P|
  mem-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
        |             User data starts here...                          .
        .                                                               .
        .             (malloc_usable_size() bytes)                      .
next    .                                                               |
chunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
        |             (size of chunk, but used for application data)    |
        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
        |             Size of next chunk, in bytes                |A|0|1|
        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

以上为malloc所得到的chunk的结构,前两个size_t为chunk_header,分别保存前一个(物理相邻)chunk的size(如果前一个chunk为空闲,则保存其size;若为使用状态则归前一个chunk作为usrdata区域使用) 和本chunk的size。因分配的空间会向2*size_t进行对齐,所以后3bit没有意义,因而将其作为三个标记位

  • A : NON_MAIN_ARENA,记录当前 chunk 是否不属于主线程,1表示不属于,0表示属于
  • M : 记录当前 chunk 是否是由 mmap 分配的
  • P : 记录前一个 chunk 块是否被分配。

chunk被free之后,其usrdata区域被复用,作为bin中的链表指针,其结构如下

chunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
        |             Size of previous chunk, if unallocated (P clear)  |
        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
`head:‘ |             Size of chunk, in bytes                     |A|0|P|
  mem-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
        |             fd                                                |
        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
        |             bk                                                |
        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
        |             (fd_nextsize)                                     |
        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
        |             (bk_nextsize)                                     |
        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
        |             Unused space (may be 0 bytes long)                .
        .                                                               .
 next   .                                                               |
chunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
`foot:‘ |             Size of chunk, in bytes                           |
        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
        |             Size of next chunk, in bytes                |A|0|0|
        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  • fd和bk指针分别指向bin中在其之前和之后的chunkfd指向先进入bin者;bk指向后来者
  • fastbin中只有fd指针,使用单向链表进行维护。
  • fd_nextsize和bk_nextsize只存在与large bin中(chunk的size不大时不需要这两个变量,也可能没有他们的空间),指向前/后一个更大size的chunk。

fastbin

对于size较小(小于max_fast)的chunk,在释放之后进行单独处理,将其放入fastbin中。

max_fast:

在32位系统中,fastbin里chunk的大小范围从16到64;

在64位系统中,fastbin里chunk的大小范围从32到128。

fastbin是main_arena中的一个数组,每个元素作为特定size的空闲堆块的链表头,指向被释放并加入fastbin的chunk。

fastbin链表采用单向链表进行连接

如图所示,在free之后,会将被free掉指向的地址“挂”在fastbin相应大小的条目下,以便于下次分配时节省时间 (曾经为了节省free指针的时间而不free,原来浪费了这么多时间,心疼我的无数个TLE)

在分配空间时,首先检查fastbin数组对应大小的条目下是否有“空闲”的空间,有则直接取下进行分配,同时修改fd指针,维护单向链表。

  1. 在fastbin条目下,无论是free掉的空间地址加进来,还是将空闲的空间地址分配出去,都是在根部操作

    • 加入free的空间时,新加入的连在根部,(如新加入chunk3,插入链表根部,chunk3->fd指向原来最靠近bin的chunk1),类似蛋白质的翻译过程
    • 分配空间时,若在对应大小的条目下有空闲的空间,则按蛋白质翻译的逆顺序进行操作(上图中取出chunk3,将chunk3->fd = chunk1链在bin上)
  2. malloc(n)时,实际申请的空间sizeof(chunk) = (n + 4) align to 8 (x86)
    • 实际申请的空间从chunk开始,当堆中物理相邻的前一个chunk为free时,Size of previous chunk标记前一个chunk的大小,否则可以存储前一个chunk的数据。之后是本chunk的大小,由于分配的必定是24bytes(64位为28bytes)的整数倍,最后三位没有影响用作三个标记位。
    • malloc函数范围的指针是从mem开始的用户可用空间。

unsorted bin

unsorted bin 可以作为chunk 被释放和分配的缓冲区。在malloc&free剖析中解释了malloc和free活动中对unsorted bin的使用。这里从更微观的角度解释unsorted bin如何工作。

main_arena

main_arena,主分配区,是一个静态全局变量,其中存储着进行堆块管理的各种变量和指针。

fastbin各项指针、topchunk、和bins指针都存在于这个变量当中。

unsorted bin指针就是bins指针的前两项,ptmalloc共维护128个bin,都存放于bins数组中。

  • 前两项为unsorted bin的指针
  • bins[2] - bins[65]的64个元素为small bin指针
  • bins[66] - bins[127]为large bin

unsorted bin

下面通过这段代码分析在释放和分配chunk时unsorted bin中各指针的工作细节:

# include <stdio.h>
# include <stdlib.h>
int main()
{
    void *a, *b, *c, *d, *e;
    a = malloc(128);
    b = malloc(128);
    c = malloc(128);
    d = malloc(128);
    e = malloc(128);
    printf("a >> %p\nb >> %p\nc >> %p\nd >> %p\ne >> %p\n",a,b,c,d,e);
    puts("free d and b, remember the bins");
    free(d);
    free(b);
    //puts("free c,look at the unsorted bin");
    //free(c);
    puts("malloc(128) again, what will happen?");
    void * newd = malloc(128);
    printf("new d -> %p\n", newd);
    return 0;
}
//make file(x64):
//gcc -o unsortedbin ./test_unosrted -no-pie

运行后得到分配的五个chunk的地址,由于直接输出了返回给用户的指针,所以指向的都是usrdata,指向实际chunk头的地址应该减去0x10。

a >> 0x602010
b >> 0x6020a0
c >> 0x602130
d >> 0x6021c0
e >> 0x602250

  1. 没有发生free之前

    bins数组的前两个可以看做unsorted bin的fd和bk指针,在unsorted bin为空的时候都指向top (main_arena+88)

    CTFwiki对这个过程具体流程和背后原理的示意图不太准确:unsorted bin链表头并不是malloc_chunk结构体,而是main_arena变量中bins列表的前两项分别做fd和bk指针,指向的位置也不是pre_size,而是main_arena中的top,top指向top chunk。我的理解是这样的,如有错误,还请指出。

  2. free(d)
    pwndbg> unsortedbin
    unsortedbin
    all: 0x6021b0 —? 0x7ffff7dd3b58 (main_arena+88) —? 0x6021b0 ?— 0x7ffff7dd3b58
    pwndbg> p main_arena
    $2 = {
      mutex = 0,
      flags = 1,
      fastbinsY = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0},
      top = 0x6026e0,
      last_remainder = 0x0,
      bins = {0x6021b0, 0x6021b0, 0x7ffff7dd3b68 <main_arena+104>, 0x7ffff7dd3b68 <main_arena+104>...
    pwndbg> telescope 0x6021b0
    00:0000│   0x6021b0 ?— 0x0
    01:0008│   0x6021b8 ?— 0x91
    02:0010│   0x6021c0 —? 0x7ffff7dd3b58 (main_arena+88) —? 0x6026e0 ?— 0x0
    ... ↓
    

    此时unsorted bin的两个指针均指向被释放的d,d的fd、bk指针指向top

  3. free(b)
    pwndbg> p main_arena
    $3 = {
      mutex = 0,
      flags = 1,
      fastbinsY = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0},
      top = 0x6026e0,
      last_remainder = 0x0,
      bins = {0x602090, 0x6021b0, 0x7ffff7dd3b68 <main_arena+104>,...
    pwndbg> unsortedbin
    unsortedbin
    all: 0x602090 —? 0x6021b0 —? 0x7ffff7dd3b58 (main_arena+88) —? 0x602090 ?— 0x6021b0
    pwndbg> telescope 0x602090
    00:0000│   0x602090 ?— 0x0
    01:0008│   0x602098 ?— 0x91
    02:0010│   0x6020a0 —? 0x6021b0 ?— 0x0
    03:0018│   0x6020a8 —? 0x7ffff7dd3b58 (main_arena+88) —? 0x6026e0 ?— 0x0
    04:0020│   0x6020b0 ?— 0x0
    ... ↓
    pwndbg> telescope 0x6021b0
    00:0000│   0x6021b0 ?— 0x0
    01:0008│   0x6021b8 ?— 0x91
    02:0010│   0x6021c0 —? 0x7ffff7dd3b58 (main_arena+88) —? 0x6026e0 ?— 0x0
    03:0018│   0x6021c8 —? 0x602090 ?— 0x0
    04:0020│   0x6021d0 ?— 0x0
    

    新释放的b会连载unsortedbin的根部,各指针的关系如图。

  4. malloc(128)
    pwndbg> p main_arena
    $4 = {
      mutex = 0,
      flags = 1,
      fastbinsY = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0},
      top = 0x6026e0,
      last_remainder = 0x0,
      bins = {0x602090, 0x602090, 0x7ffff7dd3b68 <main_arena+104>, ...
    pwndbg> unsortedbin
    unsortedbin
    all: 0x602090 —? 0x7ffff7dd3b58 (main_arena+88) —? 0x602090 ?— 0x7ffff7dd3b58
    pwndbg> telescope 0x602090
    00:0000│   0x602090 ?— 0x0
    01:0008│   0x602098 ?— 0x91
    02:0010│   0x6020a0 —? 0x7ffff7dd3b58 (main_arena+88) —? 0x6026e0 ?— 0x0
    ... ↓
    04:0020│   0x6020b0 ?— 0x0
    

    此时unsorted bin又只有一个chunk,指针关系与刚刚free(d)时相同,但在bin中的是b,先进入的d被再次分配,由此得到,unsorted bin中遵循FIFO原则,先进入的chunk在size合适的情况下会被优先分配。

    unlink

    在unsorted bin中进行分配的时候,size不合适的chunk会被放入small bin或large bin,这个unlink的过程没有对chunk进行检查,所以被篡改过的chunk也能通过unlink,破坏掉链表中的fd、bk指针,即unsorted bin attack。

small bins & large bin

chunk进入small bin和large bin的唯一机会是在分配chunk时,在unsorted bin中进行遍历,size不合适的chunk会被unlink过来。

small bin和large bin都是采用双向链表进行维护,遵循FIFO原则。

其中large bin中的chunk有fd_nextsize和bk_nextsize,分别指向之前/之后更大的chunk,加快寻找速度。

在分配chunk的时候,如果前面的步骤都没有找到合适的chunk,则在small bin和large bin中找到最小的large enough的chunk,进行分割,unlink,分配完成。



Ref:

安全技术精粹

CTF wiki

作者:辣鸡小谱尼

出处:http://www.cnblogs.com/ZHijack/

如有转载,荣幸之至!请随手标明出处;

原文地址:https://www.cnblogs.com/ZHijack/p/9835572.html

时间: 2024-10-19 01:05:12

堆之*bin理解的相关文章

初学JAVA——栈空间堆空间的理解

1.Person pangzi;    //这是在“开拓空间”于栈空间 pangzi=new Person();    //这是赋值于堆空间 上两步就是在做与空间对应的事. 2.值类型直接存入栈空间,如AF,引用类型存入堆空间,在栈空间存有“索引地址”,如当需要B时,在栈空间寻找“索引地址”后对应寻找堆空间的“详细内容”. 故,值类型“快”,引用类型“灵活”. 例String S = “ABCDEFG........Z",则S对应栈空间,“ABCDEFG........Z"对应堆空间.

算法导论第十九章 斐波那契堆

<算法导论>第二版中在讨论斐波那契堆之前还讨论了二项堆,但是第三版中已经把这块的内容放到思考题中,究极原因我想大概是二项堆只是个引子,目的是为了引出斐波那契堆,便于理解,而且许多经典的算法实现都是基于斐波那契堆,譬如计算最小生成树问题和寻找单源最短路径问题等,此时再把二项堆单独作为一章来讲显然没有必要.类似的堆结构还有很多,如左倾堆,斜堆,二项堆等,下次我打算开一篇博客来记录下它们的异同点. 一.摊还分析(第十七章) 这些高级的数据结构的性能分析一般是基于一个技术——摊还分析,可以理解成一种时

内存栈与堆的区别C#

C# 堆与栈 理解堆与栈对于理解.NET中的内存管理.垃圾回收.错误和异常.调试与日志有很大的帮助.垃圾回收的机制使程序员从复杂的内存管理中解脱出来,虽然绝大多数的C#程序并不需要程序员手动管理内存,但这并不代表程序员就无需了解分配的对象是如何被回收的,在一些特殊的场合仍需要程序员手动进行内存管理. 在32位的处理器上,每个进程的虚拟内存为4GB,.NET会在这4GB的内存块中开辟出3块内存,分别作为栈.托管堆.和非托管堆 堆(heap): 堆是从下往上分配,所以已用的空间在自由空间下面,C#中

深入理解JVM(一)——JVM内存模型

JVM内存模型 Java虚拟机(Java Virtual Machine=JVM)的内存空间分为五个部分,分别是: 1. 程序计数器 2. Java虚拟机栈 3. 本地方法栈 4. 堆 5. 方法区. 下面对这五个区域展开深入的介绍. 1. 程序计数器 1.1. 什么是程序计数器? 程序计数器是一块较小的内存空间,可以把它看作当前线程正在执行的字节码的行号指示器.也就是说,程序计数器里面记录的是当前线程正在执行的那一条字节码指令的地址. 注:但是,如果当前线程正在执行的是一个本地方法,那么此时程

浅析基础数据结构-二叉堆

如题,二叉堆是一种基础数据结构 事实上支持的操作也是挺有限的(相对于其他数据结构而言),也就插入,查询,删除这一类 对了这篇文章中讲到的堆都是二叉堆,而不是斜堆,左偏树,斐波那契堆什么的 我都不会啊 一.堆的性质 1.堆是一颗完全二叉树 2.堆的顶端一定是“最大”,最小”的,但是要注意一个点,这里的大和小并不是传统意义下的大和小,它是相对于优先级而言的,当然你也可以把优先级定为传统意义下的大小,但一定要牢记这一点,初学者容易把堆的“大小”直接定义为传统意义下的大小,某些题就不是按数字的大小为优先

深入理解JVM JVM内存模型

1.JVM内存模型        说起JVM内存模型,都是知道是Java方法区.Java栈.Native方法区.Java堆和程序计数器五部分,不过具体是做什么的,又有什么关系可能大家就不太清楚了,所以话不多说,直接上干货. 首先是JVM内存规范. 编译器和类加载在上篇博客已经讲了,不了解的可去看一下.现在主要就是运行时数据区了.具体请看下图 先给简单介绍一下什么是堆.桟.方法区以及格子 首先就是堆与栈分开设计是为什么呢? 栈存储了处理逻辑.堆存储了具体的数据,这样隔离设计更为清晰堆与栈分离,使得

【转】二叉堆与优先队列

目录 1.插入 2.删除 3.查询 1.堆排序 2.用两个堆来维护一些查询第k小/大的操作 中位数 3.利用堆来维护可以“反悔的贪心” 如题,二叉堆是一种基础数据结构 事实上支持的操作也是挺有限的(相对于其他数据结构而言),也就插入,查询,删除这一类 对了这篇文章中讲到的堆都是二叉堆,而不是斜堆,左偏树,斐波那契堆什么的 我都不会啊 更新概要: 无良博主终于想起来要更新辣 upd1:更新5.2.2-对于该子目所阐述的操作“用两个堆来维护一些查询第k小/大的操作”更新了一道例题-该操作对于中位数题

Zctf-pwn

最近有了点时间,把ZCTF的pwn总结了下,就差最后一个pwn500,另找时间总结. 文件打包:http://files.cnblogs.com/files/wangaohui/attach.zip Pwn100 很明显栈溢出,但是有canary保护.但是明显的是flag已经被读入了内存.在网上找到了dragonsector写的一个pdf,知道了当__stack_check_fail时,会打印出正在运行中程序的名称,如下: 所以,我们只要将__libc_argv[0]覆盖为flag的地址就能将f

Java 内存区域和GC机制

目录 Java垃圾回收概况 Java内存区域 Java对象的访问方式 Java内存分配机制 Java GC机制 垃圾收集器 Java垃圾回收概况 Java GC(Garbage Collection,垃圾收集,垃圾回收)机制,是Java与C++/C的主要区别之一,作为Java开发者,一般不需要专门编写内存回收和垃圾清理代 码,对内存泄露和溢出的问题,也不需要像C程序员那样战战兢兢.这是因为在Java虚拟机中,存在自动内存管理和垃圾清扫机制.概括地说,该机制对 JVM(Java Virtual M