首页 > 其他分享 >CTF-PWN 堆的相关数据结构

CTF-PWN 堆的相关数据结构

时间:2024-07-27 21:56:45浏览次数:9  
标签:bin malloc chunk free CTF PWN 数据结构 bins size

文章连接 :  《堆的相关数据结构》

参考

1.ctf.wiki : 堆相关数据结构 - CTF Wiki

2.星盟pwn佬 : 0011.哔哩哔哩-【个人向】CTF pwn 入门-P11[高清版]_哔哩哔哩_bilibili

  • malloc_chunk
  • 概念

通过malloc申请的内存称为chunk,也可以将chunk称作堆的一个单位(自己随意理解)。

free掉的chunk将会被加入到相应的空闲管理列表中,简称为XX bin,大致分为四类,unsorted bin,small bin,fast bin,large bin。

下面介绍malloc_chunk的统一结构。具体表现根据是否被释放会有所不同。

主要结构:

struct malloc_chunk {

INTERNAL_SIZE_T      prev_size;  /* Size of previous chunk (if free).  */
INTERNAL_SIZE_T      size;       /* Size in bytes, including overhead. */
// 由于 chunk 的大小必为8的整数倍,故 size的值的莫3bits必为0
// 为了不浪费这3bits,设计者为他们赋予了特殊作用

struct malloc_chunk* fd;         /* double links -- used only if free. */
struct malloc_chunk* bk;

/* Only used for large blocks: pointer to next larger size.  */
struct malloc_chunk* fd_nextsize; /* double links -- used only if free. */
struct malloc_chunk* bk_nextsize;
};

chunk_header : 前两个字段为chunk头,即为prev_szie和size

user_data : 除去chunk_header即为数据区,每次malloc申请的chunk其实指向user_data的起始部 分.

对每一部分进行解释:

  • prev_size: 记录该chunk物理相邻的上一个free_chunk(较低地址)的大小,若上一个chunk被利用了,那么prev_size就可以用来存储上一个chunk的数据,即称为prev_size的复用。

  • size代表本chunk的大小,其中需要注意chunk的大小必须是 2*SIZE_SZ 的整数倍,不满足的也会被转化为其的整数倍。其中SIZE_SZ代表最小内存单元,32位系统是4,64位系统是8。特别注意该字段的低三比特位对chunk大小没有影响。

原因:拿64位举例,由于size遵循着分配的原则,即0x10字节的整数倍,也可以说至少要是8字节的整数倍,那么也就说明了低三比特位一定为0,若为100,则会出现一个4出来,不满足分配规则,那着三比特位就该浪费掉吗?其实不然。

作用:

低三比特位从高到低位A,M,P。

      • A:ARENA-> 记录当前chunk是否在主线程,1->不属于,0->属于1.
      • M:mmap-> 记录当前chunk是否由mmap分配
      • P: prev_inuse->记录前一个chunk是否被分配1->被分配,0->没有被分配。P为0时可以通过prev_size获得前一个chunk的大小和地址。便于空闲chunk合并

以上二者如下图:

当chunk被free后,就会多出 fd,bk,fd_nextsize, bk_nextsize

  • fd,bk: 指针类型。chunk空闲时,通过fd,bk指针连接成逻辑链表。从fd开始就是数据了。
    • fd: forword,即指向下一个free_chunk->chunk_header的地址
    • bk: backword,即指向上一个free_chunk->chunk_header的地址

  • fd_nextsize, bk_nextsize:large bin中具有该部分.
    • fd_nextsize 指向前一个与当前 chunk 大小不同的第一个空闲块,不包含 bin 的头指针。
    • bk_nextsize 指向后一个与当前 chunk 大小不同的第一个空闲块,不包含 bin 的头指针。
    • 一般空闲的 large chunk 在 fd 的遍历顺序中,按照由大到小的顺序排列。这样做可以避免在寻找合适 chunk 时挨个遍历

补充知识:

  1. chunk header 和 user data图示:
chunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
header  |             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|
        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  1. free chunk的合并

