堆的基本知识

admin 2024年8月6日16:08:59评论8 views字数 3077阅读10分15秒阅读模式

本期内容完全是为刚入门的新宝准备滴~

通俗易懂 满满干货

记得军训间隙偷偷卷起来呀

堆的基础知识

                 ——二进制

堆基本数据结构分析

关于AMP位的描述:
下面是一个示例,假设一个Chunk的size字段为0x605,即二进制为 11000000101,那么它的AMP分别为:

A(0x1): 因为最低的1位为1,所以该Chunk为Arena Chunk。
M(0x2): 因为第二位为1,所以该Chunk为Main Arena Chunk。
P(0x4): 因为第三位为1,所以该Chunk为Prev in-use。

1 1 0 0 0 0 0 0 0 1 0 1
| | |             | | |
| | |             | | P (Prev in-use)
| | |             | M (Main Arena Chunk)
| | |             A (Arena Chunk)
| | |
| | +-------------+
| |   Chunk size (0x605)
| |
| +-----------------+
|   Chunk state (ALLOCATED)
|
+-------------------+
Unused (always 0)

struct malloc_chunk {

INTERNAL_SIZE_T      prev_size;  /* Size of previous chunk (if free). */
INTERNAL_SIZE_T      size;       /* Size in bytes, including overhead. */

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;
};

prev_size, 如果该 chunk 的物理相邻的前一地址chunk(两个指针的地址差值为前一chunk大小)是空闲的话,那该字段记录的是前一个 chunk 的大小(包括 chunk 头)。否则,该字段可以用来存储物理相邻的前一个chunk 的数据。这里的前一 chunk 指的是较低地址的 chunk 。

size ,该 chunk 的大小,大小必须是 2 * SIZE_SZ 的整数倍。如果申请的内存大小不是 2 * SIZE_SZ 的整数倍,会被转换满足大小的最小的 2 * SIZE_SZ 的倍数。32 位系统中,SIZE_SZ 是 4;64 位系统中,SIZE_SZ 是 8。该字段的低三个比特位对 chunk 的大小没有影响,它们从高到低分别表示:
NON_MAIN_ARENA,记录当前 chunk 是否不属于主线程,1表示不属于,0表示属于。
IS_MAPPED,记录当前 chunk 是否是由 mmap 分配的。
PREV_INUSE,记录前一个 chunk 块是否被分配。一般来说,堆中第一个被分配的内存块的 size 字段的P位都会被设置为1,以便于防止访问前面的非法内存。当一个 chunk 的 size 的 P 位为 0 时,我们能通过 prev_size 字段来获取上一个 chunk 的大小以及地址。这也方便进行空闲chunk之间的合并。

fd,bk。chunk 处于分配状态时,从 fd 字段开始是用户的数据。chunk 空闲时,会被添加到对应的空闲管理链表中,其字段的含义如下
fd 指向下一个(非物理相邻)空闲的 chunk
bk 指向上一个(非物理相邻)空闲的 chunk
通过 fd 和 bk 可以将空闲的 chunk 块加入到空闲的 chunk 块链表进行统一管理

fd_nextsize, bk_nextsize,也是只有 chunk 空闲的时候才使用,不过其用于较大的 chunk(large chunk)。
fd_nextsize 指向前一个与当前 chunk 大小不同的第一个空闲块,不包含 bin 的头指针。
bk_nextsize 指向后一个与当前 chunk 大小不同的第一个空闲块,不包含 bin 的头指针。
一般空闲的 large chunk 在 fd 的遍历顺序中,按照由大到小的顺序排列。这样做可以避免在寻找合适chunk 时挨个遍历。

size_addr + size - 0x8 -> next_chunk_prve_size

malloc_addr + size - 0x10 -> next_chunk_prve_size

malloc函数返回的地址指向user域,prve_size和size都在user前面的chunk头部分

chunksize(P) == 0x200 == 0x200 == prev_size (next_chunk(P))

根据下一个chunk的prve_size找到p,看p的size,找到下一个chunk的位置,看下一个chunk的prve_size

对于用户而言指向usr
对于计算机而言指向chunk头,
申请chunk从低地址往高地址

关于chunk的对齐问题

chunk一定会对齐为

prev_size   size

usr域

prev_size   size

usr域

即一定是prev_size在0x?????0的位置开始

所以申请0x30,返回0x41

申请0x38,也返回0x41

这之间的0x8字节由下一个chunk的prev_size来写,也就是说申请了0x30,但其实写0x38也是可以的(没有攻击性,但是做开发的话不要这样子),但是申请0x38,最多就真的只能写0x38

各种bins中的chunk情况

1.fast bin

只有fd指针,为单链表,fd指向下一个,fast bin为栈结构,后进先出

检查:

1.size检查,0x10级别的数相同,p位都为1

2.fd检查,fd需要指向一个chunk头

2.small bin

small_bin依靠bk来索引下一个,依靠下一个的fd来判断有没有错

绕过的检查:双向链表,a->bk = b那就需要b->fd = a

tcache

同一大小的 chunk free 之后前 7 个会放到一个 tcache 链表里面,不同大小的放在不同的链表中

最大能放 0x408 的,再大就要按照原本的那样放到 unsortedbin 中了

共有64条tcache链表

程序再次申请内存块的时候首先判断在 tchche 中是否存在,如果存在的话会先从 tcache 中拿

后进先出

tcache在往里放的时候没有检查,可以连续free都放到teache中

fastbin会检查是不是连续free,fastbin的double中间需要free一个其他的小chunk放到fastbin中来越过检查

fastbin_dup_into_stack

申请三个0x8,记为a,b,c

free(a),free(b),free(a)形成fastbin中的循环嵌套

d=malloc(0x8)

e=malloc(0x8)

此时fastbin中只有指向a的空闲chunk,但此时a已经被申请为d,可以通过修改d的user域来修改fastbin中的fd指针,改为某个栈地址addr,该栈地址后面跟一个0x20表示大小,伪造的fastbin中的chunk

fd size

malloc(0x8),此时的malloc会申请到某个栈地址addr,我可用的user域从0x20的位置后面开始

本期内容到此结束

表哥后续还会不定时更新相关基础知识

记得关注我们哦~

对网络安全感兴趣的宝儿速速进群 

堆的基本知识

原文始发于微信公众号(SKSEC):【表哥有话说 第99期】堆的基本知识

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年8月6日16:08:59
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   堆的基本知识http://cn-sec.com/archives/2039238.html

发表评论

匿名网友 填写信息