pwn-堆入门-祝👴生日快乐!
0-Preview
我的进度有点快了,之前的ret2系列和各种保护绕过还没学。
直接跳过来学堆,这也是没办法的事,五天后就有一场比赛。
打好了可以去重庆决赛;可以次火锅儿~
我现在终于是明白了为啥堆难学了。我现在都还没找到一个系统讲堆的视频或者博客。
都是 ctf-wiki 或者学习 ctf-wiki 的笔记。这很玄学,也证明了这玩意儿真的难学。
接下来也快带学弟了,我会尽全力把博客写的完整一些,通俗易懂一些(前提是我能学会
差点少了精髓:
👴
1-Heap(堆)是什么
这个上一篇差缺补漏环境大概描述了一下:数据段
在程序运行过程中,堆可以提供动态分配的内存,允许程序申请大小未知的内存。堆其实就是程序虚拟地址空间的一块连续的线性区域,它由低地址向高地址方向增长。我们一般称管理堆的那部分程序为堆管理器。
堆管理器处于用户程序与内核中间:
- 响应用户的申请内存请求,向操作系统申请内存,然后将其返回给用户程序。同时,为了保持内存管理的高效性,内核一般都会预先分配很大的一块连续的内存,然后让堆管理器通过某种算法管理这块内存。只有当出现了堆空间不足的情况,堆管理器才会再次与操作系统进行交互。
- 管理用户所释放的内存。一般来说,用户释放的内存并不是直接返还给操作系统的,而是由堆管理器进行管理。这些释放的内存可以来响应用户新申请的内存的请求。
目前 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 | C |
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 指针指向一个 chunk
的开始,一个 chunk
中包含了用户请求的内存区域和相关的控制信息。图中的 mem 指针 才是真正 返回给用户 的 内存指针 。
P 标志位
chunk
的第二个域的最低一位为 P
,它表示前一个块 是否在使用 中。
P 为 0 ,表示前一个 chunk 为空闲,
prev_size
有效,prev_size
表示前一个chunk
的size
, 程序可以使用这个值来找到前一个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。
当 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 | C |
写那么多 Sleep是因为我很菜,方便下断点调试。
1 | BASH |
第一个断点下在开头,这时候还没有执行 malloc
我们来看一下此刻程序的堆和内存:
继续往下走,下一个 sleep
断点处,是已经执行过一个 malloc
指令了:
可以看到,目前堆中已经产生了相应信息,内存中也给堆分配了空间。
但是还是有两个疑问:
1、为什么申请出来的0x10对应的
Size
应该是0x21?为什么我的堆在0x21
之前还有个0x251
的?
2、系统是怎么分配堆的内存的?为什么会分配在这个位置?
这些东西明天或者之后再解决,堆还是有点难度的。
今天的时间来不及了。
5-👴明天生日
实际上快了,写到这儿已经十一点半了。
👴马上去健个身就睡觉,最近的作息太乱了,搞的口腔溃疡越来越严重。
👴送给👴自己的生日礼物就是这七天的文章了。
国庆七天PWN 完美收官!
这七天学习强调很满意了,一天能完全投入学习七八个小时已经很离谱了。
所以说?👴明天就20了?离谱!离大谱!
容我偷偷把时间改成明天零点。
祝我生日快乐!