Dance In Heap(一):浅析堆的申请释放及相应保护机制
0×00 前面的话
在内存中,堆是一个很有趣的地方,因为它可以由用户去直接的进行分配与销毁,所以也产生了一些很有趣、奇思妙想的漏洞,像unlink漏洞、House系列漏洞等等。但是在学习的过程中,我们很容易难以理解那些介绍的比较模糊的概念,比如 unsortedbin 在某些条件下会放回 smallbin 或 largebin 中,那到底是什么时候?也会对一些大佬构造的 payload 犯迷糊,为什么这里多了一个chunk,为什么这个字节要填充…,大佬们往往不对这些细节做过多的解释,但是这可难为了我们初学堆利用的新兵,所以,我想写几篇文章,将堆的运作机制,例如一些基本的概念,malloc机制、free机制、保护机制,和利用方法结合起来说一下,让大家能够对堆这一块有个较为清楚的认识,少走一些弯路。首先呢,我想在这篇文章中较为细致的介绍一下堆中的一些情况,剩下的有机会的话我会一并写成一个系列。
这篇文章主要分为四个部分:
0x01 chunk 简介
0x02 bin 简介
0x03 malloc 机制
0x04 free 机制
这些内容相对比较重要,如果看完还觉得不够的,推荐大家去读一下华庭老师的《glibc内存管理ptmalloc源代码分析》。
0×01 chunk 简介
首先先说一下堆是如何分配的,在内存中,堆(低地址到高地址,属性RW)有两种分配方式(与malloc申请chunk做区分):
mmap: 当申请的size大于128kb时,由mmap分配
brk: 当申请的size小于128kb时,由brk分配,第一次分配132KB(main arena),第二次在brk下分配,不够则执行系统调用,向系统申请
在内存中进行堆的管理时,系统基本是以 chunk 作为基本单位,chunk的结构在源码中有定义
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;
};
INTERNAL_SIZE_T 即 size_t
#ifndef INTERNAL_SIZE_T
#define INTERNAL_SIZE_T size_t
#endif
我们可以打印一下本机的 sizeof(size_t),这个长度可以说是一个基准单位
#include<stdio.h>
int main() {
printf("sizeof(size_t) is %d\n",sizeof(size_t));
return 0;
}
这个结构不再多谈,相关的介绍网上很多,主要提一下结构体中最后两个指针 fd_nextsize 和 bk_nextsize,这两个指针只在 largebin 中使用,其他情况下为 NULL。我们可以根据 chunk 的状态将其分为三种(allocated chunk、free chunk、top chunk):
allocated chunk:
chunk header:
prev_size(当上一块是free状态时,存储该chunk的size,否则被上一块chunk使用)
size(该chunk大小(包括chunk header),某位3 bits为标志位)
0bit表示上一chunk是否free
1bit表示该chunk是否由mmap分配
2bit表示该chunk是否属于main arena
data:
free chunk:
chunk header:
prev_size:
size:
fd:指向 bin 中的next chunk
bk:指向 bin 中的last chunk(bin中先进的为last,后进的为next)
fd_nextsize:
bk_nextsize:
top chunk:brk中未分配的顶端chunk
chunk header:
prev_size:
size:
其中在 free chunk中有一种特殊的chunk(last remainder chunk):
last remainder chunk:从free chunk中malloc时,如果该chunk足够大,那么将其分为两部分,未分配的放到last remainder中并交由 unsorted bin 管理。
重点强调一下:这里的上一块表示在内存的堆中连续的chunk的上一块,区别bin中的前后关系。另外 chunk 的前后关系只有在bin中是使用fd、bk指针标识的,在内存中连续的chunk则通过 prev_size 和 size 来寻找前后 chunk,当然,这也就造成了漏洞。
由于chunk会在几种状态之间切换,当其为free chunk时,最少需要4*sizeof(size_t)的空间,所以有最小分配大小。
并且由于prev_size的复用,所以实际申请的大小为 max(2sizeof(size_t)(chunk_header)-sizeof(size_t)(prev_size)+申请大小,最小分配大小),而且 chunk的size是按照 2sizeof(size_t)对齐的,也就是说当你申请一个不是 2*sizeof(size_t)整倍数的空间时, malloc 返回的 size 有会对齐,大于实际申请的空间。
另外提一下,当 malloc 一个chunk后,实际返回用户的地址为chunk除去chunk header后的地址,而在bin中存储的是chunk的地址,也就是说
p = malloc(0x40); // 假设chunk的地址为 0xdeadbeef,则返回给用户的地址是 0xdeadbeef+sizeof(chunk header)
free(p) //将p释放掉后,保存在bin中的地址为 0xdeadbeef
0×02 bin简介
bin在内存中用来管理free chunk,bin为带有头结点(链表头部不是chunk)的链表数组,根据特点,将bin分为四种,分别为(fastbin、unsortedbin、smallbin、largebin):
fastbin:
根据chunk大小维护多个单向链表
sizeof(chunk) < 64(bytes)
下一chunk(内存中)的free标志位不取消,显示其仍在使用
后进先出(类似栈),先free的先被malloc
拥有维护固定大小chunk的10个链表
unsortedbin:
双向循环链表
不排序
暂时存储free后的chunk,一段时间后会将chunk放入对应的bin中(详见0x02)
只有一个链表
smallbin:
双向循环链表
sizeof(chunk) < 512 (bytes)
先进先出(类似队列)
16,24...64,72...508 bytes(62个链表)
largebin:
双向循环链表
sizeof(chunk) >= 512 (bytes)
free chunk中多两个指针分别指向前后的large chunk
63个链表:0-31(512+64*i)
32-48(2496+512*i)
...
链表中chunk大小不固定,先大后小
这其中 fastbin 像是cache,用来实现快速的chunk分配,其中的chunk size大小与smallbin中的有重复(只是说大小,chunk并不重复)
unsortedbin 功能也是作为cache,尽量减少搜索合适chunk的时间。
这四个bin中,除了fastbin,其他三个都是维护双向循环链表,并且由一个长度为128 size_t的数组bins维护,bins结构如下:
| NULL | unsortbin | smallbin | largebin | NULL |
|---|---|---|---|---|
| 0 | 1 | 2-63 | 64-126 | 127 |
0×03 malloc机制
malloc功能主要由 _int_malloc() 函数实现,原型如下:
static Void_t* _int_malloc(mstate av,size_t bytes)
当接收到申请的内存大小后,我们看一下malloc的申请过程。
长度位于 fastbin 时:
1.根据大小获得fastbin的index
2.根据index获取fastbin中链表的头指针
如果头指针为 NULL,转去smallbin
3.将头指针的下一个chunk地址作为链表头指针
4.分配的chunk保持inuse状态,避免被合并
5.返回除去chunk_header的地址
长度位于 smallbin 时:
1.根据大小获得smallbin的index
2.根据index获取smallbin中双向循环链表的头指针
3.将链表最后一个chunk赋值给victim
4.if(victim == 表头)
链表为空,不从smallbin中分配
else if(victim == 0)
链表未初始化,将fastbin中的chunk合并
else
取出victim,设置inuse
5.检查victim是否为main_arena,设置标志位
6.返回除去chunk_header的地址
长度位于 largebin 时:
1.根据大小获得largebin的index
2.将fastbin中chunk合并,加入到unsortbin中
留意一点:系统实际分配的内存地址与返回的地址是不同的,返回的地址直接指向了除去 chunk header 的地址。
当然,我们注意到上面的分配过程并没有完成,当 smallbin 中没有 chunk 或者 smallbin 未初始化时,并没有返回分配结果,这种情况下的chunk分配将在后面与largebin的分配一起处理
unsortedbin:
1.反向遍历unsortedbin,检查 2*size_t<chunk_size<内存总分配量
2.unsortedbin的特殊分配:
如果前一步smallbin分配未完成
并且 unsortedbin中只有一个chunk
并且该chunk为 last remainder chunk
并且该chunk大小 >(所需大小+最小分配大小)
则切分一块分配
3.如果请求大小正好等于当前遍历chunk的大小,则直接分配
4.继续遍历,将合适大小的chunk加入到smallbin中,向前插入作为链表的第一个chunk。(smallbin中每个链表中chunk大小相同)
5.将合适大小的chunk加入到largebin中,插入到合适的位置(largebin中每个链表chunk由大到小排列)
largebin:
1.反向遍历largebin,由下到上查找,找到合适大小后切分
切分后大小<最小分配大小,返回整个chunk,会略大于申请大小
切分后大小>最小分配大小,加入 unsortedbin。
2.未找到,index+1,继续寻找
如果这之后还未找到合适的chunk,那么就会使用top chunk进行分配,还是没有的话,如果在多线程环境中,fastbin可能会有新的chunk,再次执行合并,并向unsortedbin中重复上面,还是没有的话,就只能向系统申请了。
以上就是malloc分配的全经过。
几个malloc检查:
1.从fastbin中取出chunk后,检查size是否属于fastbin
2.从smallbin中除去chunk后,检查victim->bk->fd == victim
3.从unsortbin取chunk时,要检查2*size_t<chunk_size<内存总分配量
4.从largebin取chunk时,切分后的chunk要加入unsortedbin,需要检查 unsortedbin的第一个chunk的bk是否指向unsortedbin
0×04 free机制
1.首先使用 chunksize(p) 宏获取p的size
#define PREV_INUSE 0x1
#define IS_MMAPPED 0x2
#define NON_MAIN_ARENA 0x4
#define SIZE_BITS (PREV_INUSE|IS_MMAPPED|NON_MAIN_ARENA)
#define chunksize(p) ((p)->size & ~(SIZE_BITS))
也就是直接屏蔽了控制位信息,不过不要紧,chunk的分配是 2*sizeof(size_t) 对齐的,所以屏蔽低三位对大小无影响
2.安全检查:
chunk的指针地址不能溢出
chunk 的大小 >= MINSIZE(最小分配大小),并且检查地址是否对齐
3.大小为fastbin的情况(不改变inuse位)
1).检查下一个chunk的size:2*size_t<chunk_size<内存总分配量
2).double free检查:
检查当前free的chunk是否与fastbin中的第一个chunk相同,相同则报错
简单的小例子
#include <stdio.h>
#include <stdlib.h>
int main() {
char *a=malloc(24);
char *b=malloc(24);
free(a);
free(a);
}
报错
#include <stdio.h>
#include <stdlib.h>
int main() {
char *a=malloc(24);
char *b=malloc(24);
free(a);
free(b);
free(a);
}
没问题
4.其他情况
1).检查下一个chunk的size:2*size_t<chunk_size<内存总分配量
如果当前 chunk 为 sbrk()分配,那么它相邻的下一块 chunk 超过了分配区的地址,会报错
2).double free检查:
检查当前free的chunk是否为top chunk,是则报错
根据下一块的inuse标识检查当前free的chunk是否已被free
3) unlink合并:
检查前后chunk是否free,然后向后(top chunk方向)合并,并改变对应的inuse标志位
unlink检查: I.当前chunk的size是否等于下一chunk的prev_size
II.P->bk->fd == P && P->bk->fd == P
如果合并后 chunk_size > 64bytes,则调用函数合并fastbin中的chunk到unsortedbin中
将合并后的chunk加入unsortedbin
4) unsortedbin检查
需要检查 unsortedbin的第一个chunk的bk是否指向unsortedbin
我们可以看到,针对free的检查主要是下一块的size和inuse位,另外fastbin的检查可以用来做double free。
0×05
以上就是对堆的情况所做的一些介绍,了解堆的保护机制后,我们便可以在攻击时想办法进行绕过,从而构造出那些光怪陆离的payload。
Dance In Heap(一):浅析堆的申请释放及相应保护机制的更多相关文章
- Dance In Heap(二):一些堆利用的方法(上)
0×00 前面的话 在前面的文章里我们稍微有点啰嗦的讲解了堆中的一些细节,包括malloc.free的详细过程,以及一些检查保护机制,那在这篇文章里,我们就开始结合这些机制,以64位为例来看一看如何对 ...
- /MT、/MD编译选项,以及可能引起在不同堆中申请、释放内存的问题
一.MD(d).MT(d)编译选项的区别 1.编译选项的位置 以VS2005为例,这样子打开: 1) 打开项目的Property Pages对话框 2) 点击左侧C/C ...
- Java内存泄漏分析系列之六:JVM Heap Dump(堆转储文件)的生成和MAT的使用
原文地址:http://www.javatang.com JVM Heap Dump(堆转储文件)的生成 正如Thread Dump文件记录了当时JVM中线程运行的情况一样,Heap Dump记录了J ...
- Java堆内存Heap与非堆内存Non-Heap
堆(Heap)和非堆(Non-heap)内存 按照官方的说法:“Java 虚拟机具有一个堆,堆是运行时数据区域,所有类实例和数组的内存均从此处分配.堆是在 Java 虚拟机启动时创建的.”“在 ...
- Binary Heap(二叉堆) - 堆排序
这篇的主题主要是Heapsort(堆排序),下一篇ADT数据结构随笔再谈谈 - 优先队列(堆). 首先,我们先来了解一点与堆相关的东西.堆可以实现优先队列(Priority Queue),看到队列,我 ...
- javascript内存管理(堆和栈)和javascript运行机制
内存基本概念 内存的生命周期: 1.分配所需的内存 2.内存的读与写 3.不需要时将其释放 所有语言的内存生命周期都基本一致,不同的是最后一步在低级语言中很清晰,但是在像JavaScript 等高级语 ...
- R3环申请内存时页面保护与_MMVAD_FLAGS.Protection位的对应关系
Windows内核分析索引目录:https://www.cnblogs.com/onetrainee/p/11675224.html 技术学习来源:火哥(QQ:471194425) R3环申请内存时页 ...
- heap size eclipse 堆内存
可以根据eclipse 或 myeclipse heapstats 使用情况调整堆内存大小,heap size 设置,-vmargs-Xms256-Xmx1024 ,其中Xms表示初始值,Xmx表示最 ...
- 05-树6. Path in a Heap (25) 小根堆
05-树6. Path in a Heap (25) Time Limit: 20 Sec Memory Limit: 256 MB 题目连接 http://www.patest.cn/contes ...
随机推荐
- NPM包的安装及卸载
NPM全名:node package manager,是node包管理工具,负责安装.卸载.更新等.新版的NodeJS已经集成了npm.所以装好NodeJS的同时,npm也已经装好了! 可以用cmd命 ...
- Python 前端 js基础
Javascript 概述 JavaScript是一门编程语言,浏览器内置了JavaScript语言的解释器,所以在浏览器上按照JavaScript语言的规则编写相应代码之,浏览器可以解释并做出相应的 ...
- luogu2765 魔术球问题
发现好像没人来证明贪心啊--那我来写一下它的证明 欲证明:放一个数在已有的柱上(如果可以)总是比新开一个柱更优的 假如已经放了x1..x2....xu..xv..xw.... 现在我要放xx 我有两种 ...
- day01_09.你已学会编程
目前你已经学会编程: 学会变量,运算,控制,你就学会了编程,我擦?真的,假的? 1.打印1-100,自己试试看呗 <?php $num = 1; while($num<=100){ ech ...
- 【LeetCode】Longest Common Prefix(最长公共前缀)
这道题是LeetCode里的第14道题. 题目描述: 编写一个函数来查找字符串数组中的最长公共前缀. 如果不存在公共前缀,返回空字符串 "". 示例 1: 输入: ["f ...
- Python hash、xml、configparser、sheve、shutil模块讲解 以及 面向对象初识
今日内容: 1.hash模块2.xml模块3.configparser模块4.sheve 模块5.shutil模块 知识点一:hash什么是hash: hash是一种算法,该算法接受传入的的内容,经过 ...
- P1108 低价购买 (动态规划)
题目链接 Solution 似乎就是个很简单的最长不上升子序列输出方案. 但是有一个很艹蛋的条件: 不同方案选择价格必须不同. 且其股票价格不保证不相同. \(f[i]\) 代表以第 \(i\) 天结 ...
- webpack打包字体图标报错的解决办法
webpack打包字体图标需要两个加载器 url-loader 和 file-loader 另外 字体图标的引入方式 本来应该是 url("....") 这样的方式,但是w ...
- 开始学习es6(一) 搭建个es6的开发环境
1.开始学习es6 如果想在浏览器跑es6 需要给es6个环境 因为一直用vue-cli全家桶 这样虽然方便 但如果用es6需要跑起个vue全家桶 于是想到可以用gulp搭建个开发环境 首先需要1. ...
- canvas之webgl的打开方式
OK,一个完整的dome是从无到有的.这篇文章将记录我的webgl之旅.不定时更新. 首先,新建一个canvas元素. 然后获取到它. var e = document.ElementById('ca ...