hacktricks/binary-exploitation/heap/README.md

10 KiB
Raw Permalink Blame History

堆基础

堆基本上是程序在请求数据时可以存储数据的地方,通过调用malloccalloc等函数。此外,当不再需要这些内存时,可以通过调用free函数来释放。

如图所示,堆就在二进制文件加载到内存后面(查看[heap]部分):

基本块分配

当请求将一些数据存储在堆中时堆的一部分空间将被分配给它。这个空间将属于一个bin只有请求的数据 + bin头部的空间 + 最小的bin大小偏移量将被保留给这个块。目标是尽可能保留最少的内存而不会使查找每个块的位置变得复杂。为此使用元数据块信息来知道每个已使用/空闲块的位置。

根据使用的bin有不同的方式来保留空间但一般的方法是

  • 程序首先请求一定量的内存。
  • 如果在块列表中有足够大的块可以满足请求,将使用它
  • 这甚至可能意味着可用块的一部分将用于此请求,其余部分将添加到块列表中
  • 如果列表中没有可用块,但已分配的堆内存中仍有空间,堆管理器将创建一个新块
  • 如果没有足够的堆空间来分配新块,堆管理器会要求内核扩展分配给堆的内存,然后使用这个内存来生成新块
  • 如果一切失败,malloc返回null。

请注意,如果请求的内存超过了阈值,将使用**mmap**来映射请求的内存。

竞技场

多线程应用程序中,堆管理器必须防止可能导致崩溃的竞争条件。最初,这是通过使用全局互斥锁来实现的,以确保一次只有一个线程可以访问堆,但这会导致由于互斥锁引起的瓶颈而出现性能问题

为了解决这个问题ptmalloc2堆分配器引入了“竞技场”其中每个竞技场充当一个独立的堆,具有其自己的数据结构互斥锁,允许多个线程执行堆操作而不会相互干扰,只要它们使用不同的竞技场。

默认的“主”竞技场处理单线程应用程序的堆操作。当添加新线程时,堆管理器为它们分配次要竞技场以减少争用。它首先尝试将每个新线程附加到未使用的竞技场如有必要创建新的竞技场对于32位系统的限制是CPU核心的2倍对于64位系统是8倍。一旦达到限制线程必须共享竞技场,可能导致争用。

与主竞技场不同,主要使用brk系统调用扩展,次要竞技场使用mmapmprotect创建“子堆”来模拟堆行为,允许灵活管理多线程操作的内存。

子堆

子堆用作多线程应用程序中次要竞技场的内存储备,允许它们独立于主堆增长和管理自己的堆区域。以下是子堆与初始堆的区别以及它们的操作方式:

  1. 初始堆与子堆
  • 初始堆直接位于程序二进制文件之后的内存中,并使用sbrk系统调用扩展。
  • 次要竞技场使用的子堆是通过mmap创建的,这是一个映射指定内存区域的系统调用。
  1. 使用mmap进行内存保留
  • 当堆管理器创建子堆时,它通过mmap保留一个大块内存。此保留不会立即分配内存;它只是指定其他系统进程或分配不应使用的区域。
  • 默认情况下32位进程的子堆保留大小为1 MB64位进程为64 MB。
  1. 使用mprotect逐步扩展
  • 初始将保留的内存区域标记为PROT_NONE,表示内核不需要为此空间分配物理内存。
  • 为了“增长”子堆,堆管理器使用mprotect将页面权限从PROT_NONE更改为PROT_READ | PROT_WRITE,促使内核为先前保留的地址分配物理内存。这一步骤允许子堆根据需要扩展。
  • 一旦整个子堆耗尽,堆管理器将创建一个新的子堆以继续分配。

malloc_state

每个堆(主竞技场或其他线程竞技场)都有一个**malloc_state结构。
值得注意的是,主竞技场malloc_state结构是libc中的全局变量因此位于libc内存空间
对于线程堆的
malloc_state结构,它们位于自己的线程“堆”**中。

