堆、malloc_chunk

• 在程序运行过程中,堆可以提供动态内存的分配,允许程序申请大小未知的内存。
• 堆其实就是在程序虚拟地址空间的一块连续的线性区域,它由低地址向高地址生长。
• 我们一般称管理堆的那部分程序为堆管理器。
• 堆管理器位于用户程序和内核中间,主要负责 : 
• malloc
• free
• double free : 当p已经被释放后再次释放,造成乱七八糟的现象。
• 请求堆
• 响应用户的申请内存请求,向操作系统申请内存,然后返回给用户程序。为了保持内存管理的高效性,内核一般会预先分配很大的一块连续的内存。
• 释放堆
• 管理用户释放的内存。用户释放的内存并不是直接返还给操作系统,而是由堆管理器进行管理。这些释放的内存可以用来响应用户新申请的内存的请求。
堆历史
• Linux中早期的堆分配和回收由Doug lea实现,但它在并行处理多个线程时,会共享进程的堆内存空间。因此为了安全性,一个线程使用堆时,会进行加锁。
• 然而,加锁会导致其他线程无法使用堆,降低了内存分配和回收的高效性。在多线程使用时,没能正确控制,也可能引起内存分配和回收的正确性。
• Wolffram Gloger在Doug Lea的基础上进行改进使其可以支持多线程,这个堆分配器就是ptmalloc。在glibc-2.3.x之后,glibc中集成了ptmalloc2。ptmalloc2主要通过malloc/free函数来分配和释放内存块。
不同堆的实现
• dlmalloc : Genral purpose allocator
• ptmalloc2 : glibc
• jemalloc : Freebsd and Firefox
• tcmalloc : Google
• libumen : Solaris
• 主要以ptmalloc2中堆的实现为主
Linux内存管理思想
• 只有当真正访问一个地址的时候,系统才会在虚拟内存和物理页面的映射关系。
• 所以操作系统已经给程序分配了很大的一块内存,但是这开内存其实只是虚拟内存。只有当用户使用到相应的内存时,系统才会真正分配物理内存给用户使用。
内存分配背后的系统调用
• malloc和free在动态申请或释放内存时,主要是调用(s)brk和mmap,unmmap函数实现的。
• (s)brk函数机制
• 初始时,堆的起始地址start_brk以及堆的当前末尾brk指向同一地址。根据是否开启ALSR,两者的具体位置会有所不同。
• 不开启AMSR时,start_brk以及brk会指向data/bss段的结尾。
• 开启AMSR时,start_brk以及brk也会指向同一位置,只是这个位置是在data/bss段结尾后的随机偏移处。
# include <stdio.h>
# include <unistd.h>
# icclude <sys/types.h>
int main()
{
 void *cuur_bkr,*tmp_brk = NULL;
 printf("%d\n",getid());
 tm_brk = curr_brk = sbrk(0);      //给当前程序一个brk
 printf("%p\n",curr_brk);
 getchar();
brk(curr_brk+4096);                     //设置结尾位置,即分配了4096字节的堆块
 curr_brk=sbrk(0);
 printf("%p\n",curr_brk);
 getchar();
 brk(tmp_brk);
 curr_brk=sbrk(0);
 printf("%p\n",curr_brk);
 getchar();
 return 0;
}
• mmap函数机制
• malloc会使用mmap来创建独立的匿名映射段。
• 匿名映射的目的主要是可以申请以0填充的内存,并且这块内存仅被调用进程所使用,这块内存为系统随机分配。
• munmap用于释放内存。
data和bss段
• bss段通常是指用来存放程序中未初始化的全局变量的一块内存区域。
• data段通常是指用来存放程序中已初始化的全局变量的一块内存区域。
多线程支持
• 在原来的dlmalloc实现中,当两个线程同时要申请内存时,只有一个线程可以进入临界区申请内存,而另外一个线程必须等待直到临界区中不再有线程。
• 这是因为所有的线程共享一个堆。
• 在glibc和ptmalloc实现中,支持了多线程的快速访问,在新的实现中,所有的线程共享多个堆。
Download glibc
• https://ftp.gnu.org/gnu/glibc/ 
堆相关数据结构
• 宏观结构:包括堆的宏观信息,通过这些数据结构索引堆的基本信息
• 宏观结构主要是堆块之间的连接
• 微观结构:主要用于处理堆的分配与回收中的内存块
• malloc & free
堆块分配
• 分配堆块是基于main_arena来寻址的,首先找的是fastbin,其次再找bins 
malloc相关结构体
  1. 函数执行流程
