硬核二进制安全学习:Heap overflow堆溢出(壹)

精选文章:
硬核二进制安全学习FunctionPrologue and Function Epilogue基础函数调用机制
硬核二进制安全学习:Buffer Overflow(栈的缓冲区溢出&&Pwn技巧Return to Text)

知者行之始,行者知之成。
-——王阳明

堆溢出介绍:

堆溢出是指程序向某个堆块中写入的字节数超过了堆块本身可使用的字节数(之所以是可使用而不是用户申请的字节数,是因为堆管理器会对用户所申请的字节数进行调整,这也导致可利用的字节数都不小于用户申请的字节数),因而导致了数据溢出,并覆盖到物理相邻的高地址的下一个堆块。
程序向堆上写入数据。
写入的数据大小没有被良好地控制。
堆溢出是一种特定的缓冲区溢出(还有栈溢出, bss 段溢出等)。但是其与栈溢出所不同的是,堆上并不存在返回地址等可以让攻击者直接控制执行流程的数据,因此我们一般无法直接通过堆溢出来控制 EIP 。一般来说,我们利用堆溢出的策略是
1.覆盖与其物理相邻的下一个 chunk 的内容。
prev_size
size,主要有三个比特位,以及该堆块真正的大小。
NON_MAIN_ARENA
IS_MAPPED
PREV_INUSE
the True chunk size
chunk content,从而改变程序固有的执行流。
2.利用堆中的机制(如 unlink 等 )来实现任意地址写入( Write-Anything-Anywhere)或控制堆块中的内容等效果,从而来控制程序的执行流。
[CTFWiki]

1.堆的申请

在我们常见的操作中,堆是用malloc函数申请使用的。

Void *ptr=malloc(0x10)

系统会调用一些函数在内存中开辟一大片空间作为堆分配使用空间。
2.malloc函数在这一片堆分配使用空间中分配0x10大小的空间,将指向该空间的地址返回给ptr(余下的空间称为topchunk)
使用堆的程序演示案例:

#include <stdio.h>
#include <stdlib.h>
int main()
{
    
    
        void *ptr=malloc(0x10);
        void *ptr1=malloc(0x90);
        void *ptr2=malloc(0x800);
        return 0;

}

在这里插入图片描述
我们在main处打下断点接着run起来。
在这里插入图片描述
使用vmmap查看内存,发现系统并未给我们分配空间。使用n命令继续调试,一直到第一次调用malloc函数。
在这里插入图片描述
我们看调用完成以后有什么变化,继续使用vmmap地址映射空间。
在这里插入图片描述
此时我们观察heap大小会发现他的大小比较大
在这里插入图片描述

细心的读者会观察到,我们申请的大小是0x10,但是这里的size是0x21,是什么造成的呢?
在这里插入图片描述
malloc会给我们一大块空间,这一部分叫做top chunk

堆的调用流程:

参考csdn博客专家@董哥的黑板报堆的调用流程、堆漏洞挖掘中的malloc_chunk结构体分析
第一步:
当应用程序“第一次”使用malloc函数申请动态内存时,glibc库会向内核申请一块非常大的动态内存,这块动态内存会比malloc申请的大小大很多。
brk/mmap系统调用:glibc库使用的是brk或者mmap系统调用来想内核申请内存空间的。至于两者有什么区别后面介绍。
第二步:
glibc申请到这块大的内存之后,根据malloc需要的大小,然后切割相应的大小给应用程序malloc函数使用。
第三步:
当应用层free之后,会将刚才使用到的动态内存返回给glibc,但是返回的内存不是返回给top chunk,而是由bins链管理(后面会介绍bins链)。
第四步:
当程序再次malloc时,会从刚才申请的很大的动态内存去取,不会再去向内核申请内存。
只有当第一次申请的动态内存使用完时,glibc才会再次通过brk/mmap系统调用向内核去要内存。

2.chunk讲解(堆的数据结构)

上面讲了堆的申请,接下来讲一下堆的数据结构。
malloc_chunk
我们称运行过程中被malloc分配的内存为一个chunk,这块内存在ptmalloc中用malloc_chunk结构体表示,当程序申请的chunk被free时,会被加入相应的空闲管理列表中。
在这里插入图片描述

struct malloc_chunk {
    
    
 
  INTERNAL_SIZE_T      mchunk_prev_size;  /*  Size of previous chunk (if free).  */
 
  INTERNAL_SIZE_T      mchunk_size;       /* 当前chunk的大小 Size in bytes, including overhead. */
 
  struct malloc_chunk* fd;         /* double links -- used only if free. */
  struct malloc_chunk* bk;
 
  struct malloc_chunk* fd_nextsize; /* double links -- used only if free. */
  struct malloc_chunk* bk_nextsize;
}

参考:CSDN老码农zhuliptmalloc源码分析 - 内存组织单元malloc_chunk03

Chunk是一个统一的结构体声明,但是它在被申请与空闲的时候又有两种不同的状态。

使用中的Chunk
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

我们会发现Free Chunk的过程中多出了几个字段,在结构体中都是声明了的。为什么会这样呢?