图中是small bin(unsorted bin)中chunk的合并,当下一个free chunk的p->0,即两个物理相邻的chunk都是free chunk,那么这两个free chunk会被合并。

  1. chunk -> size的大小

我们已经知道size代表的是该chunk的大小,这里阐述size跟malloc(参数) 其中参数的关系

举例:64位操作系统,malloc(0x100):

#include <stdio.h>
#include <stdlib.h>
int main(void)
{
        void *ptr = malloc(0x100);
        free(ptr);
}

通过gdb调试,发现此时的chunk size 为0x111

为什么呢? 其实malloc(参数),该参数代表的是数据内容,没有包括prev_size和size这两个控制字段,加上这两个控制字段,那么size就是0x100+0x10=0x110,那还有个1是哪来的,size的低三比特位中的p记录的是物理相邻上一个chunk是否被利用,gdb图中发现确实被利用了,所以该chunk_size中的P记为1,如图:

上述内容也可以印证malloc返回的指针指向user_data的起始地址。

  • bin

前面也提到过bin,,用户释放掉的 chunk 不会马上归还给系统,ptmalloc会统一管理这些空闲的chunk,这些空闲的chunk会被"丢到"bin中,也就是空闲的chunk管理仓库,根据空闲的 chunk 的大小以及使用状态将 chunk 初步分为 4 类:fast bins,small bins,large bins,unsorted bin。在每类 bin 的内部仍然会有多个互不相关的链表(双向链表)来保存不同大小的 chunk。

bin的实现其实是一个数组。

注:下面只介绍四种bin中chunk的结构,关于bin更复杂的内容可以看开头参考的ctf.wiki。

  1. Fast Bin

(1)产生缘由:一些较小的chunk被free之后,chunk_size低三比特位中P=0,该chunk会与相邻的free_chunk合并,那么在下次malloc申请合适大小chunk的时候,便会对合并的free_chunk进行分割,分割出相应大小的chunk,那么像这样的每次分割就不fast了,因此啊,fast bin就产生了,如图可以发现该chunk_size中P=1,这是不会改变的,为的就是防止该chunk被free后与相邻free_chunk合并

chunk_size范围:既然是fast,那么它的chunk是有大小限制的,只有在这个范围内的chunk被free后才会进入到fastbin,64位情况下,chunk_size的范围是0x20-0x80(包括chunk_header),32位对半砍就是。该范围可以gdb调试,步过到malloc之后,然后输入fastbin,就会出现范围。

由于本人是ubuntu20.04,其是glibc2.31,版本过高,先free掉的chunk储存在新的bin结构:tcachebins

关于这个可以参考:关于如何理解Glibc堆管理器(Ⅶ——Tcache Bins!!)_tcachebins-CSDN博客

但是一般的题目还是不会用到这个新的bins。

(2)分配原则:采取LIFO策略,先进先出,即先释放的chunk将会被更早的分配。当malloc的chunk的大小 <= fastbin的max,那么ptmalloc就会先在fastbin找有没有合适的free_chunk,有的话就给malloc,没有的话就从其他bin中找。

释放chunk的时候,ptmaolloc会先将一些小的chunk放入fastbin中。

(3)异于其他:bin中是单向链表,因为"缺少"bk指针(并不是缺少,而是不会用到,因为fastbin中的chunk还可能会被分到small bin中去)

以下三种bin(s)都是双向链表,大概如下:

2.Unsorted Bin

(1)特点:位于bin数组下标1的位置,bins[1]。unsorted bin只有一项,其内容就是字面意思,没有分类的bin。chunk大小没有要求。

(2)来源:释放的chunk的size大于fastbin,并且该chunk不与top chunk紧邻,那么该chunk就会放到unsorted bin中。

3.small Bin

  • bins[2] -- bins[63]
  • 62个循环双向链表,每一个链表中的chunk大小都是一致的。32位系统下,bin[2]储存16字节的chunk,bin[3]储存24字节......504bytes , chunk_size = 2 * SIZE_SZ * bin_index。
  • FIFO(队列形式)

