2024-05-09 18:06:33 +00:00
|
|
|
|
# 堆
|
|
|
|
|
|
|
|
|
|
## 堆基础
|
|
|
|
|
|
2024-05-10 17:40:08 +00:00
|
|
|
|
堆基本上是程序在请求数据时可以存储数据的地方,通过调用`malloc`、`calloc`等函数。此外,当不再需要这些内存时,可以通过调用`free`函数来释放。
|
2024-05-09 18:06:33 +00:00
|
|
|
|
|
|
|
|
|
如图所示,堆就在二进制文件加载到内存后面(查看`[heap]`部分):
|
|
|
|
|
|
|
|
|
|
<figure><img src="../../.gitbook/assets/image (1241).png" alt=""><figcaption></figcaption></figure>
|
|
|
|
|
|
|
|
|
|
### 基本块分配
|
|
|
|
|
|
2024-05-10 17:40:08 +00:00
|
|
|
|
当请求将一些数据存储在堆中时,堆的一部分空间将被分配给它。这个空间将属于一个bin,只有请求的数据 + bin头部的空间 + 最小的bin大小偏移量将被保留给这个块。目标是尽可能保留最少的内存,而不会使查找每个块的位置变得复杂。为此,使用元数据块信息来知道每个已使用/空闲块的位置。
|
2024-05-09 18:06:33 +00:00
|
|
|
|
|
|
|
|
|
根据使用的bin,有不同的方式来保留空间,但一般的方法是:
|
|
|
|
|
|
|
|
|
|
* 程序首先请求一定量的内存。
|
2024-05-10 17:40:08 +00:00
|
|
|
|
* 如果在块列表中有足够大的块可以满足请求,将使用它
|
|
|
|
|
* 这甚至可能意味着可用块的一部分将用于此请求,其余部分将添加到块列表中
|
|
|
|
|
* 如果列表中没有可用块,但已分配的堆内存中仍有空间,堆管理器将创建一个新块
|
|
|
|
|
* 如果没有足够的堆空间来分配新块,堆管理器会要求内核扩展分配给堆的内存,然后使用这个内存来生成新块
|
2024-05-09 18:06:33 +00:00
|
|
|
|
* 如果一切失败,`malloc`返回null。
|
|
|
|
|
|
2024-05-10 17:40:08 +00:00
|
|
|
|
请注意,如果请求的内存**超过了阈值**,将使用**`mmap`**来映射请求的内存。
|
2024-05-09 18:06:33 +00:00
|
|
|
|
|
|
|
|
|
### 竞技场
|
|
|
|
|
|
2024-05-10 17:40:08 +00:00
|
|
|
|
在**多线程**应用程序中,堆管理器必须防止可能导致崩溃的**竞争条件**。最初,这是通过使用**全局互斥锁**来实现的,以确保一次只有一个线程可以访问堆,但这会导致由于互斥锁引起的瓶颈而出现**性能问题**。
|
2024-05-09 18:06:33 +00:00
|
|
|
|
|
|
|
|
|
为了解决这个问题,ptmalloc2堆分配器引入了“竞技场”,其中**每个竞技场**充当一个**独立的堆**,具有其**自己的**数据**结构**和**互斥锁**,允许多个线程执行堆操作而不会相互干扰,只要它们使用不同的竞技场。
|
|
|
|
|
|
2024-05-10 17:40:08 +00:00
|
|
|
|
默认的“主”竞技场处理单线程应用程序的堆操作。当**添加新线程**时,堆管理器为它们分配**次要竞技场**以减少争用。它首先尝试将每个新线程附加到未使用的竞技场,如有必要,创建新的竞技场,对于32位系统的限制是CPU核心的2倍,对于64位系统是8倍。一旦达到限制,**线程必须共享竞技场**,可能导致争用。
|
2024-05-09 18:06:33 +00:00
|
|
|
|
|
2024-05-10 17:40:08 +00:00
|
|
|
|
与主竞技场不同,主要使用`brk`系统调用扩展,次要竞技场使用`mmap`和`mprotect`创建“子堆”来模拟堆行为,允许灵活管理多线程操作的内存。
|
2024-05-09 18:06:33 +00:00
|
|
|
|
|
|
|
|
|
### 子堆
|
|
|
|
|
|
2024-05-10 17:40:08 +00:00
|
|
|
|
子堆用作多线程应用程序中次要竞技场的内存储备,允许它们独立于主堆增长和管理自己的堆区域。以下是子堆与初始堆的区别以及它们的操作方式:
|
2024-05-09 18:06:33 +00:00
|
|
|
|
|
|
|
|
|
1. **初始堆与子堆**:
|
2024-05-10 17:40:08 +00:00
|
|
|
|
* 初始堆直接位于程序二进制文件之后的内存中,并使用`sbrk`系统调用扩展。
|
2024-05-09 18:06:33 +00:00
|
|
|
|
* 次要竞技场使用的子堆是通过`mmap`创建的,这是一个映射指定内存区域的系统调用。
|
|
|
|
|
2. **使用`mmap`进行内存保留**:
|
2024-05-10 17:40:08 +00:00
|
|
|
|
* 当堆管理器创建子堆时,它通过`mmap`保留一个大块内存。此保留不会立即分配内存;它只是指定其他系统进程或分配不应使用的区域。
|
2024-05-09 18:06:33 +00:00
|
|
|
|
* 默认情况下,32位进程的子堆保留大小为1 MB,64位进程为64 MB。
|
|
|
|
|
3. **使用`mprotect`逐步扩展**:
|
2024-05-10 17:40:08 +00:00
|
|
|
|
* 初始将保留的内存区域标记为`PROT_NONE`,表示内核不需要为此空间分配物理内存。
|
|
|
|
|
* 为了“增长”子堆,堆管理器使用`mprotect`将页面权限从`PROT_NONE`更改为`PROT_READ | PROT_WRITE`,促使内核为先前保留的地址分配物理内存。这一步骤允许子堆根据需要扩展。
|
2024-05-09 18:06:33 +00:00
|
|
|
|
* 一旦整个子堆耗尽,堆管理器将创建一个新的子堆以继续分配。
|
|
|
|
|
|
2024-05-10 17:40:08 +00:00
|
|
|
|
### malloc\_state
|
2024-05-09 18:06:33 +00:00
|
|
|
|
|
2024-05-10 17:40:08 +00:00
|
|
|
|
**每个堆**(主竞技场或其他线程竞技场)都有一个**`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`块来自于当没有可用的确切大小块时,因此会分割一个更大的块,将指针剩余部分放在这里。
|
|
|
|
|
```c
|
|
|
|
|
// 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
|
|
|
|
|
|
|
|
|
|
这个结构代表内存的一个特定块。对于已分配和未分配的块,各个字段具有不同的含义。
|
|
|
|
|
```c
|
|
|
|
|
// 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;
|
|
|
|
|
```
|
|
|
|
|
正如先前所述,这些块还包含一些元数据,这在这张图片中很好地表示出来:
|
2024-05-09 18:06:33 +00:00
|
|
|
|
|
|
|
|
|
<figure><img src="../../.gitbook/assets/image (1242).png" alt=""><figcaption><p><a href="https://azeria-labs.com/wp-content/uploads/2019/03/chunk-allocated-CS.png">https://azeria-labs.com/wp-content/uploads/2019/03/chunk-allocated-CS.png</a></p></figcaption></figure>
|
|
|
|
|
|
2024-05-10 17:40:08 +00:00
|
|
|
|
元数据通常为0x08B,使用最后3位来指示当前块大小:
|
2024-05-09 18:06:33 +00:00
|
|
|
|
|
2024-05-10 17:40:08 +00:00
|
|
|
|
* `A`:如果为1,则来自子堆,如果为0,则在主堆中
|
|
|
|
|
* `M`:如果为1,则此块是使用mmap分配的空间的一部分,而不是堆的一部分
|
2024-05-09 18:06:33 +00:00
|
|
|
|
* `P`:如果为1,则前一个块正在使用中
|
|
|
|
|
|
2024-05-10 17:40:08 +00:00
|
|
|
|
然后是用户数据的空间,最后是0x08B,用于指示前一个块大小当块可用时(或在分配时存储用户数据)。
|
2024-05-09 18:06:33 +00:00
|
|
|
|
|
|
|
|
|
此外,当可用时,用户数据还用于包含一些数据:
|
|
|
|
|
|
|
|
|
|
* 指向下一个块的指针
|
|
|
|
|
* 指向上一个块的指针
|
|
|
|
|
* 列表中下一个块的大小
|
|
|
|
|
* 列表中上一个块的大小
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2024-05-10 17:40:08 +00:00
|
|
|
|
<figure><img src="../../.gitbook/assets/image (1243).png" alt=""><figcaption><p><a href="https://azeria-labs.com/wp-content/uploads/2019/03/chunk-allocated-CS.png">https://azeria-labs.com/wp-content/uploads/2019/03/chunk-allocated-CS.png</a></p></figcaption></figure>
|
2024-05-09 18:06:33 +00:00
|
|
|
|
|
2024-05-10 17:40:08 +00:00
|
|
|
|
{% hint style="info" %}
|
|
|
|
|
请注意,以这种方式链接列表可以避免需要注册每个单独块的数组。
|
2024-05-09 18:06:33 +00:00
|
|
|
|
{% endhint %}
|
|
|
|
|
|
2024-05-10 17:40:08 +00:00
|
|
|
|
### 快速堆示例
|
2024-05-09 18:06:33 +00:00
|
|
|
|
|
2024-05-10 17:40:08 +00:00
|
|
|
|
来自[https://guyinatuxedo.github.io/25-heap/index.html](https://guyinatuxedo.github.io/25-heap/index.html)的快速堆示例,但在arm64上:
|
2024-05-09 18:06:33 +00:00
|
|
|
|
```c
|
|
|
|
|
#include <stdio.h>
|
|
|
|
|
#include <stdlib.h>
|
|
|
|
|
#include <string.h>
|
|
|
|
|
|
|
|
|
|
void main(void)
|
|
|
|
|
{
|
|
|
|
|
char *ptr;
|
|
|
|
|
ptr = malloc(0x10);
|
|
|
|
|
strcpy(ptr, "panda");
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
在主函数的末尾设置一个断点,让我们找出信息存储在哪里:
|
|
|
|
|
|
|
|
|
|
<figure><img src="../../.gitbook/assets/image (1239).png" alt=""><figcaption></figcaption></figure>
|
|
|
|
|
|
|
|
|
|
可以看到字符串"panda"被存储在`0xaaaaaaac12a0`(这是由`x0`内的malloc返回的地址)。检查它之前的0x10个字节,可以看到`0x0`表示**前一个块未被使用**(长度为0),而这个块的长度为`0x21`。
|
|
|
|
|
|
|
|
|
|
额外保留的空间(0x21-0x10=0x11)来自于**添加的头部**(0x10),0x1并不意味着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
|
|
|
|
|
```
|
2024-05-10 17:40:08 +00:00
|
|
|
|
## Bins & 内存分配/释放
|
|
|
|
|
|
|
|
|
|
检查bins是什么,它们是如何组织的,以及内存是如何在以下情况下分配和释放的:
|
|
|
|
|
|
|
|
|
|
{% content-ref url="bins-and-memory-allocations.md" %}
|
|
|
|
|
[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](heap-functions-security-checks.md)
|
|
|
|
|
{% endcontent-ref %}
|
2024-05-09 18:06:33 +00:00
|
|
|
|
|
|
|
|
|
## 参考资料
|
|
|
|
|
|
|
|
|
|
* [https://azeria-labs.com/heap-exploitation-part-1-understanding-the-glibc-heap-implementation/](https://azeria-labs.com/heap-exploitation-part-1-understanding-the-glibc-heap-implementation/)
|
|
|
|
|
* [https://azeria-labs.com/heap-exploitation-part-2-glibc-heap-free-bins/](https://azeria-labs.com/heap-exploitation-part-2-glibc-heap-free-bins/)
|