知根知底:Chunk的结构体

我们需要了解每个字段的意义。

struct malloc_chunk{
    
    
 INTERNAL_SIZE_T prev_size;
 INTERNAL_SIZE_T size;
 struct malloc_chunk*fd;
 sturct malloc_chunk*bk;
 struct malloc_chunk* fd_nextsize;
 struct malloc_chunk* bk_nextsize;
}

1:prev_size:如果前一个chunk是空闲的,该域表示前一个chunk大小,如果前一个chunk不空闲,该域无意义。
2:size:当前chunk的大小,并且记录
3:FD:记录下一个,被free的chunk大小(used only if free)
4:BK:记录上一个被free的chunk(used only if free )
5.fd_nextsize和bk_nextsize,largebin使用,记录上/下一个被free chunk的size
mchunk_prev_size、mchunk_size

mchunk_prev_size:只有当该chunk的物理相邻的前一地址chunk是空闲的话,该字段在本chunk中才有用,用来记录前一个chunk 的大小 (包括chunk头)。否则,该字段为0是没有用的;但是当前一个chunk申请的大小大于前一个chunk的大小时,那么该字段可以用来给前一个chunk使用(这就是chunk的空间复用,后面文章介绍)。
mchunk_size:当前chunk的大小。

fd、bk

当前chunk处于分配状态时:从fd字段开始的是用户的数据。
当前chunk处于空闲时:
因为chunk处于空闲时,会被放到bin链中,所以fd和bk用于指向自己所在bin链中前后的空闲chunk
fd:指向前一个(非物理相邻)空闲的 chunk的指针(头指针)。
bk:指向后一个(非物理相邻)空闲的 chunk的指针。
通过fd和bk可以将空闲的chunk块加入到空闲的chunk块链表进行统一管理。
在这里插入图片描述
size of precious chunk是pre_size字段。
在size字段中,仔细的你会发现AMP并没有在结构体中定义
我们只需要知道P标志位作用是什么就好。
PREV_INUSE 记录前一个chunk是否被分配

Ptmolloc使用chunk实现内存管理,对chunk管理基于独特的边界标记法。

重要的是地址对齐。在不同平台下,每个chunk的最小大小,地址对齐方式是不同的ptmolloc依赖平台定义的size_t长度,对于32位平台,size_t大小是4字节,在64位平台,size_t长度可能为4字节,也可能为8字节。在Linux X86_64上size_t为8字节。
在64位平台上,一个使用中的chunk大小计算公式应该是:int_use_size=(用户请求大小+16-8) align to 8B

这里加16是因为需要存储prev_size和size,但是向chunk“借”了8B,所以减8。每分配一个chunk的overhead为8B.即SIZE_SZ的大小。 所以最小的chunk应该是0x20,这样size字段的低三位不会被使用,低三位就被用来当做flag位。

prv_size和size —>chunk头部
P标志位记录了前一个chunk块是否被使用。
向chunk“借”了8B,是指prev_size的复用

Chunk的复用技术

对于字段prev_size
字段prev_size记录什么信息呢?有两种情况:
1)如果前一个邻接的chunk块空闲,那么当前chunk块结构体内的prev_size字段记录的是前一个邻接chunk块的大小。这是由chunk当前指针获得前一个空闲chunk地址的依据。
宏prev_chunk§就是依赖这个假设实现的。

2)如果前一个邻接chunk在使用中,则当前chunk的prev_size的空间被前一个chunk借用中,其中的一个值是前一个chunk内存内容,对于当前chunk没有任何意义。
获得

获得当前一个chunk的地址:当前chunk的地址-prev_size=前一个chunk的地址

3.堆的释放

堆的释放一般都是用free函数实现,但free后chunk去哪里了呢?在bin中。

4.堆释放后的管理

堆释放后,会被添加到相应的bin中进行管理。这里涉及道的结构体是malloc_state

5.分箱式管理

对于空闲的chunk,ptmalloc采用分箱式内存管理方式,根据空闲chunk的大小和处于状态将其放在四个不同的bin中,这四个空闲的chunk的容器包括fast bins,unsorted bin,small bin和large bin

6.bins讲解

首先是什么是bin?
glibc malloc分配若干个bins,为了方便查找,glibc提供了两个数组:fastbinY和binsBins,英文解释是垃圾桶。在这里就是存放没有的chunk。
在这里插入图片描述
双链表进行管理,对照链表节点类比free后的chunk
Bins分为:
1.fastbins
2.smallbins
3.largebins
4. Unsorted bins
在这里插入图片描述

关于Heap堆bin结构的理解

以下内容转载于: ColdSnap の Blog!

概述:
用户申请堆空间是通过malloc函数实现的,而malloc在内核中对应的是ptmalloc。ptmalloc像是内核和用户的中间商,中间商向卖家(操作系统内核)要大量的货物(大块内存空间),然后分配给买家(用户程序)。同样的,当用户释放chunk的时候,也不是直接归还给系统,而是被ptmalloc所管理。当用户在一次请求内存空间的时候,ptmalloc会在空闲的chunk中选择合适大小的分配给用户。这种机制的好处在于可以避免频繁的系统效用,降低内存分配的开销,提高效率。

