# Біни та розподіл пам'яті {% hint style="success" %} Вивчайте та практикуйте хакінг AWS: [**Навчання HackTricks AWS Red Team Expert (ARTE)**](https://training.hacktricks.xyz/courses/arte)\ Вивчайте та практикуйте хакінг GCP: [**Навчання HackTricks GCP Red Team Expert (GRTE)**](https://training.hacktricks.xyz/courses/grte)
Підтримайте HackTricks * Перевірте [**плани підписки**](https://github.com/sponsors/carlospolop)! * **Приєднуйтесь до** 💬 [**групи Discord**](https://discord.gg/hRep4RUj7f) або [**групи Telegram**](https://t.me/peass) або **слідкуйте** за нами на **Twitter** 🐦 [**@hacktricks\_live**](https://twitter.com/hacktricks\_live)**.** * **Поширюйте хакерські трюки, надсилаючи PR до** [**HackTricks**](https://github.com/carlospolop/hacktricks) та [**HackTricks Cloud**](https://github.com/carlospolop/hacktricks-cloud) репозиторіїв на GitHub.
{% endhint %} ## Основна інформація Для покращення ефективності зберігання блоків кожен блок не зберігається лише в одному зв'язаному списку, а існує кілька типів. Це біни, існує 5 типів бінів: [62](https://sourceware.org/git/gitweb.cgi?p=glibc.git;a=blob;f=malloc/malloc.c;h=6e766d11bc85b6480fa5c9f2a76559f8acf9deb5;hb=HEAD#l1407) малих бінів, 63 великих бінів, 1 неупорядкований бін, 10 швидких бінів та 64 біни tcache на кожен потік. Початкова адреса кожного неупорядкованого, малого та великого бінів знаходиться в одному масиві. Індекс 0 не використовується, 1 - це неупорядкований бін, біни 2-64 - це малі біни, а біни 65-127 - це великі біни. ### Біни кешу Tcache (для кожного потоку) Навіть якщо потоки намагаються мати свій власний стек (див. [Арени](bins-and-memory-allocations.md#arenas) та [Підстеки](bins-and-memory-allocations.md#subheaps)), існує можливість того, що процес з великою кількістю потоків (наприклад, веб-сервер) **закінчиться спільним стеком з іншими потоками**. У цьому випадку основним рішенням є використання **замків**, які можуть **значно уповільнити роботу потоків**. Отже, tcache схожий на швидкий бін на кожен потік тим, що це **однозв'язний список**, який не об'єднує блоки. Кожен потік має **64 однозв'язних біни tcache**. Кожен бін може містити максимум [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 **не заповнений** (вже 7 блоків), **він буде виділений туди**. Якщо він не може перейти до tcache, йому доведеться зачекати, поки стек буде розблокований, щоб виконати операцію вивільнення глобально. Коли **блок виділяється**, якщо є вільний блок потрібного розміру в **Tcache, він використовує його**, якщо немає, йому доведеться зачекати, поки стек буде розблокований, щоб знайти його в глобальних бінах або створити новий.\ Тут також є оптимізація, в цьому випадку, під час блокування стеку, потік **заповнить свій Tcache блоками стеку (7) потрібного розміру**, тому в разі потреби він знайде їх в 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 з функції main. Потім за допомогою gef ви можете побачити використання біна tcache: ```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 У наступному коді можна побачити **максимальні біни** та **частки на індекс**, створену структуру **`tcache_entry`** для уникнення подвійних вивільнень та **`tcache_perthread_struct`**, структуру, яку кожен потік використовує для зберігання адрес кожного індексу біна.
tcache_entry та tcache_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` ```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)); } } ```
#### Індекси Tcache Tcache має кілька блоків в залежності від розміру та початкові вказівники на **перший фрагмент кожного індексу та кількість фрагментів на кожен індекс розташовані всередині фрагмента**. Це означає, що знаходячи фрагмент з цією інформацією (зазвичай перший), можна знайти всі початкові точки tcache та кількість фрагментів Tcache. ### Швидкі блоки Швидкі блоки призначені для **прискорення виділення пам'яті для невеликих фрагментів**, тримаючи недавно звільнені фрагменти в швидкодійній структурі. Ці блоки використовують підхід "Останнім прийшов - першим вийшов" (LIFO), що означає, що **останній звільнений фрагмент перший**, який буде використаний при новому запиті на виділення. Ця поведінка є вигідною для швидкості, оскільки швидше вставляти та видаляти з верхушки стеку (LIFO) порівняно з чергою (FIFO). Крім того, **швидкі блоки використовують однозв'язні списки**, а не двозв'язні, що подальше покращує швидкість. Оскільки фрагменти в швидких блоках не об'єднуються з сусідніми, немає потреби в складній структурі, яка дозволяє видалення з середини. Однозв'язний список є простішим та швидшим для цих операцій. Основна ідея полягає в тому, що заголовок (вказівник на перший фрагмент для перевірки) завжди вказує на останній звільнений фрагмент такого розміру. Таким чином: * Коли виділяється новий фрагмент такого розміру, заголовок вказує на вільний фрагмент для використання. Оскільки цей вільний фрагмент вказує на наступний для використання, ця адреса зберігається в заголовку, щоб наступне виділення знало, де отримати доступний фрагмент. * Коли фрагмент звільняється, вільний фрагмент збереже адресу поточного доступного фрагмента, а адреса цього ново звільненого фрагмента буде поміщена в заголовок. Максимальний розмір зв'язаного списку - `0x80`, і вони організовані так, що фрагмент розміром `0x20` буде в індексі `0`, фрагмент розміром `0x30` буде в індексі `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` з функції `main`. Потім за допомогою `gef` ви можете побачити, що бін tcache повний, і один чанк знаходиться в швидкому біні: ```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 ```
### Невідсортований бін Невідсортований бін - це **кеш**, який використовує менеджер купи для прискорення виділення пам'яті. Ось як це працює: коли програма звільняє фрагмент, і якщо цей фрагмент не може бути виділений в tcache або fast bin і не конфліктує з верхнім фрагментом, менеджер купи не відразу поміщає його в певний малий або великий бін. Замість цього він спочатку намагається **об'єднати його з будь-якими сусідніми вільними фрагментами**, щоб створити більший блок вільної пам'яті. Потім він поміщає цей новий фрагмент в загальний бін, який називається "невідсортований бін". Коли програма **потребує пам'яті**, менеджер купи **перевіряє невідсортований бін**, щоб побачити, чи є там достатньо великий фрагмент. Якщо він знаходить один, він використовує його відразу. Якщо не знаходить підходящого фрагмента в невідсортованому біні, він переміщає всі фрагменти у цьому списку до відповідних бінів, або малих, або великих, в залежності від їх розміру. Зверніть увагу, що якщо більший фрагмент розбивається на 2 половини, і решта більша за MINSIZE, його буде повернуто назад у невідсортований бін. Отже, невідсортований бін - це спосіб прискорити виділення пам'яті шляхом швидкого повторного використання недавно звільненої пам'яті та зменшення потреби в часовитратних пошуках та об'єднаннях. {% hint style="danger" %} Зверніть увагу, що навіть якщо фрагменти належать до різних категорій, якщо доступний фрагмент конфліктує з іншим доступним фрагментом (навіть якщо вони початково належать до різних бінів), вони будуть об'єднані. {% 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**, а восьмий зберігається в неупорядкованому блоці, оскільки він **занадто великий для fastbin**, і дев'ятий не звільнений, тому дев'ятий та восьмий **не об'єднуються з верхнім чанком**. Скомпілюйте його та відлагодьте з точкою зупинки в опкоді `ret` з функції `main`. Потім за допомогою `gef` ви можете побачити, що бін tcache повний, і один чанк знаходиться в неупорядкованому блоці: ```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. ```
### Малі біни Малі біни швидші за великі біни, але повільніші за швидкі біни. Кожен бін з 62 матиме **частини одного розміру**: 16, 24, ... (з максимальним розміром 504 байти в 32-бітних системах та 1024 в 64-бітних). Це допомагає прискорити пошук біна, де має бути виділено місце, а також вставку та видалення записів у цих списках. Ось як розраховується розмір малого біна в залежності від індексу біна: * Найменший розмір: 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) ``` Функція для вибору між малими та великими блоками: ```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**, і восьма зберігається в неупорядкованому блоці, оскільки вона **занадто велика для fastbin**, а дев'ята не звільнена, тому дев'ята та восьма **не об'єднуються з верхнім блоцом**. Потім ми виділяємо більший блоць розміром 0x110, що призводить до того, що **блок в неупорядкованому блоці переходить до small bin**. Скомпілюйте це та відлагодьте його з точкою зупинки в опкоді `ret` з функції `main`. Потім за допомогою `gef` ви можете побачити, що бін tcache повний, і один блок знаходиться в 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. ```
### Великі біни На відміну від малих бінів, які керують частинами фіксованих розмірів, **кожен великий бін обробляє діапазон розмірів частин**. Це більш гнучко, дозволяючи системі пристосовуватися до **різних розмірів** без необхідності окремого біна для кожного розміру. У розподільчій пам'яті великі біни починаються там, де закінчуються малі біни. Діапазони для великих бінів поступово збільшуються, що означає, що перший бін може охоплювати частини від 512 до 576 байтів, тоді як наступний охоплює від 576 до 640 байтів. Ця модель продовжується, і найбільший бін містить всі частини понад 1 МБ. Великі біни повільніше працюють порівняно з малими бінами, оскільки їм потрібно **сортувати та шукати через список різних розмірів частин, щоб знайти найкраще відповідне** для виділення. Коли частина вставляється в великий бін, її потрібно відсортувати, а коли виділяється пам'ять, системі потрібно знайти відповідну частину. Ця додаткова робота робить їх **повільнішими**, але оскільки великі виділення менш поширені, це прийнятний компроміс. Є: * 32 біни діапазону 64 байти (збігаються з малими бінами) * 16 бінів діапазону 512 байтів (збігаються з малими бінами) * 8 бінів діапазону 4096 байтів (частково збігаються з малими бінами) * 4 біни діапазону 32768 байтів * 2 біни діапазону 262144 байти * 1 бін для залишкових розмірів
Код розмірів великих бінів ```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 великі виділення виконуються, потім одне з них звільняється (поміщаючи його в неупорядкований бін) і робиться більше виділення (переміщаючи вільний блок з неупорядкованого біна в великий бін). Скомпілюйте його та відлагодьте з точкою зупинки в опкоді `ret` з функції `main`. Потім за допомогою `gef` ви можете побачити, що бін tcache повний, і один блок знаходиться в великому біні: ```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; } ``` Після компіляції та налагодження з точкою зупинки в опкоді `ret` `main` я побачив, що 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`. Це не диво, оскільки останній виділений фрагмент був за адресою `0xaaaaaaac12a0` з розміром `0x410`, і `0xaaaaaaac12a0 + 0x410 = 0xaaaaaaac1ae0`.\ Також можна побачити довжину верхнього фрагмента на його заголовку фрагмента: ```bash gef➤ x/8wx 0xaaaaaaac1ae0 - 16 0xaaaaaaac1ad0: 0x00000000 0x00000000 0x00020531 0x00000000 0xaaaaaaac1ae0: 0x00000000 0x00000000 0x00000000 0x00000000 ```
### Останній залишок Коли використовується malloc і блок розділяється (наприклад, з невпорядкованого баку або з верхнього блоку), блок, створений з решти розділеного блоку, називається Останнім залишком, а його вказівник зберігається в структурі `malloc_state`. ## Потік виділення Перевірте: {% content-ref url="heap-memory-functions/malloc-and-sysmalloc.md" %} [malloc-and-sysmalloc.md](heap-memory-functions/malloc-and-sysmalloc.md) {% endcontent-ref %} ## Потік вивільнення Перевірте: {% content-ref url="heap-memory-functions/free.md" %} [free.md](heap-memory-functions/free.md) {% endcontent-ref %} ## Перевірки безпеки функцій купи Перевірте перевірки безпеки, які виконуються в широко використовуваних функціях в купі, в: {% 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://ctf-wiki.mahaloz.re/pwn/linux/glibc-heap/implementation/tcache/](https://ctf-wiki.mahaloz.re/pwn/linux/glibc-heap/implementation/tcache/) {% hint style="success" %} Вивчайте та практикуйте взлом AWS:[**Навчання HackTricks AWS Red Team Expert (ARTE)**](https://training.hacktricks.xyz/courses/arte)\ Вивчайте та практикуйте взлом GCP: [**Навчання HackTricks GCP Red Team Expert (GRTE)**](https://training.hacktricks.xyz/courses/grte)
Підтримайте HackTricks * Перевірте [**плани підписки**](https://github.com/sponsors/carlospolop)! * **Приєднуйтесь до** 💬 [**групи Discord**](https://discord.gg/hRep4RUj7f) або групи [**telegram**](https://t.me/peass) або **слідкуйте** за нами на **Twitter** 🐦 [**@hacktricks\_live**](https://twitter.com/hacktricks\_live)**.** * **Поширюйте хакерські трюки, надсилаючи PR до** [**HackTricks**](https://github.com/carlospolop/hacktricks) та [**HackTricks Cloud**](https://github.com/carlospolop/hacktricks-cloud) репозиторіїв на GitHub.
{% endhint %}