0-Preview

我的进度有点快了,之前的ret2系列和各种保护绕过还没学。
直接跳过来学堆,这也是没办法的事,五天后就有一场比赛。
打好了可以去重庆决赛;可以次火锅儿~

我现在终于是明白了为啥堆难学了。我现在都还没找到一个系统讲堆的视频或者博客。
都是 ctf-wiki 或者学习 ctf-wiki 的笔记。这很玄学,也证明了这玩意儿真的难学。
接下来也快带学弟了,我会尽全力把博客写的完整一些,通俗易懂一些(前提是我能学会
差点少了精髓:
👴

1-Heap(堆)是什么

这个上一篇差缺补漏环境大概描述了一下:数据段
image-20211007212027749

在程序运行过程中,堆可以提供动态分配的内存,允许程序申请大小未知的内存。堆其实就是程序虚拟地址空间的一块连续的线性区域,它由低地址向高地址方向增长。我们一般称管理堆的那部分程序为堆管理器。

堆管理器处于用户程序与内核中间:

  1. 响应用户的申请内存请求,向操作系统申请内存,然后将其返回给用户程序。同时,为了保持内存管理的高效性,内核一般都会预先分配很大的一块连续的内存,然后让堆管理器通过某种算法管理这块内存。只有当出现了堆空间不足的情况,堆管理器才会再次与操作系统进行交互
  2. 管理用户所释放的内存。一般来说,用户释放的内存并不是直接返还给操作系统的,而是由堆管理器进行管理。这些释放的内存可以来响应用户新申请的内存的请求。

目前 Linux 标准发行版中使用的堆分配器是 glibc 中的堆分配器:ptmalloc2。
ptmalloc2 主要是通过 malloc/free 函数来分配和释放内存块。
Linux 有这样的一个基本内存管理思想,只有当真正访问一个地址的时候,系统才会建立虚拟页面与物理页面的映射关系。 所以虽然操作系统已经给程序分配了很大的一块内存,但是这块内存其实只是虚拟内存。只有当用户使用到相应的内存时,系统才会真正分配物理页面给用户使用。

2-堆的基本操作

2.1-malloc

malloc 函数返回对应大小字节的内存块的指针。此外,该函数还对一些异常情况进行了处理:

🍌当 n=0 时,返回当前系统允许的堆的最小内存块。
🍌当 n 为负数时,由于在大多数系统上,size_t 是无符号数(这一点非常重要),所以程序就会申请很大的内存空间,但通常来说都会失败,因为系统没有那么多的内存可以分配。

2.2-free

free(void* p)
free 函数会释放由 p 所指向的内存块。这个内存块有可能是通过 malloc 函数得到的,也有可能是通过相关的函数 realloc 得到的。此外,该函数也同样对异常情况进行了处理

🍌当 p 为空指针时,函数不执行任何操作。
🍌当 p 已经被释放之后,再次释放会出现乱七八糟的效果,这其实就是 double free
🍌除了被禁用 (mallopt) 的情况下,当释放很大的内存空间时,程序会将这些内存空间还给系统,以便于减小程序所使用的内存空间。

3-Chunk

chunk 是堆的最小操作单元。也是一个 结构体

1
2
3
4
5
6
7
8
9
10
11
12
C
struct malloc_chunk
{
INTERNAL_SIZE_T prev_size; // 存放上一块chunk的大小
INTERNAL_SIZE_T size; // 存放当前chunk大小,包括chunk头的大小
//上面那俩就是 chunk 头
struct malloc_chunk* fd;
struct malloc_chunk* bk;
仅用于较大的块:指向下一个较大大小的指针.
struct malloc_chunk* fd_nextsize;
struct malloc_chunk* bk_nextsize;
};

prev_size
如果前一个 chunk 是空闲的,该域表示前一个 chunk 的大小,如果前一个 chunk不空闲,该域无意义。
size
当前 chunk 的大小,并且记录了当前 chunk 和前一个 chunk 的一些属性,包括前一个 chunk 是否在使用中,当前 chunk 是否是通过 mmap 获得的内存,当前 chunk 是否属于非主分配区。
fd 和 bk
指针 fd 和 bk 只有当该 chunk 块空闲时才存在,其作用是用于将对应的空闲chunk 块加入到空闲 chunk 块链表中统一管理,如果该 chunk 块被分配给应用程序使用,那么这两个指针也就没有用(该 chunk 块已经从空闲链中拆出)了,所以也当作应用程序的使用空间,而不至于浪费。 fd_nextsize 和 bk_nextsize
当当前的 chunk 存在于 large bins 中时,large bins 中的空闲chunk 是按照大小排序的,但同一个大小的 chunk 可能有多个,增加了这两个字段可以加快遍历空闲 chunk,并查找满足需要的空闲 chunk,fd_nextsize 指向下一个比当前 chunk 大小大的第一个空闲 chunk,bk_nextszie 指向前一个比当前 chunk 大小小的第一个空闲 chunk。如果该 chunk 块被分配给应用程序使用,那么这两个指针也就没有用(该 chunk 块已经从 size链中拆出)了,所以也当作应用程序的使用空间,而不至于浪费。

使用中的 Chunk
image-20211007220213269

chunk 指针指向一个 chunk 的开始,一个 chunk 中包含了用户请求的内存区域和相关的控制信息。图中的 mem 指针 才是真正 返回给用户 的 内存指针

P 标志位

chunk 的第二个域的最低一位为 P,它表示前一个块 是否在使用 中。

P 为 0 ,表示前一个 chunk 为空闲, prev_size 有效,prev_size 表示前一个 chunksize程序可以使用这个值来找到前一个 chunk 的开始地址
P 为 1 ,表示前一个 chunk 正在使用中,prev_size 无效,程序也就不可以得到前一个 chunk 的小。不能对前一个chunk进行任何操作。
ptmalloc分配的第一个块总是将 P 设为 1,以防止程序引用到不存在的区域。

M 标志位

Chunk 的第二个域的倒数第二个位为 M,他表示当前 chunk 是从哪个内存区域获得的虚拟内存
M 为 1 表示该 chunk 是从 mmap 映射区域分配的,否则是从 heap 区域分配的。

A 标志位

Chunk 的第二个域倒数第三个位为 A,表示该 chunk 属于主分配区或者非主分配区,如果属于非主分配区,将该位置为 1,否则置为 0。

空闲 Chunkimage-20211007220251635

当 chunk 空闲时,其 M 状态不存在,只有 AP 状态,原本是 用户数据区 的地方存储了四个 指针fd 指向后一个空闲的 chunk
bk 指向前一个空闲的 chunk
ptmalloc 通过这两个指针将大小相近的 chunk 连成一个双向链表
对于 large bin 中的空闲 chunk,还有两个指针**fd_nextsize **和 bk_nextsize,这两个指针用于加快在 large bin 中查找最近匹配的空闲chunk。不同的 chunk 链表又是通过 bins 或者 fastbins 来组织的

4-Chunk实验

为了更方便理解,还是上手自己试验一下更好。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
C
#include<stdio.h>
#include<stdlib.h>
int main()
{
sleep(0.1);
char *s0 = malloc(0x10);
sleep(0.1);
char *s01 = malloc(0x10);
sleep(0.1);
char *s02 = malloc(0x10);
sleep(0.1);
char *s03 = malloc(0x10);
sleep(0.1);
char *s1 = malloc(0x100);
sleep(0.1);
char *s2 = malloc(0x500);
sleep(0.1);
char *s3 = malloc(0x1000);
sleep(0.1);
free(s0);
sleep(0.1);
free(s01);
sleep(0.1);
free(s02);
sleep(0.1);
free(s03);
sleep(0.1);
free(s2);
printf("Shangu is very handsome!")
return 0;
}

写那么多 Sleep是因为我很菜,方便下断点调试。

1
2
3
BASH
gcc -no-pie test.c -o test
这次我们研究的 64位linux,就不用指定别的参数了,有空我再写个交叉编译的博客

第一个断点下在开头,这时候还没有执行 malloc 我们来看一下此刻程序的堆和内存:
image-20211007230744846
image-20211007231744785

继续往下走,下一个 sleep 断点处,是已经执行过一个 malloc 指令了:
image-20211007232705588

可以看到,目前堆中已经产生了相应信息,内存中也给堆分配了空间。
但是还是有两个疑问:

1、为什么申请出来的0x10对应的Size应该是0x21?为什么我的堆在 0x21 之前还有个 0x251的?
2、系统是怎么分配堆的内存的?为什么会分配在这个位置?

这些东西明天或者之后再解决,堆还是有点难度的。
今天的时间来不及了。

5-👴明天生日

实际上快了,写到这儿已经十一点半了。
👴马上去健个身就睡觉,最近的作息太乱了,搞的口腔溃疡越来越严重。
👴送给👴自己的生日礼物就是这七天的文章了。
国庆七天PWN 完美收官!
这七天学习强调很满意了,一天能完全投入学习七八个小时已经很离谱了。

猛然惊醒,我竟然已经忘了我几岁了,去找个在线网站算一下吧。
image-20211007233741682

所以说?👴明天就20了?离谱!离大谱!
容我偷偷把时间改成明天零点。

祝我生日快乐!

happme