:可以发现small bin和fast bin对于chunk_size是有重合的

4.Large Bin

  • bin[64] -- bin[126]
  • 63个循环双向链表,每一个bin中chunk大小不一致,处于一定范围内。(详情见参考)
  • 管理大于504bytes(user_data)的free_chunks(32位),第一个larbin的起始chunk_size=512字节
  • FIFO(队列形式)

  • Top Chunk
  • 内容:当程序第一次malloc的时候,heap会被分为两部分,一部分给用户,另外一部分就是top chunk,其地址处于当前堆的最高物理地址。
  • 本质上是free_chunk,会和上面的free_chunk合并
  • 该chunk不被放入bin中当用户区域的bin满足不了用户申请的大小,并且chunk大小合适,才会从这部分分配chunk,剩下没被分配的重新作为top chunk。若是top chunk也不满足用户所申请的内存大小,就会扩展heap。

  • Last_remainder_chunk

用户在malloc申请分配内存的时候,ptmalloc找到的chunk会按照fastbin -> unsorted bin -> small bins -> Large bins顺序去寻找free_chunk,只要size大小大于等于所申请的,就会将这段chunk给用户,而此时给的chunk可能和比申请的大,那么ptmalloc会将这部分chunk分割,只会给用户合适的chunk,那么剩下的部分就被称为last remainder chunk。

注:top chunk 分割剩下的部分不会作为 last remainder 。上面提到了,就是新的top chunk

实操理解:

#include <stdio.h>
#include <stdlib.h>
int main(void)
{
	void *ptr = malloc(0x20);
	free(ptr);
}

gdb调试图:

如图是可以看到Top chunk的size,并且它的物理地址确实位于当前堆的最高物理地址。可以分析这三个chunk_size之和(不包括P位的1),刚好就是所分配的heap大小。

  • malloc_state

该结构是用来管理堆的,记录每个arena当前申请的内存的具体状态。每个arena都只有一个malloc_state结构。

注:main arena 的 malloc_state 并不是 heap segment 的一部分,而是一个全局变量,存储在 libc.so 的数据段

struct malloc_state {
    /* Serialize access.  */
    __libc_lock_define(, mutex);

    /* Flags (formerly in max_fast).  */
    int flags;

    /* Fastbins */
    mfastbinptr fastbinsY[ NFASTBINS ];	
    // fastbin就储存在其中

    /* Base of the topmost chunk -- not otherwise kept in a bin */
    mchunkptr top;

    /* The remainder from the most recent split of a small request */
    mchunkptr last_remainder;

    /* Normal bins packed as described above */
    mchunkptr bins[ NBINS * 2 - 2 ]; 
    // 该bins中储存的上面说的三种bin

    /* Bitmap of bins, help to speed up the process of determinating if a given bin is definitely empty.*/
    unsigned int binmap[ BINMAPSIZE ];

    /* Linked list, points to the next arena */
    struct malloc_state *next;

    /* Linked list for free arenas.  Access to this field is serialized
       by free_list_lock in arena.c.  */
    struct malloc_state *next_free;

    /* Number of threads attached to this arena.  0 if the arena is on
       the free list.  Access to this field is serialized by
       free_list_lock in arena.c.  */
    INTERNAL_SIZE_T attached_threads;

    /* Memory allocated from the system in this arena.  */
    INTERNAL_SIZE_T system_mem;
    INTERNAL_SIZE_T max_system_mem;
};

解释:

  • fastbinsY[ NFASTBINS ]:
    • 存放每个 fast chunk链表头部的指针
  • top
    • 指向Top chunk
  • last_reminder
    • 最新的 chunk 分割之后剩下的那部分
  • bins[ NBINS * 2 - 2 ]
    • 用于储存unsorted bin,small bins和Large bins的chunk链表
  • binmap[ BINMAPSIZE ]
    • ptmalloc用一个 bit 来标识某一个 bin 中是否包含空闲 chunk 。

标签:bin,malloc,chunk,free,CTF,PWN,数据结构,bins,size
From: https://blog.csdn.net/zwb2603096342/article/details/140741978

