1 概述
在 glibc-2.3.x. 之后,glibc 中集成了ptmalloc2。
可以下载glibc源码查看ptmalloc
查看glibc版本
millionsky@ubuntu-16:~/tmp$ ldd --version ldd (Ubuntu GLIBC 2.23-0ubuntu9) 2.23 |
这里主要参考:
https://ctf-wiki.github.io/ctf-wiki/pwn/heap
本文参考的glibc源码是glibc-2.25.tar.xz
理解ptmalloc堆最后的办法是查看相关资料后看源码。word文档拷贝的时候颜色丢失了,格式也有丢失。先这样。
2 API
2.1 其它
2.1.1 catomic_compare_and_exchange_val_acq
如果*MEM等于OLDVAL,则将*MEM存储为NEWVAL,返回OLDVAL;
/* Atomically store NEWVAL in *MEM if *MEM is equal to OLDVAL.
Return the old *MEM value. */
#ifndef catomic_compare_and_exchange_val_acq
# ifdef __arch_c_compare_and_exchange_val_32_acq
# define catomic_compare_and_exchange_val_acq(mem, newval, oldval) \
__atomic_val_bysize (__arch_c_compare_and_exchange_val,acq, \
mem, newval, oldval)
# else
# define catomic_compare_and_exchange_val_acq(mem, newval, oldval) \
atomic_compare_and_exchange_val_acq (mem, newval, oldval)
# endif
#endif
2.1.2 catomic_compare_and_exchange_val_rel
如果*MEM等于OLDVAL,则将*MEM存储为NEWVAL,返回OLDVAL;
#ifndef catomic_compare_and_exchange_val_rel
# ifndef atomic_compare_and_exchange_val_rel
# define catomic_compare_and_exchange_val_rel(mem, newval, oldval) \
catomic_compare_and_exchange_val_acq (mem, newval, oldval)
# else
# define catomic_compare_and_exchange_val_rel(mem, newval, oldval) \
atomic_compare_and_exchange_val_rel (mem, newval, oldval)
# endif
#endif
2.1.3 Perturb_byte
l perturb_byte:一个字节,用于初始化分配的内存,出于调试的目的。
l __libc_mallopt(int param_number, int value):设置perturb_byte
l alloc_perturb (char *p, size_t n):内存初始化为perturb_byte^0xff
l free_perturb (char *p, size_t n):内存初始化为perturb_byte
l do_set_perturb_byte (int32_t value):设置perturb_byte
static int perturb_byte;
static void
alloc_perturb (char *p, size_t n)
{
if (__glibc_unlikely (perturb_byte))
memset (p, perturb_byte ^ 0xff, n);
}
static void
free_perturb (char *p, size_t n)
{
if (__glibc_unlikely (perturb_byte))
memset (p, perturb_byte, n);
}
static inline int
__always_inline
do_set_perturb_byte (int32_t value)
{
LIBC_PROBE (memory_mallopt_perturb, 2, value, perturb_byte);
perturb_byte = value;
return 1;
}
int
__libc_mallopt (int param_number, int value){
//......
switch (param_number)
{
case M_PERTURB:
do_set_perturb_byte (value);
break;
}
//......
}
2.2 malloc_init_state
l 初始化arena header(malloc_state)结构:
初始化bins数组,构造bin双链表;
设置NONCONTIGUOUS_BIT标记;
设置fastbin用户数据的最大值:global_max_fast;
设置FASTCHUNKS_BIT标记;
初始化top chunk;
static void
malloc_init_state (mstate av)
{
int i;
mbinptr bin;
/* Establish circular links for normal bins */
for (i = 1; i < NBINS; ++i)
{
bin = bin_at (av, i);
bin->fd = bin->bk = bin;
}
#if MORECORE_CONTIGUOUS
if (av != &main_arena)
#endif
set_noncontiguous (av);
if (av == &main_arena)
set_max_fast (DEFAULT_MXFAST);
av->flags |= FASTCHUNKS_BIT;
av->top = initial_top (av);
}
2.3 Unlink
unlink 用来将一个双向 bin 链表中的一个 chunk 取出来
参数:
AV:arena header(malloc_state)
P :将要unlink的chunk
BK:P后面的chunk <--
FD:P前面的chunk -->
3个chunk的顺序
-------->地址增加
BK P FD
具体过程如下:
将chunk从FD/BK链表中摘除;
如果是large chunk,则将chunk从fd_nextsize/bk_nextsize链表中摘除
/* Take a chunk off a bin list */ #define unlink(AV, P, BK, FD) { \ FD = P->fd; \ BK = P->bk; \ // 检查3个chunk的BK/FD链接是否一致 if (__builtin_expect (FD->bk != P || BK->fd != P, 0)) \ malloc_printerr (check_action, "corrupted double-linked list", P, AV); \ else { \ // unlink P FD->bk = BK; \ BK->fd = FD; \ // 如果P的大小不属于small bin if (!in_smallbin_range (chunksize_nomask (P)) \ // P的fd_nextsize字段非空,即位于large bin中 // 这里期望fd_nextsize字段为NULL,即大概率为unsorted bin,小概率为large bin && __builtin_expect (P->fd_nextsize != NULL, 0)) { \ // 检查3个chunk的fd_nextsize/bk_nextsize链接是否一致 if (__builtin_expect (P->fd_nextsize->bk_nextsize != P, 0) \ || __builtin_expect (P->bk_nextsize->fd_nextsize != P, 0)) \ malloc_printerr (check_action, \ "corrupted double-linked list (not small)", \ P, AV); \ // 此时P在fd_nextsize链表中 // 如果FD不在fd_nextsize链表中,则将FD加入链表中 if (FD->fd_nextsize == NULL) { \ // 如果P链接到自身,则令FD也链接到自身 if (P->fd_nextsize == P) \ FD->fd_nextsize = FD->bk_nextsize = FD; \ else { \ // 否则我们需要将 FD 插入到 nextsize 形成的双链表中 // 更新FD的fd_nextsize/bk_nextsize // 更新P->fd_nextsize/P->bk_nextsize FD->fd_nextsize = P->fd_nextsize; \ FD->bk_nextsize = P->bk_nextsize; \ P->fd_nextsize->bk_nextsize = FD; \ P->bk_nextsize->fd_nextsize = FD; \ } \ } else { \ // P和FD都在fd_nextsize链表中,将P从fd_nextsize链表中摘除 P->fd_nextsize->bk_nextsize = P->bk_nextsize; \ P->bk_nextsize->fd_nextsize = P->fd_nextsize; \ } \ } \ } \ } |
1. __builtin_expect
l 函数__builtin_expect()是GCC v2.96版本引入的, 其声明如下:
long __builtin_expect(long exp, long c);
参数
exp 为一个整型表达式, 例如: (ptr != NULL)
c 必须是一个编译期常量, 不能使用变量
返回值
等于 第一个参数 exp
功能
GCC 提供了这个内建函数来帮助程序员处理分支预测.
允许程序员将最有可能执行的分支告诉编译,即exp的值很可能是c;
GCC在编译过程中,会将可能性更大的代码紧跟着前面的代码,从而减少指令跳转带来的性能上的下降, 达到优化程序的目的。(此时CPU流水线预取指令会起作用)
2.4 Malloc
见注释,最终调用的是_int_malloc
strong_alias (__libc_malloc, __malloc) strong_alias (__libc_malloc, malloc)
void * __libc_malloc (size_t bytes) { mstate ar_ptr; void *victim;
// 检查是否有内存分配钩子,如果有,调用钩子并返回 void *(*hook) (size_t, const void *) = atomic_forced_read (__malloc_hook); if (__builtin_expect (hook != NULL, 0)) return (*hook)(bytes, RETURN_ADDRESS (0));
//接着会寻找一个 arena 来试图分配内存 arena_get (ar_ptr, bytes);
//调用 _int_malloc 函数去申请对应的内存 victim = _int_malloc (ar_ptr, bytes);
//尝试再去寻找一个可用的 arena,并分配内存 /* Retry with another arena only if we were able to find a usable arena before. */ if (!victim && ar_ptr != NULL) { LIBC_PROBE (memory_malloc_retry, 1, bytes); ar_ptr = arena_get_retry (ar_ptr, bytes); victim = _int_malloc (ar_ptr, bytes); }
//unlock if (ar_ptr != NULL) __libc_lock_unlock (ar_ptr->mutex);
//判断目前的状态是否满足以下条件 //要么没有申请到内存 //要么是 mmap 的内存 //要么申请到的内存必须在其所分配的arena中 assert (!victim || chunk_is_mmapped (mem2chunk (victim)) || ar_ptr == arena_for_chunk (mem2chunk (victim))); return victim; } libc_hidden_def (__libc_malloc) |
2.5 _int_malloc
static void *
_int_malloc (mstate av, size_t bytes)
_int_malloc 是内存分配的核心函数,其核心思路有如下
1. Fast bin服务请求
2. Small bin服务请求
3. 如果是large chunk,则合并fast bin
4. 循环处理
1) Unsorted bin服务请求
(遍历的unsorted chunk要么匹配用户请求被返回,要么放入对应的bin中)
2) Large bin服务请求
3) next largest bin服务请求
4) top chunk服务请求
5) 合并fast bin,下次尝试
6) 从系统请求内存
具体过程如下:
1. 没有可用的arena,则使用sysmalloc从mmap获取chunk;
2. chunk大小属于fast bin,尝试从fast bin(单链表)链表头部获取;
3. chunk大小属于small bin,尝试从small bin(环形双链表)链表尾部获取;
4. chunk大小属于large bin,先合并 fast bin以解决分片问题;
5. 循环处理
l 这里使用外循环,是因为我们可能在处理中合并了一个满足要求的chunk,需要重新尝试。最多重新尝试一次,否则我们将扩展内存来服务请求。
l unsorted bin