从这个结构中有一些有趣的事情需要注意见下面的C代码

  • mchunkptr bins[NBINS * 2 - 2]; 包含指向小、大和未排序bins第一个和最后一个块的指针(-2是因为索引0未被使用
  • 因此这些bins的第一个块将具有向后指针指向此结构这些bins的最后一个块将具有向前指针指向此结构。这基本上意味着如果您可以在主竞技场中泄漏这些地址,您将获得指向libc中结构的指针。
  • 结构struct malloc_state *next;struct malloc_state *next_free; 是竞技场的链表
  • top块是最后一个“块”,基本上是所有堆剩余空间。一旦顶部块“空”,堆就完全被使用,需要请求更多空间。
  • last reminder块来自于当没有可用的确切大小块时,因此会分割一个更大的块,将指针剩余部分放在这里。
// From https://heap-exploitation.dhavalkapil.com/diving_into_glibc_heap/malloc_state
struct malloc_state
{
/* Serialize access.  */
__libc_lock_define (, mutex);
/* Flags (formerly in max_fast).  */
int flags;

/* 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;
};

typedef struct malloc_state *mstate;

malloc_chunk

这个结构代表内存的一个特定块。对于已分配和未分配的块,各个字段具有不同的含义。

// From https://heap-exploitation.dhavalkapil.com/diving_into_glibc_heap/malloc_chunk
struct malloc_chunk {
INTERNAL_SIZE_T      mchunk_prev_size;  /* Size of previous chunk, if it is free. */
INTERNAL_SIZE_T      mchunk_size;       /* Size in bytes, including overhead. */
struct malloc_chunk* fd;                /* double links -- used only if this chunk is 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 this chunk is free. */
struct malloc_chunk* bk_nextsize;
};

typedef struct malloc_chunk* mchunkptr;

正如先前所述,这些块还包含一些元数据,这在这张图片中很好地表示出来:

https://azeria-labs.com/wp-content/uploads/2019/03/chunk-allocated-CS.png

元数据通常为0x08B使用最后3位来指示当前块大小

  • A如果为1则来自子堆如果为0则在主堆中
  • M如果为1则此块是使用mmap分配的空间的一部分而不是堆的一部分
  • P如果为1则前一个块正在使用中

然后是用户数据的空间最后是0x08B用于指示前一个块大小当块可用时或在分配时存储用户数据

此外,当可用时,用户数据还用于包含一些数据:

  • 指向下一个块的指针
  • 指向上一个块的指针
  • 列表中下一个块的大小
  • 列表中上一个块的大小

https://azeria-labs.com/wp-content/uploads/2019/03/chunk-allocated-CS.png

{% hint style="info" %} 请注意,以这种方式链接列表可以避免需要注册每个单独块的数组。 {% endhint %}

快速堆示例

来自https://guyinatuxedo.github.io/25-heap/index.html的快速堆示例但在arm64上

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void main(void)
{
char *ptr;
ptr = malloc(0x10);
strcpy(ptr, "panda");
}

在主函数的末尾设置一个断点,让我们找出信息存储在哪里:

可以看到字符串"panda"被存储在0xaaaaaaac12a0(这是由x0内的malloc返回的地址。检查它之前的0x10个字节可以看到0x0表示前一个块未被使用长度为0而这个块的长度为0x21

额外保留的空间0x21-0x10=0x11来自于添加的头部0x100x1并不意味着0x21B被保留而是当前头部长度的最后3位具有一些特殊含义。由于长度始终是16字节对齐的在64位机器上这些位实际上永远不会被长度数字使用。

0x1:     Previous in Use     - Specifies that the chunk before it in memory is in use
0x2:     Is MMAPPED          - Specifies that the chunk was obtained with mmap()
0x4:     Non Main Arena      - Specifies that the chunk was obtained from outside of the main arena

Bins & 内存分配/释放

检查bins是什么它们是如何组织的以及内存是如何在以下情况下分配和释放的

{% content-ref url="bins-and-memory-allocations.md" %} bins-and-memory-allocations.md {% endcontent-ref %}

堆函数安全检查

堆中涉及的函数在执行操作之前会执行某些检查,以确保堆没有被损坏:

{% content-ref url="heap-functions-security-checks.md" %} heap-functions-security-checks.md {% endcontent-ref %}

参考资料