相关文章

  • 数据结构—红黑树
    红黑树的概念红黑树,是一种二叉搜索树,但在每个结点上增加一个存储位表示结点的颜色,可以是Red或Black。通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路径会比其他路径长出俩倍,因而是接近平衡的。红黑树的性质每个结点不是红色就......
  • Python 教程(二):语法与数据结构
    目录前言专栏列表语法特点实例代码基本数据类型变量命名规则赋值动态类型作用域示例代码运算符`list`、`set`和`dict`数据结构区别1.list(列表)2.set(集合)3.dict(字典)总结前言Python是一种计算机编程语言。每种编程语言都有自己的语法规则。在本教程中,我们将学......
  • BUUCTF 3.warmup_csaw_2016
    拿到题目,我们先运行一下我们发现这道题的样子和BUUCTF的rip很像,一样是让我们输入,一样是在输入超长字符串后程序会崩溃,所以我们可以猜测是一道栈溢出的问题,我们来看一下保护机制我们发现依旧是几乎没开保护机制,所以大概率是一道栈溢出的题。我们看一下IDA我们发现最后的get......
  • 数据结构-二叉树(顺序结构)
    引言顺序结构存储就是使⽤数组来存储,⼀般只适合表⽰完全⼆叉树,因为不是完全⼆叉树会有空间的浪费,完全⼆叉树更适合使⽤顺序结构存储。一、堆的概念将一个元素集合k里,所有数据按照完成二叉树的顺序存储方式存储。并且数组中的元素,满足以下关系i=0、1、2...,则称为......
  • BUUCTF 2.rip
    拿到题目首先运行一下我们可以看到在我第一次运行时我们发现他就是将我们输入的重新输出了一遍,我们可以猜测应该是gets函数输入,然后输出,那我们便可以测试第二次,我们输入一个超长字符串,发现程序崩溃了,我们可以猜测应该是程序没有对长度进行检测而导致的栈溢出,那么我们带着这个猜......
  • 数据结构:顺序表
    顺序表的概述与实现顺序表(SequentialList)是计算机科学中一种常用的数据结构,其特点是用一段连续的存储单元依次存储数据元素。顺序表的底层实现通常采用数组,但与数组不同的是,顺序表封装了对数据的插入、删除、查找等操作,使其使用起来更加灵活和方便。本文将详细介绍顺序表的概......
  • 数据结构:算法复杂度
    目录前言数据结构和算法的基本概念数据结构和算法的重要性衡量算法的好坏时间复杂度空间复杂度例子分析例子1:冒泡排序例子2:对数时间复杂度总结前言在编程学习中,理解数据结构和算法是至关重要的。这不仅是计算机科学的基础知识,也是解决复杂问题和优化代码效率的关......
  • EEG数据结构
    基本数据集信息:EEG.setname-数据集的描述性名称/标题EEG.filename-磁盘上数据集文件的文件名EEG.filepath–数据集文件的文件路径(目录/文件夹EEG.trials-数据集中的历时(或试验)数。如果数据是连续的,则该数字为1。EEG.pnts-每次试验(历元)的时间点(或数据帧)数。如......
  • 数据结构篇——栈的操作实现(顺序栈、链栈)!
    一:前言对于栈的操作,虽不及其他数据结构一样多,但是栈的实际应用却是十分广泛。比如在我们进行代码编写的编译器中,对于函数调用、递归操作、表达式求值以及编译器的括号匹配等问题均是通过反复的入栈和出栈操作进行控制的。栈结构在计算机科学的历史上,地位是举重若轻的,值得我们......
  • 简单的数据结构:栈
    1.栈的基本概念1.1栈的定义栈是一种线性表,只能在一端进行数据的插入或删除,可以用数组或链表来实现,这里以数组为例进行说明栈顶 :数据出入的那一端,通常用Top表示栈底:相对于栈顶的另一端,也是固定的一端,不允许数据的插入和删除空栈:不含数据的栈1.2栈的基本操作栈的初始......