而ptmalloc正是通过bin结构来管理空闲堆块。它会根据空闲chunk的大小及使用状态将chunk分为4类:fast bin,small bin,large bin和unsorted bin。

Fastbin

对于size较小的chunk,释放之后单独处理,被放入fast bin中。
对于size较小的chunk,释放之后单独处理,被放入fast bin中。
32位系统,fast bin中的chunk大小范围在16字节到64字节;
64位系统,fast bin中的chunk大小范围在32字节到128字节。
 fast bin链表采用单向链表进行连接,并且每个bin采取了LIFO策略,最近释放的chunk会被更早地分配。所以当用户申请的chunk大小在fast bin范围内时,ptmalloc会首先判断fast bin中是否有对应大小的空闲chunk,有的话就会直接分配出去。

fast bin范围内chunk的inuse标志位始终被置为1,即它们不会和其他被释放的chunk合并,也就不会触发Unlink操作。

fastbin链表最末端的块fd域为0,此后每个块的fd域指向前一个块。因此通过fastbin只能泄漏heap的基地址。

Small bin

small bin中chunk大小范围的序号index从2到63,总共62个循环双向链表。

每个chunk的size大小用一个关系式表达是:
  
  chunk_size = 2 * SIZE_SZ(4 or 8)* index

从关系式可以看出每个链表中的chunk大小是一样的,这也是堆中堆表的数据结构。并且可以发现small bin中的chunk_size与fast bin有重复,这仅是大小重复,而不是chunk重复。fast bin中的chunk也有可能被放到small bin中去。
  此外small bin中每个bin对应的链表采用FIFO策略,所以同一个链表中先被释放的chunk会被先分配。
通过smallbin可以获得:

1.libc.so的基地址
2.heap基地址

Largebin

large bin也是遵循FIFO策略的循环双向链表,一共有63个bin,每个bin中的chunk大小不一致,但处于一定区间范围内。32位系统中chunk_size >= 512字节。

unsortedbin

unsorted bin可以看作空闲chunk回归其所属bin之前的缓冲区,该bin只有一个遵循FIFO策略的循环双向链表,且其中的free chunk处于乱序状态。unsorted bin暂时存储free后的chunk,一段时间后会将chunk放入对应的bin中去。
  通过unsorted bin我们可以获取到某个堆块的地址和main_areana的地址。一旦获取到某个堆块的地址就可以通过malloc的size进行计算从而获得堆基地址。一旦获取到main_arena的地址,因为main_arena存在于libc.so中就可以计算偏移得出libc.so的基地址。
  
因此,通过unsorted bin可以获得:
1.libc.so的基地址
2.heap基地址

glibc/malloc/malloc_state源代码
https://code.woboq.org/userspace/glibc/malloc/malloc.c.html#malloc_state

struct malloc_state
{
    
    
  /* Serialize access.  */
  __libc_lock_define (, mutex);
  /* Flags (formerly in max_fast).  */
  int flags;
  /* 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;
  /* Fastbins */
  mfastbinptr fastbinsY[NFASTBINS];
  /* Base of the topmost chunk -- not otherwise kept in a bin */
  mchunkptr top;
  /* 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;
};

mfastbinptr fastbinsY[NFASTBINS];
Fastbins拥有10个[NFASTBINS]元素数组,用于存放每个fast chunk链表头的指针,所以fast bins最多包含10个单向链表。
堆释放的管理是通过链表进行管理的
Fastbin实验
实验程序如下:

#include<stdio.h>
#include<stdlib.h>
int main()
{
    
    
	void *ptr1,*ptr2,*ptr3,*ptr4;
	ptr1=malloc(0x10);
	ptr2=malloc(0x10);
	ptr3=malloc(0x10);
	ptr4=malloc(0x10);
	free(ptr1);
	free(ptr2);
	free(ptr3);
	free(ptr4);
	malloc(0x150);
}

小于1328B大小的chunk都会存储在fastbin中

在这里插入图片描述我们可以清楚的发现申请的堆块大小都是0x21在这里插入图片描述
在这里插入图片描述

运行完第一个free函数后,观察bins大小
在这里插入图片描述

接着继续运行,再观察bins大小,会发现fastbins有变化
在这里插入图片描述
fd指针存储了上一个指针存放的内容
在这里插入图片描述
Fastbin由单链表进行管理存储
在这里插入图片描述
将程序分配的0x10大小改为0x40,call完4个free后继续观察fastbin的变化情况
在这里插入图片描述
fastbins 里面为空,而fastbins链表头变为0x50
在这里插入图片描述

7.堆的再次申请

参考

glibc内存管理ptmalloc源码分析华庭(庄明强)

csdn@giantbranch bilibili@Freedom-zy的堆溢出视频总结

微信公众号:
知柯信息安全Zhicr Known Security
@狩猎者网络安全-知柯信安

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/qq_43332010/article/details/120402102
今日推荐