CTF-PWN 堆的相关数据结构

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

参考

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 。
  • 16
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
ctfd-pwn是一个非常受欢迎的CTF(Capture The Flag)比赛中的一个赛题类型,它主要涉及二进制漏洞的利用和系统安全的挑战。 在ctfd-pwn赛题的收集过程中,通常需要考虑以下几个方面: 1. 题目类型:ctfd-pwn赛题可以包含多种类型的漏洞,例如缓冲区溢出、格式化字符串漏洞、整数溢出等。在收集赛题时需要确保涵盖各种漏洞类型,增加题目的多样性和挑战性。 2. 难度级别:赛题的难度级别应该根据参赛者的水平来确定。可以设置多个难度级别的赛题,包括初级、中级和高级,以便参赛者可以逐步提高自己的技能。 3. 原创性:收集ctfd-pwn赛题时应尽量保持赛题的原创性,避免过多的抄袭或重复的赛题。这有助于增加参赛者的学习价值,同时也能提高比赛的公平性。 4. 实用性:收集的赛题应该具有实际应用的意义,能够模拟真实的漏洞和攻击场景。这样可以帮助参赛者更好地理解和掌握系统安全的基本原理。 5. 文档和解答:为每个收集的赛题准备详细的文档和解答是很有必要的。这些文档包括赛题的描述、利用漏洞的步骤和参考资源等,可以帮助参赛者更好地理解赛题和解题思路。 6. 持续更新:CTF比赛的赛题应该定期进行更新和维护,以适应不断变化的网络安全环境。同时也要根据参赛者的反馈和需求,不断收集新的赛题,提供更好的比赛体验。 综上所述,ctfd-pwn赛题的收集需要考虑赛题类型、难度级别、原创性、实用性、文档和解答的准备,以及持续更新的需求。这样才能提供一个富有挑战性和教育性的CTF比赛平台。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值