From c11a8e96834089aa5ac46886ee806f06398a32c5 Mon Sep 17 00:00:00 2001 From: Translator Date: Tue, 11 Jun 2024 17:32:41 +0000 Subject: [PATCH] Translated ['binary-exploitation/heap/bins-and-memory-allocations.md', ' --- .../heap/bins-and-memory-allocations.md | 760 ++++++++++++++---- binary-exploitation/heap/heap-overflow.md | 28 +- binary-exploitation/heap/unlink-attack.md | 32 +- .../heap/use-after-free/README.md | 6 +- 4 files changed, 629 insertions(+), 197 deletions(-) diff --git a/binary-exploitation/heap/bins-and-memory-allocations.md b/binary-exploitation/heap/bins-and-memory-allocations.md index 4596747df..e867d87e4 100644 --- a/binary-exploitation/heap/bins-and-memory-allocations.md +++ b/binary-exploitation/heap/bins-and-memory-allocations.md @@ -2,195 +2,631 @@
-从零开始学习 AWS 黑客技术,成为专家 htARTE(HackTricks AWS 红队专家) +从零开始学习AWS黑客技术,成为专家 htARTE(HackTricks AWS红队专家) -支持 HackTricks 的其他方式: +支持HackTricks的其他方式: -* 如果您想看到您的**公司在 HackTricks 中做广告**或**下载 HackTricks 的 PDF**,请查看[**订阅计划**](https://github.com/sponsors/carlospolop)! -* 获取[**官方 PEASS & HackTricks 商品**](https://peass.creator-spring.com) -* 探索[**PEASS 家族**](https://opensea.io/collection/the-peass-family),我们的独家[**NFT**](https://opensea.io/collection/the-peass-family)收藏品 -* **加入** 💬 [**Discord 群组**](https://discord.gg/hRep4RUj7f) 或 [**电报群组**](https://t.me/peass) 或在 **Twitter** 🐦 [**@hacktricks\_live**](https://twitter.com/hacktricks\_live)** 上关注**我们。 -* 通过向 [**HackTricks**](https://github.com/carlospolop/hacktricks) 和 [**HackTricks Cloud**](https://github.com/carlospolop/hacktricks-cloud) github 仓库提交 PR 来分享您的黑客技巧。 +- 如果您想看到您的**公司在HackTricks中做广告**或**下载PDF格式的HackTricks**,请查看[**订阅计划**](https://github.com/sponsors/carlospolop)! +- 获取[**官方PEASS & HackTricks周边产品**](https://peass.creator-spring.com) +- 探索[**PEASS家族**](https://opensea.io/collection/the-peass-family),我们的独家[NFT收藏品](https://opensea.io/collection/the-peass-family) +- **加入** 💬 [**Discord群**](https://discord.gg/hRep4RUj7f) 或 [**电报群**](https://t.me/peass) 或在**Twitter**上关注我们 🐦 [**@hacktricks\_live**](https://twitter.com/hacktricks\_live)**。** +- 通过向[**HackTricks**](https://github.com/carlospolop/hacktricks)和[**HackTricks Cloud**](https://github.com/carlospolop/hacktricks-cloud) github仓库提交PR来分享您的黑客技巧。
## 基本信息 -为了提高块存储的效率,每个块不仅仅在一个链接列表中,而是有几种类型。这些是 bins,有 5 种类型的 bins:[62](https://sourceware.org/git/gitweb.cgi?p=glibc.git;a=blob;f=malloc/malloc.c;h=6e766d11bc85b6480fa5c9f2a76559f8acf9deb5;hb=HEAD#l1407) 小型 bins,63 大型 bins,1 未排序 bin,10 快速 bins 和每个线程 64 个 tcache bins。 +为了提高块存储的效率,每个块不仅仅在一个链接列表中,而是有几种类型。这些是bins,有5种类型的bins:[62](https://sourceware.org/git/gitweb.cgi?p=glibc.git;a=blob;f=malloc/malloc.c;h=6e766d11bc85b6480fa5c9f2a76559f8acf9deb5;hb=HEAD#l1407) small bins,63 large bins,1 unsorted bin,10 fast bins和每个线程64个tcache bins。 -每个未排序、小型和大型 bins 的初始地址都在同一个数组内。索引 0 未使用,1 是未排序 bin,bins 2-64 是小型 bins,bins 65-127 是大型 bins。 - -### 小型 Bins - -小型 bins 比大型 bins 更快,但比快速 bins 更慢。 - -62 个 bins 中的每个 bin 将具有**相同大小的块**:16、24、...(在 32 位系统中最大大小为 504 字节,在 64 位系统中为 1024 字节)。这有助于加快查找应分配空间的 bin、插入和删除这些列表中的条目的速度。 - -### 大型 Bins - -与管理固定大小块的小型 bins 不同,每个**大型 bin 处理一系列块大小**。这更加灵活,允许系统**容纳各种大小**而无需为每个大小单独设置 bin。 - -在内存分配器中,大型 bins 从小型 bins 结束的地方开始。大型 bins 的范围逐渐变大,意味着第一个 bin 可能覆盖从 512 到 576 字节的块,而下一个覆盖从 576 到 640 字节的块。这种模式继续下去,最大的 bin 包含所有大于 1MB 的块。 - -与小型 bins 相比,大型 bins 操作速度较慢,因为它们必须**对包含不同块大小的列表进行排序和搜索**,以找到最佳匹配的分配。当将块插入大型 bin 时,必须对其进行排序,当分配内存时,系统必须找到正确的块。这额外的工作使它们**更慢**,但由于大型分配比小型分配更少见,这是一个可以接受的折衷。 - -有: - -* 32 个 64B 范围的 bins -* 16 个 512B 范围的 bins -* 8 个 4096B 范围的 bins -* 4 个 32768B 范围的 bins -* 2 个 262144B 范围的 bins -* 1 个用于剩余大小的 bins - -### 未排序 Bin - -未排序 bin 是堆管理器用于加快内存分配的**快速缓存**。它的工作原理是:当程序释放内存时,堆管理器不会立即将其放入特定的 bin 中。相反,它首先尝试**将其与任何相邻的空闲块合并**,以创建一个更大的空闲内存块。然后,它将这个新块放入一个称为“未排序 bin”的通用 bin 中。 - -当程序**请求内存**时,堆管理器**检查未排序 bin**,看看是否有足够大小的块。如果找到一个,它立即使用。如果找不到合适的块,它将释放的块移动到其相应的 bins 中,无论是小型还是大型,基于它们的大小。 - -因此,未排序 bin 是通过快速重用最近释放的内存来加速内存分配,并减少耗时的搜索和合并的需求。 - -{% hint style="danger" %} -请注意,即使块属于不同的类别,如果一个可用块与另一个可用块发生冲突(即使它们属于不同的类别),它们将被合并。 -{% endhint %} - -### 快速 Bins - -快速 bins 旨在通过将最近释放的块保留在快速访问结构中来**加速小块的内存分配**。这些 bins 使用后进先出(LIFO)方法,这意味着**最近释放的块是**在有新的分配请求时**首先被重用**。这种行为对速度有利,因为与队列(FIFO)相比,从栈顶(LIFO)插入和删除更快。 - -此外,**快速 bins 使用单链表**,而不是双链表,这进一步提高了速度。由于快速 bins 中的块不会与相邻块合并,因此不需要允许从中间删除的复杂结构。单链表对于这些操作更简单、更快。 - -基本上,这里发生的是,头部(指向要检查的第一个块的指针)始终指向该大小的最新释放块。因此: - -* 当分配该大小的新块时,头部指向一个可用的空闲块以供使用。由于此空闲块指向下一个要使用的块,因此将此地址存储在头部中,以便下一个分配知道从哪里获取可用块 -* 当释放块时,空闲块将保存当前可用块的地址,并将这个新释放块的地址放入头部 - -{% hint style="danger" %} -快速 bins 中的块不会自动设置为可用,因此它们会保持一段时间作为快速 bin 块,而不是能够与其他块合并。 -{% endhint %} +每个未排序、small和large bins的初始地址都在同一个数组中。索引0未使用,1是未排序bin,bins 2-64是small bins,bins 65-127是large bins。 ### Tcache(每线程缓存)Bins -尽管线程尝试拥有自己的堆(参见[竞技场](bins-and-memory-allocations.md#arenas)和[子堆](bins-and-memory-allocations.md#subheaps)),但存在一个可能性,即具有大量线程的进程(如 Web 服务器)**最终会与其他线程共享堆**。在这种情况下,主要解决方案是使用**锁**,这可能**显著减慢线程**。 +尽管线程尝试拥有自己的堆(参见[Arenas](bins-and-memory-allocations.md#arenas)和[Subheaps](bins-and-memory-allocations.md#subheaps)),但有可能一个具有大量线程的进程(如Web服务器)**最终会与其他线程共享堆**。在这种情况下,主要解决方案是使用**锁**,这可能会**显著减慢线程**。 -因此,tcache 类似于每个线程的快速 bin,因为它是一个**单链表**,不合并块。每个线程有**64 个单链式 tcache bins**。每个 bin 可以拥有最多[7 个相同大小的块](https://sourceware.org/git/?p=glibc.git;a=blob;f=malloc/malloc.c;h=2527e2504761744df2bdb1abdc02d936ff907ad2;hb=d5c3fafc4307c9b7a4c7d5cb381fcdbfad340bcc#l323),范围从[24 到 1032B(64 位系统)和 12 到 516B(32 位系统)](https://sourceware.org/git/?p=glibc.git;a=blob;f=malloc/malloc.c;h=2527e2504761744df2bdb1abdc02d936ff907ad2;hb=d5c3fafc4307c9b7a4c7d5cb381fcdbfad340bcc#l315)。 +因此,tcache类似于每个线程的fast bin,因为它是一个**不合并块的单链表**。每个线程有**64个单链tcache bins**。每个bin最多可以有[7个相同大小的块](https://sourceware.org/git/?p=glibc.git;a=blob;f=malloc/malloc.c;h=2527e2504761744df2bdb1abdc02d936ff907ad2;hb=d5c3fafc4307c9b7a4c7d5cb381fcdbfad340bcc#l323),范围从[24到1032字节(64位系统)和12到516字节(32位系统)](https://sourceware.org/git/?p=glibc.git;a=blob;f=malloc/malloc.c;h=2527e2504761744df2bdb1abdc02d936ff907ad2;hb=d5c3fafc4307c9b7a4c7d5cb381fcdbfad340bcc#l315)。 -当一个线程**释放**一个块时,**如果它不太大**以至于无法在 tcache 中分配,并且相应的 tcache bin **不满**(已有 7 个块),**它将被分配到那里**。如果无法进入 tcache,它将需要等待堆锁以能够在全局 bins 中执行释放操作。 +当一个线程释放一个块时,如果它不太大以至于无法在tcache中分配,并且相应的tcache bin**未满**(已有7个块),**它将被分配到那里**。如果无法进入tcache,则需要等待堆锁以执行全局释放操作。 + +当**分配一个块**时,如果在**Tcache中有所需大小的空闲块**,它将使用它,否则,需要等待堆锁以在全局bins中找到一个或创建一个新的。\ +还有一个优化,在这种情况下,在持有堆锁的同时,线程**将用请求大小的堆块(7个)填充其Tcache**,因此如果需要更多,它将在Tcache中找到它们。 + +
+ +添加一个tcache块示例 +```c +#include +#include + +int main(void) +{ +char *chunk; +chunk = malloc(24); +printf("Address of the chunk: %p\n", (void *)chunk); +gets(chunk); +free(chunk); +return 0; +} +``` +编译并在主函数的ret操作码处设置断点进行调试。然后使用gef工具,您可以看到正在使用的tcache bin: +```bash +gef➤ heap bins +──────────────────────────────────────────────────────────────────────────────── Tcachebins for thread 1 ──────────────────────────────────────────────────────────────────────────────── +Tcachebins[idx=0, size=0x20, count=1] ← Chunk(addr=0xaaaaaaac12a0, size=0x20, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA) +``` +
+ +#### Tcache 结构和函数 + +在下面的代码中,可以看到**最大 bin**和**每个索引的块数**,创建的**`tcache_entry`**结构用于避免双重释放,以及**`tcache_perthread_struct`**,每个线程使用它来存储 bin 的每个索引的地址。 + +
+ +tcache_entrytcache_perthread_struct +```c +// From https://github.com/bminor/glibc/blob/f942a732d37a96217ef828116ebe64a644db18d7/malloc/malloc.c + +/* We want 64 entries. This is an arbitrary limit, which tunables can reduce. */ +# define TCACHE_MAX_BINS 64 +# define MAX_TCACHE_SIZE tidx2usize (TCACHE_MAX_BINS-1) + +/* Only used to pre-fill the tunables. */ +# define tidx2usize(idx) (((size_t) idx) * MALLOC_ALIGNMENT + MINSIZE - SIZE_SZ) + +/* When "x" is from chunksize(). */ +# define csize2tidx(x) (((x) - MINSIZE + MALLOC_ALIGNMENT - 1) / MALLOC_ALIGNMENT) +/* When "x" is a user-provided size. */ +# define usize2tidx(x) csize2tidx (request2size (x)) + +/* With rounding and alignment, the bins are... +idx 0 bytes 0..24 (64-bit) or 0..12 (32-bit) +idx 1 bytes 25..40 or 13..20 +idx 2 bytes 41..56 or 21..28 +etc. */ + +/* This is another arbitrary limit, which tunables can change. Each +tcache bin will hold at most this number of chunks. */ +# define TCACHE_FILL_COUNT 7 + +/* Maximum chunks in tcache bins for tunables. This value must fit the range +of tcache->counts[] entries, else they may overflow. */ +# define MAX_TCACHE_COUNT UINT16_MAX + +[...] + +typedef struct tcache_entry +{ +struct tcache_entry *next; +/* This field exists to detect double frees. */ +uintptr_t key; +} tcache_entry; + +/* There is one of these for each thread, which contains the +per-thread cache (hence "tcache_perthread_struct"). Keeping +overall size low is mildly important. Note that COUNTS and ENTRIES +are redundant (we could have just counted the linked list each +time), this is for performance reasons. */ +typedef struct tcache_perthread_struct +{ +uint16_t counts[TCACHE_MAX_BINS]; +tcache_entry *entries[TCACHE_MAX_BINS]; +} tcache_perthread_struct; +``` +
+ +函数`__tcache_init`是创建并为`tcache_perthread_struct`对象分配空间的函数 + +
+ +tcache_init 代码 +```c +// From https://github.com/bminor/glibc/blob/f942a732d37a96217ef828116ebe64a644db18d7/malloc/malloc.c#L3241C1-L3274C2 + +static void +tcache_init(void) +{ +mstate ar_ptr; +void *victim = 0; +const size_t bytes = sizeof (tcache_perthread_struct); + +if (tcache_shutting_down) +return; + +arena_get (ar_ptr, bytes); +victim = _int_malloc (ar_ptr, bytes); +if (!victim && ar_ptr != NULL) +{ +ar_ptr = arena_get_retry (ar_ptr, bytes); +victim = _int_malloc (ar_ptr, bytes); +} + + +if (ar_ptr != NULL) +__libc_lock_unlock (ar_ptr->mutex); + +/* In a low memory situation, we may not be able to allocate memory +- in which case, we just keep trying later. However, we +typically do this very early, so either there is sufficient +memory, or there isn't enough memory to do non-trivial +allocations anyway. */ +if (victim) +{ +tcache = (tcache_perthread_struct *) victim; +memset (tcache, 0, sizeof (tcache_perthread_struct)); +} + +} +``` +
+ +### 快速分配区 + +快速分配区旨在通过将最近释放的块保留在快速访问结构中,**加快小块内存分配的速度**。这些区域采用后进先出(LIFO)的方法,这意味着**最近释放的块是首先**在有新的分配请求时被重用的。这种行为对于速度是有利的,因为与队列(FIFO)相比,从栈的顶部(LIFO)插入和移除更快。 + +此外,**快速分配区使用单向链表**,而不是双向链表,这进一步提高了速度。由于快速分配区中的块不会与相邻块合并,所以不需要一个复杂的结构来允许从中间删除。单向链表对于这些操作来说更简单更快。 + +基本上,这里发生的情况是头部(指向要检查的第一个块的指针)始终指向该大小的最新释放的块。所以: + +- 当分配了一个新的该大小的块时,头部指向一个可用的空闲块。由于这个空闲块指向下一个要使用的块,所以这个地址被存储在头部中,以便下一个分配知道从哪里获取一个可用的块。 +- 当一个块被释放时,这个空闲块将保存当前可用块的地址,而这个新释放的块的地址将被放入头部。 + +链表的最大大小为`0x80`,它们被组织成大小为`0x20-0x2f`的块将在索引`0`中,大小为`0x30-0x3f`的块将在索引`1`中... + +{% hint style="danger" %} +快速分配区中的块不会被设置为可用,因此它们会在一段时间内保持为快速分配区块,而不是能够与周围的其他空闲块合并。 +{% endhint %} +```c +// From https://github.com/bminor/glibc/blob/a07e000e82cb71238259e674529c37c12dc7d423/malloc/malloc.c#L1711 + +/* +Fastbins + +An array of lists holding recently freed small chunks. Fastbins +are not doubly linked. It is faster to single-link them, and +since chunks are never removed from the middles of these lists, +double linking is not necessary. Also, unlike regular bins, they +are not even processed in FIFO order (they use faster LIFO) since +ordering doesn't much matter in the transient contexts in which +fastbins are normally used. + +Chunks in fastbins keep their inuse bit set, so they cannot +be consolidated with other free chunks. malloc_consolidate +releases all chunks in fastbins and consolidates them with +other free chunks. +*/ + +typedef struct malloc_chunk *mfastbinptr; +#define fastbin(ar_ptr, idx) ((ar_ptr)->fastbinsY[idx]) + +/* offset 2 to use otherwise unindexable first 2 bins */ +#define fastbin_index(sz) \ +((((unsigned int) (sz)) >> (SIZE_SZ == 8 ? 4 : 3)) - 2) + + +/* The maximum fastbin request size we support */ +#define MAX_FAST_SIZE (80 * SIZE_SZ / 4) + +#define NFASTBINS (fastbin_index (request2size (MAX_FAST_SIZE)) + 1) +``` +
+ +添加一个fastbin块示例 +```c +#include +#include + +int main(void) +{ +char *chunks[8]; +int i; + +// Loop to allocate memory 8 times +for (i = 0; i < 8; i++) { +chunks[i] = malloc(24); +if (chunks[i] == NULL) { // Check if malloc failed +fprintf(stderr, "Memory allocation failed at iteration %d\n", i); +return 1; +} +printf("Address of chunk %d: %p\n", i, (void *)chunks[i]); +} + +// Loop to free the allocated memory +for (i = 0; i < 8; i++) { +free(chunks[i]); +} + +return 0; +} +``` +注意我们如何分配和释放相同大小的8个块,使它们填满tcache,并且第八个存储在快速块中。 + +编译并在主函数的ret操作码处设置断点进行调试。然后使用gef,您可以看到tcache bin填充和快速bin中的一个块: +```bash +gef➤ heap bins +──────────────────────────────────────────────────────────────────────────────── Tcachebins for thread 1 ──────────────────────────────────────────────────────────────────────────────── +Tcachebins[idx=0, size=0x20, count=7] ← Chunk(addr=0xaaaaaaac1770, size=0x20, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA) ← Chunk(addr=0xaaaaaaac1750, size=0x20, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA) ← Chunk(addr=0xaaaaaaac1730, size=0x20, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA) ← Chunk(addr=0xaaaaaaac1710, size=0x20, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA) ← Chunk(addr=0xaaaaaaac16f0, size=0x20, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA) ← Chunk(addr=0xaaaaaaac16d0, size=0x20, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA) ← Chunk(addr=0xaaaaaaac12a0, size=0x20, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA) +───────────────────────────────────────────────────────────────────────── Fastbins for arena at 0xfffff7f90b00 ───────────────────────────────────────────────────────────────────────── +Fastbins[idx=0, size=0x20] ← Chunk(addr=0xaaaaaaac1790, size=0x20, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA) +Fastbins[idx=1, size=0x30] 0x00 +``` +
+ +### 未排序 bin + +未排序 bin 是堆管理器用来加快内存分配速度的缓存。它的工作原理如下:当程序释放一个块时,如果这个块不能被分配到 tcache 或 fast bin,并且不与顶部块发生冲突,堆管理器不会立即将其放入特定的小型或大型 bin 中。相反,它首先尝试**与任何相邻的空闲块合并**,以创建一个更大的空闲内存块。然后,它将这个新块放入一个名为“未排序 bin”的通用 bin 中。 + +当程序**请求内存**时,堆管理器会**检查未排序 bin**,看看是否有足够大小的块。如果找到一个,它会立即使用。如果在未排序 bin 中找不到合适的块,它会将此列表中的所有块移动到它们相应的 bin 中,无论是小型还是大型,都基于它们的大小。 + +请注意,如果一个较大的块被分成两半,剩下的部分大于 MINSIZE,它将被放回未排序 bin 中。 + +因此,未排序 bin 是一种通过快速重用最近释放的内存来加快内存分配速度的方法,从而减少对耗时搜索和合并的需求。 + +{% hint style="danger" %} +请注意,即使块属于不同的类别,如果一个可用块与另一个可用块发生冲突(即使它们最初属于不同的 bin),它们将被合并。 +{% endhint %} + +
+ +添加一个未排序块的示例 +```c +#include +#include + +int main(void) +{ +char *chunks[9]; +int i; + +// Loop to allocate memory 8 times +for (i = 0; i < 9; i++) { +chunks[i] = malloc(0x100); +if (chunks[i] == NULL) { // Check if malloc failed +fprintf(stderr, "Memory allocation failed at iteration %d\n", i); +return 1; +} +printf("Address of chunk %d: %p\n", i, (void *)chunks[i]); +} + +// Loop to free the allocated memory +for (i = 0; i < 8; i++) { +free(chunks[i]); +} + +return 0; +} +``` +注意我们如何分配和释放9个相同大小的块,使它们填满了tcache,第八个块存储在未排序的bin中,因为它对于fastbin来说太大了,第九个块没有被释放,所以第九个和第八个块不会与顶部块合并。 + +编译并在主函数的ret操作码处设置断点进行调试。然后使用gef可以看到tcache bin填充和未排序bin中的一个块: +```bash +gef➤ heap bins +──────────────────────────────────────────────────────────────────────────────── Tcachebins for thread 1 ──────────────────────────────────────────────────────────────────────────────── +Tcachebins[idx=15, size=0x110, count=7] ← Chunk(addr=0xaaaaaaac1d10, size=0x110, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA) ← Chunk(addr=0xaaaaaaac1c00, size=0x110, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA) ← Chunk(addr=0xaaaaaaac1af0, size=0x110, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA) ← Chunk(addr=0xaaaaaaac19e0, size=0x110, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA) ← Chunk(addr=0xaaaaaaac18d0, size=0x110, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA) ← Chunk(addr=0xaaaaaaac17c0, size=0x110, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA) ← Chunk(addr=0xaaaaaaac12a0, size=0x110, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA) +───────────────────────────────────────────────────────────────────────── Fastbins for arena at 0xfffff7f90b00 ───────────────────────────────────────────────────────────────────────── +Fastbins[idx=0, size=0x20] 0x00 +Fastbins[idx=1, size=0x30] 0x00 +Fastbins[idx=2, size=0x40] 0x00 +Fastbins[idx=3, size=0x50] 0x00 +Fastbins[idx=4, size=0x60] 0x00 +Fastbins[idx=5, size=0x70] 0x00 +Fastbins[idx=6, size=0x80] 0x00 +─────────────────────────────────────────────────────────────────────── Unsorted Bin for arena at 0xfffff7f90b00 ─────────────────────────────────────────────────────────────────────── +[+] unsorted_bins[0]: fw=0xaaaaaaac1e10, bk=0xaaaaaaac1e10 +→ Chunk(addr=0xaaaaaaac1e20, size=0x110, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA) +[+] Found 1 chunks in unsorted bin. +``` +
+ +### 小型 Bins + +小型 bins 比大型 bins 快,但比快速 bins 慢。 + +62 个 bins 中的每个 bin 都将具有**相同大小的块**:16、24、...(在 32 位系统中最大大小为 504 字节,在 64 位系统中为 1024 字节)。这有助于加快查找应分配空间的 bin、在这些列表上插入和移除条目的速度。 + +这是根据 bin 的索引计算小型 bin 大小的方法: + +* 最小大小:2\*4\*索引(例如,索引 5 -> 40) +* 最大大小:2\*8\*索引(例如,索引 5 -> 80) +```c +// From https://github.com/bminor/glibc/blob/a07e000e82cb71238259e674529c37c12dc7d423/malloc/malloc.c#L1711 +#define NSMALLBINS 64 +#define SMALLBIN_WIDTH MALLOC_ALIGNMENT +#define SMALLBIN_CORRECTION (MALLOC_ALIGNMENT > CHUNK_HDR_SZ) +#define MIN_LARGE_SIZE ((NSMALLBINS - SMALLBIN_CORRECTION) * SMALLBIN_WIDTH) + +#define in_smallbin_range(sz) \ +((unsigned long) (sz) < (unsigned long) MIN_LARGE_SIZE) + +#define smallbin_index(sz) \ +((SMALLBIN_WIDTH == 16 ? (((unsigned) (sz)) >> 4) : (((unsigned) (sz)) >> 3))\ ++ SMALLBIN_CORRECTION) +``` +### 选择小型和大型 bin 的函数: + +```c +int choose_bin(size_t size) { + if (size <= SMALL_BIN_SIZE) { + return SMALL_BIN; + } else { + return LARGE_BIN; + } +} +``` +```c +#define bin_index(sz) \ +((in_smallbin_range (sz)) ? smallbin_index (sz) : largebin_index (sz)) +``` +
+ +添加一个小块示例 +```c +#include +#include + +int main(void) +{ +char *chunks[10]; +int i; + +// Loop to allocate memory 8 times +for (i = 0; i < 9; i++) { +chunks[i] = malloc(0x100); +if (chunks[i] == NULL) { // Check if malloc failed +fprintf(stderr, "Memory allocation failed at iteration %d\n", i); +return 1; +} +printf("Address of chunk %d: %p\n", i, (void *)chunks[i]); +} + +// Loop to free the allocated memory +for (i = 0; i < 8; i++) { +free(chunks[i]); +} + +chunks[9] = malloc(0x110); + +return 0; +} +``` +注意我们如何分配和释放9个相同大小的块,这样它们就会**填满tcache**,第八个块存储在未排序的bin中,因为它**对于fastbin来说太大**,第九个块没有被释放,所以第九个和第八个块**不会与顶部块合并**。然后我们分配一个更大的0x110块,这会使**未排序bin中的块进入small bin**。 + +编译它并在main函数的ret操作码处设置断点进行调试。然后使用gef可以看到tcache bin填充和small bin中的一个块: +```bash +gef➤ heap bins +──────────────────────────────────────────────────────────────────────────────── Tcachebins for thread 1 ──────────────────────────────────────────────────────────────────────────────── +Tcachebins[idx=15, size=0x110, count=7] ← Chunk(addr=0xaaaaaaac1d10, size=0x110, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA) ← Chunk(addr=0xaaaaaaac1c00, size=0x110, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA) ← Chunk(addr=0xaaaaaaac1af0, size=0x110, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA) ← Chunk(addr=0xaaaaaaac19e0, size=0x110, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA) ← Chunk(addr=0xaaaaaaac18d0, size=0x110, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA) ← Chunk(addr=0xaaaaaaac17c0, size=0x110, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA) ← Chunk(addr=0xaaaaaaac12a0, size=0x110, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA) +───────────────────────────────────────────────────────────────────────── Fastbins for arena at 0xfffff7f90b00 ───────────────────────────────────────────────────────────────────────── +Fastbins[idx=0, size=0x20] 0x00 +Fastbins[idx=1, size=0x30] 0x00 +Fastbins[idx=2, size=0x40] 0x00 +Fastbins[idx=3, size=0x50] 0x00 +Fastbins[idx=4, size=0x60] 0x00 +Fastbins[idx=5, size=0x70] 0x00 +Fastbins[idx=6, size=0x80] 0x00 +─────────────────────────────────────────────────────────────────────── Unsorted Bin for arena at 0xfffff7f90b00 ─────────────────────────────────────────────────────────────────────── +[+] Found 0 chunks in unsorted bin. +──────────────────────────────────────────────────────────────────────── Small Bins for arena at 0xfffff7f90b00 ──────────────────────────────────────────────────────────────────────── +[+] small_bins[16]: fw=0xaaaaaaac1e10, bk=0xaaaaaaac1e10 +→ Chunk(addr=0xaaaaaaac1e20, size=0x110, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA) +[+] Found 1 chunks in 1 small non-empty bins. +``` +
+ +### 大型bins + +与管理固定大小的块的小型bins不同,**每个大型bin处理一系列块大小**。这样更加灵活,允许系统在不需要为每个大小单独设置bin的情况下**容纳各种大小**。 + +在内存分配器中,大型bins从小型bins结束的地方开始。大型bins的范围逐渐增大,意味着第一个bin可能覆盖512到576字节的块,而下一个覆盖576到640字节的块。这种模式继续下去,最大的bin包含所有大于1MB的块。 + +与小型bins相比,大型bins的操作速度较慢,因为它们必须**对各种块大小的列表进行排序和搜索以找到最佳匹配**。当一个块被插入到大型bin中时,它必须被排序,当分配内存时,系统必须找到正确的块。这额外的工作使它们**更慢**,但由于大型分配比小型分配更少见,这是一个可以接受的权衡。 + +有: + +* 32个64字节范围的bins(与小型bins冲突) +* 16个512字节范围的bins(与小型bins冲突) +* 8个4096字节范围的bins(部分与小型bins冲突) +* 4个32768字节范围的bins +* 2个262144字节范围的bins +* 1个用于剩余大小的bin + +
+ +大型bin大小代码 +```c +// From https://github.com/bminor/glibc/blob/a07e000e82cb71238259e674529c37c12dc7d423/malloc/malloc.c#L1711 + +#define largebin_index_32(sz) \ +(((((unsigned long) (sz)) >> 6) <= 38) ? 56 + (((unsigned long) (sz)) >> 6) :\ +((((unsigned long) (sz)) >> 9) <= 20) ? 91 + (((unsigned long) (sz)) >> 9) :\ +((((unsigned long) (sz)) >> 12) <= 10) ? 110 + (((unsigned long) (sz)) >> 12) :\ +((((unsigned long) (sz)) >> 15) <= 4) ? 119 + (((unsigned long) (sz)) >> 15) :\ +((((unsigned long) (sz)) >> 18) <= 2) ? 124 + (((unsigned long) (sz)) >> 18) :\ +126) + +#define largebin_index_32_big(sz) \ +(((((unsigned long) (sz)) >> 6) <= 45) ? 49 + (((unsigned long) (sz)) >> 6) :\ +((((unsigned long) (sz)) >> 9) <= 20) ? 91 + (((unsigned long) (sz)) >> 9) :\ +((((unsigned long) (sz)) >> 12) <= 10) ? 110 + (((unsigned long) (sz)) >> 12) :\ +((((unsigned long) (sz)) >> 15) <= 4) ? 119 + (((unsigned long) (sz)) >> 15) :\ +((((unsigned long) (sz)) >> 18) <= 2) ? 124 + (((unsigned long) (sz)) >> 18) :\ +126) + +// XXX It remains to be seen whether it is good to keep the widths of +// XXX the buckets the same or whether it should be scaled by a factor +// XXX of two as well. +#define largebin_index_64(sz) \ +(((((unsigned long) (sz)) >> 6) <= 48) ? 48 + (((unsigned long) (sz)) >> 6) :\ +((((unsigned long) (sz)) >> 9) <= 20) ? 91 + (((unsigned long) (sz)) >> 9) :\ +((((unsigned long) (sz)) >> 12) <= 10) ? 110 + (((unsigned long) (sz)) >> 12) :\ +((((unsigned long) (sz)) >> 15) <= 4) ? 119 + (((unsigned long) (sz)) >> 15) :\ +((((unsigned long) (sz)) >> 18) <= 2) ? 124 + (((unsigned long) (sz)) >> 18) :\ +126) + +#define largebin_index(sz) \ +(SIZE_SZ == 8 ? largebin_index_64 (sz) \ +: MALLOC_ALIGNMENT == 16 ? largebin_index_32_big (sz) \ +: largebin_index_32 (sz)) +``` +
+ +
+ +添加一个大块示例 +```c +#include +#include + +int main(void) +{ +char *chunks[2]; + +chunks[0] = malloc(0x1500); +chunks[1] = malloc(0x1500); +free(chunks[0]); +chunks[0] = malloc(0x2000); + +return 0; +} +``` +2个大内存分配被执行,然后一个被释放(将其放入未排序的bin中),接着进行更大的内存分配(将释放的内存从未排序的bin移动到大的bin中)。 + +编译并在主函数的ret操作码处设置断点进行调试。然后使用gef可以看到tcache bin的填充情况以及大的bin中的一个chunk: +```bash +gef➤ heap bin +──────────────────────────────────────────────────────────────────────────────── Tcachebins for thread 1 ──────────────────────────────────────────────────────────────────────────────── +All tcachebins are empty +───────────────────────────────────────────────────────────────────────── Fastbins for arena at 0xfffff7f90b00 ───────────────────────────────────────────────────────────────────────── +Fastbins[idx=0, size=0x20] 0x00 +Fastbins[idx=1, size=0x30] 0x00 +Fastbins[idx=2, size=0x40] 0x00 +Fastbins[idx=3, size=0x50] 0x00 +Fastbins[idx=4, size=0x60] 0x00 +Fastbins[idx=5, size=0x70] 0x00 +Fastbins[idx=6, size=0x80] 0x00 +─────────────────────────────────────────────────────────────────────── Unsorted Bin for arena at 0xfffff7f90b00 ─────────────────────────────────────────────────────────────────────── +[+] Found 0 chunks in unsorted bin. +──────────────────────────────────────────────────────────────────────── Small Bins for arena at 0xfffff7f90b00 ──────────────────────────────────────────────────────────────────────── +[+] Found 0 chunks in 0 small non-empty bins. +──────────────────────────────────────────────────────────────────────── Large Bins for arena at 0xfffff7f90b00 ──────────────────────────────────────────────────────────────────────── +[+] large_bins[100]: fw=0xaaaaaaac1290, bk=0xaaaaaaac1290 +→ Chunk(addr=0xaaaaaaac12a0, size=0x1510, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA) +[+] Found 1 chunks in 1 large non-empty bins. +``` +
+ +### 顶部块 +```c +// From https://github.com/bminor/glibc/blob/a07e000e82cb71238259e674529c37c12dc7d423/malloc/malloc.c#L1711 + +/* +Top + +The top-most available chunk (i.e., the one bordering the end of +available memory) is treated specially. It is never included in +any bin, is used only if no other chunk is available, and is +released back to the system if it is very large (see +M_TRIM_THRESHOLD). Because top initially +points to its own bin with initial zero size, thus forcing +extension on the first malloc request, we avoid having any special +code in malloc to check whether it even exists yet. But we still +need to do so when getting memory from system, so we make +initial_top treat the bin as a legal but unusable chunk during the +interval between initialization and the first call to +sysmalloc. (This is somewhat delicate, since it relies on +the 2 preceding words to be zero during this interval as well.) +*/ + +/* Conveniently, the unsorted bin can be used as dummy top on first call */ +#define initial_top(M) (unsorted_chunks (M)) +``` +基本上,这是一个包含所有当前可用堆的块。当执行malloc时,如果没有可用的空闲块可用,这个顶部块将减小其大小,提供必要的空间。\ +顶部块的指针存储在`malloc_state`结构中。 + +此外,在开始时,可以将未排序的块用作顶部块。 + +
+ +观察顶部块示例 +```c +#include +#include + +int main(void) +{ +char *chunk; +chunk = malloc(24); +printf("Address of the chunk: %p\n", (void *)chunk); +gets(chunk); +return 0; +} +``` +在编译和调试后,在 main 函数的 ret 操作码处设置断点,我发现 malloc 返回了地址:`0xaaaaaaac12a0`,这些是分配的块: +```bash +gef➤ heap chunks +Chunk(addr=0xaaaaaaac1010, size=0x290, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA) +[0x0000aaaaaaac1010 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................] +Chunk(addr=0xaaaaaaac12a0, size=0x20, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA) +[0x0000aaaaaaac12a0 41 41 41 41 41 41 41 00 00 00 00 00 00 00 00 00 AAAAAAA.........] +Chunk(addr=0xaaaaaaac12c0, size=0x410, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA) +[0x0000aaaaaaac12c0 41 64 64 72 65 73 73 20 6f 66 20 74 68 65 20 63 Address of the c] +Chunk(addr=0xaaaaaaac16d0, size=0x410, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA) +[0x0000aaaaaaac16d0 41 41 41 41 41 41 41 0a 00 00 00 00 00 00 00 00 AAAAAAA.........] +Chunk(addr=0xaaaaaaac1ae0, size=0x20530, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA) ← top chunk +``` +在地址`0xaaaaaaac1ae0`处可以看到顶部块(top chunk)。这并不奇怪,因为最新分配的块位于`0xaaaaaaac12a0`,大小为`0x410`,因此`0xaaaaaaac12a0 + 0x410 = 0xaaaaaaac1ae0`。\ +还可以在顶部块的块头中看到顶部块的长度: +```bash +gef➤ x/8wx 0xaaaaaaac1ae0 - 16 +0xaaaaaaac1ad0: 0x00000000 0x00000000 0x00020531 0x00000000 +0xaaaaaaac1ae0: 0x00000000 0x00000000 0x00000000 0x00000000 +``` +
+ +### 最后提醒 + +当使用malloc并且一个块被分割(例如从未链接列表或从顶部块分割)时,从剩余部分创建的块被称为Last Reminder,并且其指针存储在`malloc_state`结构中。 -当**分配块**时,如果在**Tcache 中有所需大小的空闲块,它将使用它**,否则,它将需要等待堆锁以能够在全局 bins 中找到一个或创建一个新的。\ -还有一个优化,在这种情况下,持有堆锁时,线程**将用请求大小的堆块(7 个)填充其 Tcache**,因此如果需要更多,它将在 Tcache 中找到它们。 ## 分配流程 -{% hint style="success" %} -(此当前解释来自[https://heap-exploitation.dhavalkapil.com/diving\_into\_glibc\_heap/core\_functions](https://heap-exploitation.dhavalkapil.com/diving\_into\_glibc\_heap/core\_functions). TODO: 检查最新版本并更新) -{% endhint %} +查看: -分配最终通过函数执行:`void * _int_malloc (mstate av, size_t bytes)`,并按照以下顺序进行: +{% content-ref url="heap-memory-functions/malloc-and-sysmalloc.md" %} +[malloc-and-sysmalloc.md](heap-memory-functions/malloc-and-sysmalloc.md) +{% endcontent-ref %} -1. 更新 `bytes` 以处理**对齐**等问题。 -2. 检查 `av` 是否为**NULL**。 -3. 在**可用的区域**缺失的情况下(当 `av` 为 NULL 时),调用 `sysmalloc` 使用 mmap 获取块。如果成功,调用 `alloc_perturb`。返回指针。 -4. 根据大小不同: -* \[原文添加] 在检查下一个 fastbin 之前使用 tcache。 -* \[原文添加] 如果没有 tcache 但使用了不同的 bin(见后续步骤),尝试从该 bin 填充 tcache。 -* 如果大小在**fastbin**范围内: -1. 获取索引以访问适当的 bin 中的第一个块。 -2. 移除该 bin 中的第一个块,并使 `victim` 指向它。 -3. 如果 `victim` 为 NULL,则转到下一个情况(smallbin)。 -4. 如果 `victim` 不为 NULL,则检查块的大小以确保它属于该特定 bin。否则会抛出错误("malloc(): memory corruption (fast)")。 -5. 调用 `alloc_perturb`,然后返回指针。 -* 如果大小在**smallbin**范围内: -1. 获取索引以访问适当的 bin 中的第一个块。 -2. 如果该 bin 中没有块,则转到下一个情况。通过比较指针 `bin` 和 `bin->bk` 来检查。 -3. 使 `victim` 等于 `bin->bk`(该 bin 中的最后一个块)。如果它为 NULL(在 `初始化` 期间发生),则调用 `malloc_consolidate` 并跳过检查不同 bin 的完整步骤。 -4. 否则,当 `victim` 不为 NULL 时,检查 `victim->bk->fd` 和 `victim` 是否相等。如果它们不相等,则抛出错误("malloc(): smallbin double linked list corrupted")。 -5. 为下一个块(在内存中,而不是在双向链表中)设置 PREV\_INSUSE 位。 -6. 从 bin 列表中移除此块。 -7. 根据 `av` 设置此块的适当区域位。 -8. 调用 `alloc_perturb`,然后返回指针。 -* 如果大小不在 smallbin 范围内: -1. 获取索引以访问适当的 bin 中的第一个块。 -2. 查看 `av` 是否有 fastchunks。通过检查 `av->flags` 中的 `FASTCHUNKS_BIT` 来完成。如果是,则在 `av` 上调用 `malloc_consolidate`。 -5. 如果尚未返回指针,则表示以下一种或多种情况: -1. 大小在 'fastbin' 范围内,但没有可用的 fastchunk。 -2. 大小在 'smallbin' 范围内,但没有可用的 smallchunk(在初始化期间调用 `malloc_consolidate`)。 -3. 大小在 'largbin' 范围内。 -6. 接下来,检查**未排序的块**,并将遍历的块放入 bin 中。这是唯一将块放入 bin 中的地方。从 'TAIL' 迭代未排序的 bin。 -1. `victim` 指向当前考虑的块。 -2. 检查 `victim` 的块大小是否在最小值(`2*SIZE_SZ`)和最大值(`av->system_mem`)范围内。否则抛出错误("malloc(): memory corruption")。 -3. 如果(请求的块大小在 smallbin 范围内)且(`victim` 是最后的剩余块)且(它是未排序 bin 中的唯一块)且(块大小 >= 请求的大小):**将块分成 2 个块**: -* 第一个块匹配请求的大小并返回。 -* 剩余块成为新的最后剩余块。将其插入未排序的 bin 中。 -1. 适当设置这两个块的 `chunk_size` 和 `chunk_prev_size` 字段。 -2. 在调用 `alloc_perturb` 后返回第一个块。 -3. 如果上述条件为假,则控制到此处。从未排序的 bin 中移除 `victim`。如果 `victim` 的大小与请求的大小完全匹配,则在调用 `alloc_perturb` 后返回此块。 -4. 如果 `victim` 的大小在 smallbin 范围内,则将块添加到适当的 smallbin 中的 `HEAD`。 -5. 否则,插入到适当的 largebin 中并保持排序顺序: -6. 首先检查最后一个块(最小的)。如果 `victim` 小于最后一个块,则将其插入到最后。 -7. 否则,循环查找大小 >= `victim` 大小的块。如果大小完全相同,则始终插入到第二个位置。 -8. 最多重复此整个步骤 `MAX_ITERS`(10000 次)或直到未排序 bin 中的所有块用尽。 -7. 检查未排序块后,检查请求的大小是否不在 smallbin 范围内,如果是,则检查**largebins**。 -1. 获取索引以访问适当的 bin 中的第一个块。 -2. 如果最大块的大小(bin 中的第一个块)大于请求的大小: -1. 从 'TAIL' 开始迭代以找到一个大小 >= 请求大小的块(`victim`)。 -2. 调用 `unlink` 以从 bin 中移除 `victim` 块。 -3. 为 `victim` 的块计算 `remainder_size`(这将是 `victim` 的块大小 - 请求的大小)。 -4. 如果此 `remainder_size` >= `MINSIZE`(包括头部的最小块大小),则将块分成两个块。否则,整个 `victim` 块将被返回。将剩余块插入未排序的 bin 中(在 'TAIL' 末尾)。在未排序的 bin 中检查是否 `unsorted_chunks(av)->fd->bk == unsorted_chunks(av)`。否则抛出错误("malloc(): corrupted unsorted chunks")。 -5. 在调用 `alloc_perturb` 后返回 `victim` 块。 -8. 到目前为止,我们已经检查了未排序 bin 以及相应的 fast、small 或 large bin。请注意,使用**确切**请求块大小检查了单个 bin(fast 或 small)。重复以下步骤直到所有 bin 被用尽: -1. 递增 bin 数组的索引以检查下一个 bin。 -2. 使用 `av->binmap` 映射跳过空的 bin。 -3. `victim` 指向当前 bin 的 'TAIL'。 -4. 使用 binmap 确保如果跳过了一个 bin(在上述第 2 步中),则它肯定是空的。但这并不保证所有空的 bin 都会被跳过。检查 victim 是否为空。如果为空,则再次跳过该 bin 并重复上述过程(或“继续”此循环),直到到达非空 bin。 -5. 将块分割为两个块(`victim` 指向非空 bin 的最后一个块)。将剩余块插入未排序的 bin 中(在 'TAIL' 末尾)。在未排序的 bin 中检查是否 `unsorted_chunks(av)->fd->bk == unsorted_chunks(av)`。否则抛出错误("malloc(): corrupted unsorted chunks 2")。 -6. 在调用 `alloc_perturb` 后返回 `victim` 块。 -9. 如果仍未找到空的 bin,则将使用 'top' 块来处理请求: -1. `victim` 指向 `av->top`。 -2. 如果 'top' 块的大小 >= '请求的大小' + `MINSIZE`,则将其分成两个块。在这种情况下,剩余块成为新的 'top' 块,并在调用 `alloc_perturb` 后将另一个块返回给用户。 -3. 查看 `av` 是否有 fastchunks。通过检查 `av->flags` 中的 `FASTCHUNKS_BIT` 来完成。如果是,则在 `av` 上调用 `malloc_consolidate`。返回到步骤 6(检查未排序 bin)。 -4. 如果 `av` 没有 fastchunks,则调用 `sysmalloc` 并在调用 `alloc_perturb` 后返回获得的指针。 -## 自由流动 +## 释放流程 -{% hint style="success" %} -(此当前解释来自[https://heap-exploitation.dhavalkapil.com/diving\_into\_glibc\_heap/core\_functions](https://heap-exploitation.dhavalkapil.com/diving_into_glibc_heap/core_functions). TODO: 检查最新版本并更新) -{% endhint %} +查看: -释放内存块的最终函数是 `_int_free (mstate av, mchunkptr p, int have_lock)`: - -1. 检查 `p` 是否在内存中的 `p + chunksize(p)` 之前(避免包装)。否则会抛出错误 (`free(): invalid pointer`)。 -2. 检查内存块的大小是否至少为 `MINSIZE` 或 `MALLOC_ALIGNMENT` 的倍数。否则会抛出错误 (`free(): invalid size`)。 -3. 如果内存块的大小在 fastbin 列表中: - 1. 检查下一个内存块的大小是否在最小和最大大小 (`av->system_mem`) 之间,否则抛出错误 (`free(): invalid next size (fast)`)。 - 2. 对内存块调用 `free_perturb`。 - 3. 为 `av` 设置 `FASTCHUNKS_BIT`。 - 4. 根据内存块大小获取 fastbin 数组中的索引。 - 5. 检查 bin 顶部是否不是我们要添加的内存块。否则抛出错误 (`double free or corruption (fasttop)`)。 - 6. 检查顶部的 fastbin 内存块大小是否与我们要添加的内存块大小相同。否则抛出错误 (`invalid fastbin entry (free)`)。 - 7. 将内存块插入到 fastbin 列表的顶部并返回。 -4. 如果内存块未被映射: - 1. 检查内存块是否为顶部块。如果是,则抛出错误 (`double free or corruption (top)`)。 - 2. 检查下一个内存块(按内存)是否在区域的边界内。如果不是,则抛出错误 (`double free or corruption (out)`)。 - 3. 检查下一个内存块(按内存)的前一个使用位是否标记。如果没有,则抛出错误 (`double free or corruption (!prev)`)。 - 4. 检查下一个内存块的大小是否在最小和最大大小 (`av->system_mem`) 之间。如果不是,则抛出错误 (`free(): invalid next size (normal)`)。 - 5. 对内存块调用 `free_perturb`。 - 6. 如果前一个内存块(按内存)未被使用,则对前一个内存块调用 `unlink`。 - 7. 如果下一个内存块(按内存)不是顶部块: - 1. 如果下一个内存块未被使用,则对下一个内存块调用 `unlink`。 - 2. 合并前一个、下一个内存块(按内存)中的空闲块,并将其添加到未排序 bin 的头部。在插入之前,检查 `unsorted_chunks(av)->fd->bk == unsorted_chunks(av)` 是否成立。如果不成立,则抛出错误 ("free(): corrupted unsorted chunks")。 - 8. 如果下一个内存块(按内存)是顶部块,则将内存块适当地合并为单个顶部块。 -5. 如果内存块已被映射,则调用 `munmap_chunk`。 +{% content-ref url="heap-memory-functions/free.md" %} +[free.md](heap-memory-functions/free.md) +{% endcontent-ref %} ## 堆函数安全检查 -查看堆中常用函数执行的安全检查: +查看堆中广泛使用的函数执行的安全检查: -{% content-ref url="heap-functions-security-checks.md" %} -[heap-functions-security-checks.md](heap-functions-security-checks.md) +{% content-ref url="heap-memory-functions/heap-functions-security-checks.md" %} +[heap-functions-security-checks.md](heap-memory-functions/heap-functions-security-checks.md) {% endcontent-ref %} ## 参考资料 * [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/) -* [https://heap-exploitation.dhavalkapil.com/diving\_into\_glibc\_heap/core\_functions](https://heap-exploitation.dhavalkapil.com/diving_into_glibc_heap/core_functions) +* [https://heap-exploitation.dhavalkapil.com/diving\_into\_glibc\_heap/core\_functions](https://heap-exploitation.dhavalkapil.com/diving\_into\_glibc\_heap/core\_functions) +* [https://ctf-wiki.mahaloz.re/pwn/linux/glibc-heap/implementation/tcache/](https://ctf-wiki.mahaloz.re/pwn/linux/glibc-heap/implementation/tcache/) + +
+ +从零开始学习AWS黑客技术,成为专家 htARTE(HackTricks AWS Red Team Expert) + +支持HackTricks的其他方式: + +* 如果您想在HackTricks中看到您的**公司广告**或**下载PDF版本的HackTricks**,请查看[**订阅计划**](https://github.com/sponsors/carlospolop)! +* 获取[**官方PEASS & HackTricks周边产品**](https://peass.creator-spring.com) +* 探索[**PEASS Family**](https://opensea.io/collection/the-peass-family),我们的独家[NFTs](https://opensea.io/collection/the-peass-family)收藏品 +* **加入** 💬 [**Discord群**](https://discord.gg/hRep4RUj7f) 或 [**电报群**](https://t.me/peass) 或在**Twitter** 🐦 [**@hacktricks\_live**](https://twitter.com/hacktricks\_live)**上关注**我们。 +* 通过向[**HackTricks**](https://github.com/carlospolop/hacktricks)和[**HackTricks Cloud**](https://github.com/carlospolop/hacktricks-cloud) github仓库提交PR来分享您的黑客技巧。 + +
diff --git a/binary-exploitation/heap/heap-overflow.md b/binary-exploitation/heap/heap-overflow.md index dfae338ce..28288fa90 100644 --- a/binary-exploitation/heap/heap-overflow.md +++ b/binary-exploitation/heap/heap-overflow.md @@ -2,7 +2,7 @@
-从零开始学习AWS黑客技术,成为专家 htARTE(HackTricks AWS红队专家) +从零开始学习AWS黑客技术,成为 htARTE(HackTricks AWS红队专家) 支持HackTricks的其他方式: @@ -16,9 +16,9 @@ ## 基本信息 -堆溢出类似于[**栈溢出**](../stack-overflow/),但发生在堆中。基本上意味着在堆中保留了一些空间来存储一些数据,**存储的数据大于保留的空间**。 +堆溢出类似于[**栈溢出**](../stack-overflow/),但发生在堆中。基本上意味着在堆中保留了一些空间来存储一些数据,**存储的数据大于所保留的空间**。 -在栈溢出中,我们知道一些寄存器,如指令指针或堆栈帧,将从堆栈中恢复,并且可能会滥用这一点。在堆溢出的情况下,**堆块中默认没有存储任何敏感信息**,可以被溢出。但是,可能会有敏感信息或指针,因此此漏洞的**严重性取决于**可以被覆盖的**数据以及攻击者如何滥用此漏洞**。 +在栈溢出中,我们知道一些寄存器,如指令指针或堆栈帧,将从堆栈中恢复,并且可能会滥用这一点。在堆溢出的情况下,**堆块中默认没有存储任何敏感信息**,可以被溢出。但是,可能存在敏感信息或指针,因此此漏洞的**严重性取决于**可以**覆盖哪些数据**以及攻击者如何滥用此漏洞。 {% hint style="success" %} 为了找到溢出偏移量,您可以使用与[**栈溢出**](../stack-overflow/#finding-stack-overflows-offsets)相同的模式。 @@ -28,19 +28,27 @@ 在栈溢出中,当漏洞触发时,堆栈中将出现的排列和数据相当可靠。这是因为堆栈是线性的,在冲突内存中始终增加,在程序运行的特定位置,堆栈内存通常存储类似类型的数据,并且具有一些特定结构,每个函数使用的堆栈部分末尾有一些指针。 -然而,在堆溢出的情况下,由于使用的内存不是线性的,而是通常在内存的分离位置分配的块(不是相邻的),这是因为**通过大小分隔分配的bins和zones**,以及**在分配新块之前使用先前释放的内存**。很难知道将与易受堆溢出的对象发生冲突的对象是什么。因此,当发现堆溢出时,需要找到一种**可靠的方法**,使期望的对象从易受溢出的对象的旁边开始。 +然而,在堆溢出的情况下,由于使用的内存不是线性的,而是通常在内存的分离位置分配的块(不是相邻的),这是因为**通过大小分隔分配的bins和zones**,以及**在分配新块之前使用先前释放的内存**。很难知道将与易受堆溢出的对象发生冲突的对象是什么。因此,当发现堆溢出时,需要找到一种**可靠的方法**,使期望的对象**在易受溢出的对象旁边**。 -用于此目的的技术之一是**堆整理**,例如在[**此文章**](https://azeria-labs.com/grooming-the-ios-kernel-heap/)中使用。文章中解释了在iOS内核中,当一个区域的内存用完以存储内存块时,它会通过一个内核页扩展,然后将该页分割为预期大小的块,这些块将按顺序使用(直到iOS版本9.2,然后这些块以随机方式使用,以增加这些攻击的利用难度)。 +用于此目的的一种技术是**堆整理**,例如在[**此文章**](https://azeria-labs.com/grooming-the-ios-kernel-heap/)中使用。文章中解释了在iOS内核中,当一个区域的内存用完以存储内存块时,它会通过一个内核页扩展,然后将该页分割为预期大小的块,这些块将按顺序使用(直到iOS版本9.2,然后这些块以随机方式使用,以增加这些攻击的利用难度)。 -因此,在先前的文章中,发生堆溢出时,为了强制溢出的对象与受害者顺序发生冲突,通过几个线程强制执行几个**`kallocs`**,以确保所有空闲块都被填充,并创建一个新页面。 +因此,在先前的文章中,发生堆溢出时,为了强制溢出的对象与受害者顺序发生冲突,通过几个线程强制执行**`kallocs`**以确保所有空闲块都被填充,并创建一个新页面。 -为了强制使用特定大小的对象填充此对象,与iOS mach端口相关联的**离线分配**是一个理想的选择。通过精心制作消息的大小,可以精确指定`kalloc`分配的大小,当相应的mach端口被销毁时,相应的分配将立即释放回`kfree`。 +为了强制使用特定大小的对象填充,与iOS mach端口相关联的**离线分配**是一个理想的选择。通过精心制作消息的大小,可以精确指定`kalloc`分配的大小,当相应的mach端口被销毁时,相应的分配将立即释放回`kfree`。 -然后,可以**释放**其中一些占位符。**`kalloc.4096`空闲列表以后进先出的顺序释放元素**,这基本上意味着如果释放了一些占位符,并且尝试在分配易受溢出的对象时分配多个受害者对象,那么很可能该对象将被一个受害者对象跟随。 +然后,可以**释放**其中一些占位符。**`kalloc.4096`**空闲列表以后进先出的顺序释放元素,这基本上意味着如果释放了一些占位符,并且尝试在分配易受溢出的对象时分配多个受害者对象,那么很可能该对象将被一个受害者对象跟随。 -## ARM64示例 +### 示例libc -在页面[https://8ksec.io/arm64-reversing-and-exploitation-part-1-arm-instruction-set-simple-heap-overflow/](https://8ksec.io/arm64-reversing-and-exploitation-part-1-arm-instruction-set-simple-heap-overflow/)中,您可以找到一个堆溢出示例,其中将要执行的命令存储在溢出块的下一个块中。因此,可以通过用易受攻击的块覆盖它来修改要执行的命令,例如: +在[**此页面**](https://guyinatuxedo.github.io/27-edit\_free\_chunk/heap\_consolidation\_explanation/index.html)中,可以找到一个基本的堆溢出仿真,展示了如何通过覆盖下一个块的使用中的前一个位和前一个大小的位置,可以**整理已使用的块**(使其认为未使用),然后再次分配它,从而能够覆盖正在不同指针中使用的数据。 + +另一个来自[**protostar heap 0**](https://guyinatuxedo.github.io/24-heap\_overflow/protostar\_heap0/index.html)的示例展示了一个CTF的非常基本示例,其中可以利用**堆溢出**调用winner函数以**获取标志**。 + +在[**protostar heap 1**](https://guyinatuxedo.github.io/24-heap\_overflow/protostar\_heap1/index.html)示例中,可以看到如何利用缓冲区溢出**在附近的块中覆盖地址**,其中**用户的任意数据**将被写入。 + +### 示例ARM64 + +在页面[https://8ksec.io/arm64-reversing-and-exploitation-part-1-arm-instruction-set-simple-heap-overflow/](https://8ksec.io/arm64-reversing-and-exploitation-part-1-arm-instruction-set-simple-heap-overflow/)中,您可以找到一个堆溢出示例,其中将要执行的命令存储在溢出块的下一个块中。因此,可以通过覆盖它来修改要执行的命令,例如使用简单的利用方式: ```bash python3 -c 'print("/"*0x400+"/bin/ls\x00")' > hax.txt ``` diff --git a/binary-exploitation/heap/unlink-attack.md b/binary-exploitation/heap/unlink-attack.md index 0ba89308f..8f9862f46 100644 --- a/binary-exploitation/heap/unlink-attack.md +++ b/binary-exploitation/heap/unlink-attack.md @@ -9,7 +9,7 @@ - 如果您想看到您的公司在 HackTricks 中被广告,或者**下载 HackTricks 的 PDF**,请查看[**订阅计划**](https://github.com/sponsors/carlospolop)! - 获取[**官方 PEASS & HackTricks 商品**](https://peass.creator-spring.com) - 探索[**PEASS 家族**](https://opensea.io/collection/the-peass-family),我们的独家[**NFTs**](https://opensea.io/collection/the-peass-family) -- **加入** 💬 [**Discord 群组**](https://discord.gg/hRep4RUj7f) 或 [**电报群组**](https://t.me/peass) 或在 **Twitter** 🐦 [**@hacktricks\_live**](https://twitter.com/hacktricks\_live)** 上关注我们**。 +- **加入** 💬 [**Discord 群组**](https://discord.gg/hRep4RUj7f) 或 [**电报群组**](https://t.me/peass) 或在 **Twitter** 上关注我们 🐦 [**@hacktricks\_live**](https://twitter.com/hacktricks\_live)**。** - 通过向 [**HackTricks**](https://github.com/carlospolop/hacktricks) 和 [**HackTricks Cloud**](https://github.com/carlospolop/hacktricks-cloud) github 仓库提交 PR 来分享您的黑客技巧。
@@ -91,7 +91,7 @@ return 0; ### 目标 -* 修改堆栈中一个指向块的指针,使其指向堆栈,从而可以通过在块中写入内容来修改堆栈的内容 +* 修改堆栈中指向一个块的指针,使其指向堆栈,从而可以通过在块中写入内容来修改堆栈的内容 ### 要求 @@ -104,7 +104,7 @@ return 0; * 攻击者控制 chunk1 的内容和 chunk2 的头部。 * 在 chunk1 中,攻击者创建了一个假块的结构: * 为了绕过保护,确保字段 `size` 正确,以避免错误:`corrupted size vs. prev_size while consolidating` -* 假块的字段 `fd` 和 `bk` 指向 chunk1 指针存储位置,偏移分别为 -3 和 -2,因此 `fake_chunk->fd->bk` 和 `fake_chunk->bk->fd` 指向内存(堆栈)中真实 chunk1 地址所在的位置: +* 假块的字段 `fd` 和 `bk` 指向 chunk1 指针存储的位置,偏移分别为 -3 和 -2,因此 `fake_chunk->fd->bk` 和 `fake_chunk->bk->fd` 指向内存(堆栈)中真实 chunk1 地址所在的位置:

https://heap-exploitation.dhavalkapil.com/attacks/unlink_exploit

@@ -112,26 +112,16 @@ return 0; * 当第二个块被释放时,发生了这个假块被取消链接的过程: * `fake_chunk->fd->bk` = `fake_chunk->bk` * `fake_chunk->bk->fd` = `fake_chunk->fd` -* 之前确保 `fake_chunk->fd->bk` 和 `fake_chunk->fd->bk` 指向相同位置(堆栈中存储 `chunk1` 的位置,因此是有效的链接列表)。由于**两者指向相同位置**,只有最后一个(`fake_chunk->bk->fd = fake_chunk->fd`)会生效。 -* 这将**覆盖堆栈中指向 chunk1 的指针,使其指向堆栈中存储的前 3 个地址(或字节)的地址**。 -* 因此,如果攻击者再次控制 chunk1 的内容,他将能够**在堆栈内部写入**,从而有可能覆盖返回地址,跳过 canary 并修改本地变量的值和指针。甚至再次修改堆栈中存储的 chunk1 地址的地址到不同位置,如果攻击者再次控制 chunk1 的内容,他将能够在任何地方写入。 +* 之前确保 `fake_chunk->fd->bk` 和 `fake_chunk->fd->bk` 指向相同位置(堆栈中存储 `chunk1` 的位置,因此是有效的链接列表)。由于**两者都指向相同位置**,只有最后一个(`fake_chunk->bk->fd = fake_chunk->fd`)会**生效**。 +* 这将**覆盖堆栈中指向 chunk1 的指针,使其指向堆栈中存储的地址(或字节)的前 3 个地址**。 +* 因此,如果攻击者再次控制 chunk1 的内容,他将能够**在堆栈内部写入**,从而有可能覆盖返回地址,跳过 canary 并修改本地变量的值和指针。甚至再次修改堆栈中存储的 chunk1 地址的地址,将其指向不同的位置,如果攻击者再次控制 chunk1 的内容,他将能够在任何地方写入。 +* 请注意,这是可能的,因为**地址存储在堆栈中**。风险和利用可能取决于**假块地址存储在何处**。

https://heap-exploitation.dhavalkapil.com/attacks/unlink_exploit

-## 参考 +## 参考资料 * [https://heap-exploitation.dhavalkapil.com/attacks/unlink\_exploit](https://heap-exploitation.dhavalkapil.com/attacks/unlink\_exploit) - -
- -从零开始学习 AWS 黑客技术 htARTE(HackTricks AWS 红队专家) - -支持 HackTricks 的其他方式: - -* 如果您想在 HackTricks 中看到您的**公司广告**或**下载 PDF 版本的 HackTricks**,请查看[**订阅计划**](https://github.com/sponsors/carlospolop)! -* 获取[**官方 PEASS & HackTricks 商品**](https://peass.creator-spring.com) -* 探索[**PEASS 家族**](https://opensea.io/collection/the-peass-family),我们独家的[**NFT**](https://opensea.io/collection/the-peass-family)收藏品 -* **加入** 💬 [**Discord 群组**](https://discord.gg/hRep4RUj7f) 或 [**电报群组**](https://t.me/peass) 或在 **Twitter** 🐦 [**@hacktricks\_live**](https://twitter.com/hacktricks\_live)** 上关注我们**。 -* 通过向 [**HackTricks**](https://github.com/carlospolop/hacktricks) 和 [**HackTricks Cloud**](https://github.com/carlospolop/hacktricks-cloud) github 仓库提交 PR 来分享您的黑客技巧。 - -
+* 尽管在 CTF 中找到 unlink 攻击可能有些奇怪,但这里有一些使用此攻击的 writeup: +* CTF 示例:[https://guyinatuxedo.github.io/30-unlink/hitcon14\_stkof/index.html](https://guyinatuxedo.github.io/30-unlink/hitcon14\_stkof/index.html) +* 在这个示例中,不是堆栈,而是一组 malloc 分配的地址数组。执行 unlink 攻击以能够在此处分配一个块,从而能够控制 malloc 分配的地址数组的指针。然后,还有另一个功能,允许修改这些地址中块的内容,这允许将地址指向 GOT,修改函数地址以获取泄漏和 RCE。 diff --git a/binary-exploitation/heap/use-after-free/README.md b/binary-exploitation/heap/use-after-free/README.md index f9ad031cd..424cb4b42 100644 --- a/binary-exploitation/heap/use-after-free/README.md +++ b/binary-exploitation/heap/use-after-free/README.md @@ -8,7 +8,7 @@ * 如果您想看到您的**公司在HackTricks中做广告**或**下载PDF格式的HackTricks**,请查看[**订阅计划**](https://github.com/sponsors/carlospolop)! * 获取[**官方PEASS & HackTricks周边产品**](https://peass.creator-spring.com) -* 探索[**PEASS家族**](https://opensea.io/collection/the-peass-family),我们的独家[**NFTs**](https://opensea.io/collection/the-peass-family) +* 探索[**PEASS家族**](https://opensea.io/collection/the-peass-family),我们独家[**NFTs**](https://opensea.io/collection/the-peass-family)收藏品 * **加入** 💬 [**Discord群**](https://discord.gg/hRep4RUj7f) 或 [**电报群**](https://t.me/peass) 或在**Twitter**上关注我们 🐦 [**@hacktricks\_live**](https://twitter.com/hacktricks\_live)**。** * 通过向[**HackTricks**](https://github.com/carlospolop/hacktricks)和[**HackTricks Cloud**](https://github.com/carlospolop/hacktricks-cloud) github仓库提交PR来分享您的黑客技巧。 @@ -29,8 +29,6 @@ [first-fit.md](first-fit.md) {% endcontent-ref %} -## -
从零开始学习AWS黑客技术,成为专家 htARTE(HackTricks AWS红队专家) @@ -39,7 +37,7 @@ * 如果您想看到您的**公司在HackTricks中做广告**或**下载PDF格式的HackTricks**,请查看[**订阅计划**](https://github.com/sponsors/carlospolop)! * 获取[**官方PEASS & HackTricks周边产品**](https://peass.creator-spring.com) -* 探索[**PEASS家族**](https://opensea.io/collection/the-peass-family),我们的独家[**NFTs**](https://opensea.io/collection/the-peass-family) +* 探索[**PEASS家族**](https://opensea.io/collection/the-peass-family),我们独家[**NFTs**](https://opensea.io/collection/the-peass-family)收藏品 * **加入** 💬 [**Discord群**](https://discord.gg/hRep4RUj7f) 或 [**电报群**](https://t.me/peass) 或在**Twitter**上关注我们 🐦 [**@hacktricks\_live**](https://twitter.com/hacktricks\_live)**。** * 通过向[**HackTricks**](https://github.com/carlospolop/hacktricks)和[**HackTricks Cloud**](https://github.com/carlospolop/hacktricks-cloud) github仓库提交PR来分享您的黑客技巧。