void *malloc (size_t bytes)
void *__libc_malloc (size_t bytes)
       __malloc_hook //在libc中被置null,利用点:修改__malloc_hook
_int_malloc(mstate av, size_t bytes)
  1. main_arena
struct malloc_state
{
 /* Serialize access.  */
 __libc_lock_define (, mutex);     //定义了一个0x4字节的lock/* Flags (formerly in max_fast).  */
 int flags;                        //0x8/* Set if the fastbin chunks contain recently inserted free blocks.  */
 /* Note this is a bool but not all targets support atomics on booleans.  */
 int have_fastchunks;              //0xc/* Fastbins */
 mfastbinptr fastbinsY[NFASTBINS]; //fastbin链的管理头,10个, 0x10/* Base of the topmost chunk -- not otherwise kept in a bin */
 mchunkptr top;        //0x60/* The remainder from the most recent split of a small request */
 mchunkptr last_remainder;/* Normal bins packed as described above */
 mchunkptr bins[NBINS * 2 - 2];/* Bitmap of bins */
 unsigned int binmap[BINMAPSIZE];/* Linked list */
 struct malloc_state *next;/* Linked list for free arenas.  Access to this field is serialized
    by free_list_lock in arena.c.  */
 struct malloc_state *next_free;/* Number of threads attached to this arena.  0 if the arena is on
    the free list.  Access to this field is serialized by
    free_list_lock in arena.c.  */
 INTERNAL_SIZE_T attached_threads;/* Memory allocated from the system in this arena.  */
 INTERNAL_SIZE_T system_mem;
 INTERNAL_SIZE_T max_system_mem;
}
  1. malloc_chunk
    • 在程序的执行过程中,我们称malloc申请的内存为chunk。这块内存在ptmalloc内部用malloc_chunk结构体来表示。
    • 当程序申请的chunk被free后,会被加入到相应的空闲管理列表中。
    • 无论一个chunk的大小如何,处于分配状态还是释放状态,它们都使用一个统一的结构。但根据是否被释放,结构会有所更改。
struct malloc_chunk {
​
 INTERNAL_SIZE_T      mchunk_prev_size;  /* Size of previous chunk (if free).前面一个空闲块的大小*/ 
 INTERNAL_SIZE_T      mchunk_size;       /* Size in bytes, including overhead. */ //真正的内存从这里开始分配
 struct malloc_chunk* fd;        /* double links -- used only if free. */ // 指向前一项chunk
 struct malloc_chunk* bk;         //指向后一个chunk,当前的chunk需要被free
 
 /* Only used for large blocks: pointer to next larger size.> 0x400  */
 struct malloc_chunk* fd_nextsize; /* double links -- used only if free. */
 struct malloc_chunk* bk_nextsize; //指向后一个large chunk
};
• prev_size
• 如果该chunk的物理相邻的前一地址chunk(两个指针的地址差值为前一个chunk大小)是空闲的话,那该字段记录的是前一个chunk的大小
• 否则用来存储物理相邻的前一个chunk的数据,这里前一个chunk指的是较低地址的chunk。
• size
• 64位chunk的size必须是16字节对齐 2SIZE_T 32位chunk的size必须是8字节对齐 2SIZE_T 
• 64位低四位比特位对chunk的大小没有影响
• 64位低三位比特位对chunk的大小没有影响
• 低三个比特位由高到低分别是NON_MAIN_ARENA,IS_MAPPED,PREV_INUSE。
• NON_MAIN_ARENA,记录当前chunk是否是main_arena管理的堆块,1表示不属于,0表示属于
• IS_MAPPED,记录当前的chunk是否是由mmap分配的。
• PREV_INUSE,记录前一个chunk是否被分配。一般来说,堆中第一个被分配的内存块的size字段的P位都会被设置为1,以便于防止访问前面的非法内存。当一个chunk的size位的P位为0时,我们能通过prev_size获取上一个chunk的大小及地址,方便进行空闲堆块的合并。
• ToDo:易受攻击
• fd,bk
• chunk处于分配状态时,从fd字段开始是用户的数据。当前chunk空闲时,会被添加到对应的空闲管理链表中。
• 其字段的含义为指向下一个(非物理相邻)空闲的chunk
• bk指向上一个(非物理相邻)空闲的chunk
• 通过fd和bk可以将空闲的chunk块加入到空闲的chunk链表进行统一管理。
• fd_nextsize,bk_nextsize,也是只有chunk空闲时才使用,不过其用于较大的chunk(large chunk)
• fd_nextsize指向前一个与当前chunk大小不同的第一个空闲块,不包含bin的头指针
• bk_nextsize指向后一个与当前chunk大小不同的第一个空闲块,不包含bin的头指针
• 一般空闲的largechunk在fd的遍历顺序中,按照从大到小的顺序排列,可以避免在寻找合适的chunk时挨个遍历。
  1. _init_malloc
