本期内容完全是为刚入门的新宝准备滴~
通俗易懂 满满干货
记得军训间隙偷偷卷起来呀
下面是一个示例,假设一个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从低地址往高地址
prev_size size
usr域
prev_size size
usr域
即一定是prev_size在0x?????0的位置开始
所以申请0x30,返回0x41
申请0x38,也返回0x41
这之间的0x8字节由下一个chunk的prev_size来写,也就是说申请了0x30,但其实写0x38也是可以的(没有攻击性,但是做开发的话不要这样子),但是申请0x38,最多就真的只能写0x38
只有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
最大能放 0x408 的,再大就要按照原本的那样放到 unsortedbin 中了
共有64条tcache链表
程序再次申请内存块的时候首先判断在 tchche 中是否存在,如果存在的话会先从 tcache 中拿
后进先出
tcache在往里放的时候没有检查,可以连续free都放到teache中
而fastbin会检查是不是连续free,fastbin的double中间需要free一个其他的小chunk放到fastbin中来越过检查
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期】堆的基本知识
- 左青龙
- 微信扫一扫
- 右白虎
- 微信扫一扫
评论