CC BY 4.0 (除特别声明或转载文章外)
如果这篇博客帮助到你,我会很高兴~
因为我比较注重文章的结构性,所以这里只是简单的列举一些相关概念和简单直观的解释(记录已经理解了的知识),源码阅读和调试这些具体细节的知识会单独开文章记录
主体知识
malloc与free(32位系统,64位所有size*2)
- 第一次执行malloc时,申请空间小于128kb,调用brk,反之调用mmap
- 若第一次调用malloc是用brk这种,则kernel一定分配132kb,这部分为main arena
- free时,这些空间被堆管理器管理,先不还给kernel
- arena:堆管理器向操作系统申请一段足够大的物理空间并映射到虚拟内存空间中,这段虚拟空间就叫arena,一个进程有多个线程,每个线程都可以申请arena
- 管理器:功能命名法(栈是本质命名法),本质为动态链接库中(linux系统下就是glibc)的一段极其复杂代码。为用户态代码,所以封装了一些系统调用来操控物理的heap内存段
- 内存分配策略:申请的空间需要对齐,所以有request2size(req)的宏
chunk
- chunk:在glibc中就是一段结构体,被malloc出去的一个空间,free掉后会进入bin的linked list
源码
struct malloc_chunk {
NTERNAL_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;
};
结构
- 每个chunk都分为chunk header和chunk data两部分,chunk header包含了一些控制信息,是chunk多样化的原因
- 大体可以分为allocated chunk,free chunk,top chunk
思考:为什么要设计这么多的chunk,为了解决什么样的现实问题?
malloced chunk
prev size:记录上一个free chunk的大小,如果上一个chunk不是free掉则可以被覆用为data,当连续的上一块内存为free chunk时才会有这个信息 size:当前chunk所有空间的大小,也(可能)是数据段的起始地址
size段低三位的控制信息(涉及内存分配策略)
N:是否不属于main arena,1表示不属于,0表示属于
M:是否为mmap分配的内存,1表示是,0表示不是
P:记录连续的上一个chunk是否为free chunk,如果不是是malloced chunk,那么自身被free掉的时候会与上一个free chunk合并,节省该free chunk的四个字长的数据(两个size和两个指针)
这个设计就是上面的内存分配策略:字节对齐,比如我索要31字节,那么自动补齐为32字节(详情见源码),那么此时size段低三位永远是0(去看看8(64位)的倍数的数字转成二进制),为了
充分利用每一比特空间
就做了这种设计
free chunk
- free chunk的header在malloced chunk的基础上多了两个指针
- fd:指向下一个free chunk(这里指linked list中的下一个chunk,包含bin,不是指内存中的下一个chunk)
- bk:指向上一个free chunk(同理)
fd和bk指针的存在使得free chunk可以被连接成一个双向链表,方便管理和分配
- fd_nextsize:(不包含bin)
- bk_nextsize:(同理)
top chunk
- 第一次执行malloc的时候就会将heap切成两块,一块为分配出去的chunk,另一块为top chunk,之后要是分配的空间不足(main arena 中的空闲块不足)就会在top chunk上切分出去
- 只有size一个字段(prevsize始终作数据段),其中size展示当前top chunk的大小
- top chunk 的 prev_inuse 比特位始终为 1,否则其前面的 chunk 就会被合并到 top chunk 中
- 初始情况下,我们可以将 unsorted chunk 作为 top chunk
bin
- linked bin:为了让maollloc尽快找到合适大小的chunk,free掉的chunk会被放到对应大小的bin中
- bin空间:相当于堆管理器的逻辑回收站,用于储存用户free掉的空间和管理arena中闲置的chunk的数据结构,本质为数组
fast bin(s)
- 线性数组+单向链表(唯一一个单向链表)结构,chunk size<64kb,不取消in useflag,
- 每个bin是一个链表,这些链表串起来就是fast bins
- bin的本身是保存的地址,这个地址指向相应的大小(由size的段信息决定)的chunk(fast bin free chunk)的prev size的地址(malloc得到的是数据区起始的地址)
- bin管理的chunk大小这一信息是由数组的
顺序
(容易忽视的东西)包含的(0索引处是0x20,1索引是0x30…)tcache(libc2.26以后才有的机制)
- tcache_entry相当于chunk_size,next指针就相当于fd。不过,next指针指向的是chunk_data开头而非chunk_header,一个tcache链表里面最多放七个堆块
small bin
large bin
unsorted bin
- 双向链表,管理刚刚释放还为分类的 chunk,也就是空闲 chunk 回归其所属 bin 之前的缓冲区
- 位于topchunk上面,用unlink管理其中的free_chunk
攻击手法
UseAfterFree
当一个指针被free后没有被置空,而且它为于fastbins的头部时,当这个指针再次被malloc的时候就会造成uaf,此时就可以构造chunk来让glibc把该chunk分配成我们想要的结构来实现任意地址读写
DoubleFree
当一个freechunk再次被free时它的bk就会指向当前bins的头部,而它的fd就会和整个bins形成一个循环的结构,此时将bins中的chunk free出去的话就可以改写bins中的chunk,如果我们向这个chunk写入构造好的payload就可以实现任意地址读写
On
相关补充知识
虚拟内存与物理内存
每个进程都有独立的虚拟内存机制供自己使用,虚拟内存机制是操作系统提供的内存管理机制,该机制操作一段逻辑上的虚拟内存空间,在程序访问内存时操作系统将相应逻辑内存映射到物理内存来进行访问,因此访问硬件的仍然是操作系统
进程与线程
代码执行视角: 单线程代码执行,cpu从text段以此读取并处理一条指令并执行每一个流程跳转,相当于线性执行所有的代码片段 多线程执行:cpu同时读取并执行text段的多块独立代码,执行速度加倍。执行独立线程的物理存在叫做:cpu核心,单核处理器以极快速度在多个线程间切换实现多线程执行
物理chunk链表:由size和prev size信息构成的可全部寻址的这些chunk,这个链表结构可以实现内存合并
逻辑链表:以bin结构体(存有size和具有对应size的chunk的地址)和一些bin chunk为要素,以指针单向寻址的结构(fast bins),用于prev size覆写分配heap操作
fast chunk:size段中的in use位总是1(在使用),所以不参与合并等复杂操作(即使被释放了),所以可以快速的被分配