static void * _int_malloc (mstate av, size_t bytes)
{
         if (in_smallbin_range (nb) &&
             bck == unsorted_chunks (av) &&
             victim == av->last_remainder &&
             (unsigned long) (size) > (unsigned long) (nb + MINSIZE))
           {
             /* split and reattach remainder */
             remainder_size = size - nb;
             remainder = chunk_at_offset (victim, nb);
             unsorted_chunks (av)->bk = unsorted_chunks (av)->fd = remainder;
             av->last_remainder = remainder;
             remainder->bk = remainder->fd = unsorted_chunks (av);
             if (!in_smallbin_range (remainder_size))
               {
                 remainder->fd_nextsize = NULL;
                 remainder->bk_nextsize = NULL;
               }set_head (victim, nb | PREV_INUSE |
                       (av != &main_arena ? NON_MAIN_ARENA : 0));
             set_head (remainder, remainder_size | PREV_INUSE);
             set_foot (remainder, remainder_size);check_malloced_chunk (av, victim, nb);
             void *p = chunk2mem (victim);
             alloc_perturb (p, bytes);
             return p;
           }/* remove from unsorted list */
         unsorted_chunks (av)->bk = bck;
         bck->fd = unsorted_chunks (av);   //修改av即main_arena可实现unsorted_attack
         /* Take now instead of binning if exact fit */if (size == nb)
           {
             set_inuse_bit_at_offset (victim, size);
             if (av != &main_arena)
......
   
/* place chunk in bin */if (in_smallbin_range (size))   //TODO:largebin attack POC Instance as Following
           {
             victim_index = smallbin_index (size);
             bck = bin_at (av, victim_index);
             fwd = bck->fd;
           }
         else
           {
             victim_index = largebin_index (size);
             bck = bin_at (av, victim_index);
             fwd = bck->fd;/* maintain large bins in sorted order */
             if (fwd != bck)
               {
                 /* Or with inuse bit to speed comparisons */
                 size |= PREV_INUSE;
                 /* if smaller than smallest, bypass loop below */
                 assert (chunk_main_arena (bck->bk));
                 if ((unsigned long) (size)
     < (unsigned long) chunksize_nomask (bck->bk))
                   {
                     fwd = bck;
                     bck = bck->bk;
​
                     victim->fd_nextsize = fwd->fd;
                     victim->bk_nextsize = fwd->fd->bk_nextsize;
                     fwd->fd->bk_nextsize = victim->bk_nextsize->fd_nextsize = victim;
                   }
                 else
                   {
                     assert (chunk_main_arena (fwd));
                     while ((unsigned long) size < chunksize_nomask (fwd))
                       {
                         fwd = fwd->fd_nextsize;
 assert (chunk_main_arena (fwd));
                       }if ((unsigned long) size
 == (unsigned long) chunksize_nomask (fwd))
                       /* Always insert in the second position.  */
                       fwd = fwd->fd;
                     else
                       {
                         victim->fd_nextsize = fwd;
                         victim->bk_nextsize = fwd->bk_nextsize;
                         fwd->bk_nextsize = victim;
                         victim->bk_nextsize->fd_nextsize = victim;
                       }
                     bck = fwd->bk;
                   }
               }
             else
               victim->fd_nextsize = victim->bk_nextsize = victim;
           }mark_bin (av, victim_index);
         victim->bk = bck;
         victim->fd = fwd;
         fwd->bk = victim;
         bck->fd = victim;
  1. largebin attack poc
bck=fwd->bk
bck=target-0x10
*(target)=victim
fwd->bk_nextsize=target-0x20
victim->bk_nextsize=target
*(target)=victim
victim->bk=target-0x10
*(target)=unsorted_chunk(av)

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值