Bins & Memory Allocations

Learn & practice AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE) Learn & practice GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)

Support HackTricks

Basic Information

Щоб покращити ефективність зберігання шматків, кожен шматок не просто в одному зв'язаному списку, а існує кілька типів. Це бінси, і є 5 типів бінсів: 62 маленькі бінси, 63 великі бінси, 1 незасортований бін, 10 швидких бінсів і 64 tcache бінси на потік.

Початкова адреса для кожного незасортованого, маленького та великого бінса знаходиться в одному масиві. Індекс 0 не використовується, 1 - це незасортований бін, бінси 2-64 - це маленькі бінси, а бінси 65-127 - це великі бінси.

Tcache (Кеш на потік) Бінси

Хоча потоки намагаються мати свій власний купу (див. Arenas та Subheaps), існує можливість, що процес з великою кількістю потоків (як веб-сервер) в кінцевому підсумку поділиться купою з іншими потоками. У цьому випадку основним рішенням є використання замків, які можуть значно сповільнити потоки.

Отже, tcache подібний до швидкого бінса на потік тим, що це одинарний зв'язаний список, який не об'єднує шматки. Кожен потік має 64 односпрямованих tcache бінси. Кожен бін може мати максимум 7 шматків одного розміру, що варіюються від 24 до 1032B на 64-бітних системах і від 12 до 516B на 32-бітних системах.

Коли потік звільняє шматок, якщо він не занадто великий для розподілу в tcache і відповідний tcache бін не заповнений (вже 7 шматків), він буде розподілений там. Якщо він не може потрапити в tcache, йому потрібно буде чекати на блокування купи, щоб мати можливість виконати операцію звільнення глобально.

Коли шматок розподіляється, якщо є вільний шматок потрібного розміру в Tcache, він його використає, якщо ні, йому потрібно буде чекати на блокування купи, щоб мати можливість знайти один у глобальних бінсах або створити новий. Існує також оптимізація, в цьому випадку, маючи блокування купи, потік заповнить свій Tcache шматками купи (7) запитуваного розміру, так що в разі, якщо йому потрібно більше, він знайде їх у Tcache.

Add a tcache chunk example

```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 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

У наступному коді можна побачити максимальні контейнери та частини на індекс, структуру 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;

</details>

Функція `__tcache_init` — це функція, яка створює та виділяє простір для об'єкта `tcache_perthread_struct`

<details>

<summary>код tcache_init</summary>
```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...

Шматки в швидких бінах не встановлюються як доступні, тому їх зберігають як швидкі біни на деякий час, замість того, щоб мати можливість об'єднуватися з іншими вільними шматками, що їх оточують.

// 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)
Додати приклад швидкого блоку

```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

Unsorted bin

Unsorted bin - це кеш, який використовується менеджером купи для прискорення виділення пам'яті. Ось як це працює: коли програма звільняє шматок, і якщо цей шматок не може бути виділений у tcache або швидкому біні і не стикається з верхнім шматком, менеджер купи не відразу поміщає його в конкретний малий або великий бін. Замість цього він спочатку намагається об'єднати його з будь-якими сусідніми вільними шматками, щоб створити більший блок вільної пам'яті. Потім він поміщає цей новий шматок у загальний бін, званий "unsorted bin."

Коли програма запитує пам'ять, менеджер купи перевіряє unsorted bin, щоб дізнатися, чи є шматок достатнього розміру. Якщо він знаходить один, він використовує його відразу. Якщо він не знаходить підходящий шматок в unsorted bin, він переміщує всі шматки в цьому списку в їх відповідні біни, або малі, або великі, залежно від їх розміру.

Зверніть увагу, що якщо більший шматок розділений на 2 половини, а решта більша за MINSIZE, він буде повернутий назад у unsorted bin.

Отже, unsorted bin - це спосіб прискорити виділення пам'яті, швидко повторно використовуючи нещодавно звільнену пам'ять і зменшуючи потребу в трудомістких пошуках і злиттях.

Зверніть увагу, що навіть якщо шматки належать до різних категорій, якщо доступний шматок стикається з іншим доступним шматком (навіть якщо вони спочатку належать до різних бінів), вони будуть об'єднані.

Додати приклад unsorted chunk

```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` opcode з функції `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)

// 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)

Функція для вибору між малими та великими біном:

#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, що призводить до того, що **шматок в незасортованому біні переходить до малого біна**.

Скомпілюйте це та налагодьте з точкою зупинки в `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 ───────────────────────────────────────────────────────────────────────
[+] 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 контейнери діапазону 64B (перекриваються з малими контейнерами)

  • 16 контейнерів діапазону 512B (перекриваються з малими контейнерами)

  • 8 контейнерів діапазону 4096B (частково перекриваються з малими контейнерами)

  • 4 контейнери діапазону 32768B

  • 2 контейнери діапазону 262144B

  • 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))

</details>

<details>

<summary>Додати приклад великого блоку</summary>
```c
#include <stdlib.h>
#include <stdio.h>

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 заповнений, а один шматок знаходиться у великому біні:

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.

Верхній шматок

// 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. Також можна побачити довжину верхнього шматка на його заголовку шматка:

gef➤  x/8wx 0xaaaaaaac1ae0 - 16
0xaaaaaaac1ad0:	0x00000000	0x00000000	0x00020531	0x00000000
0xaaaaaaac1ae0:	0x00000000	0x00000000	0x00000000	0x00000000

Останній залишок

Коли використовується malloc і шматок ділиться (наприклад, з неупорядкованого бін або з верхнього шматка), шматок, створений з решти поділеного шматка, називається Останній залишок, а його вказівник зберігається в структурі malloc_state.

Потік виділення

Перегляньте:

malloc & sysmalloc

Потік звільнення

Перегляньте:

free

Перевірки безпеки функцій купи

Перевірте перевірки безпеки, які виконуються широко використовуваними функціями в купі в:

Heap Functions Security Checks

Посилання

Вивчайте та практикуйте Hacking AWS:HackTricks Training AWS Red Team Expert (ARTE) Вивчайте та практикуйте Hacking GCP: HackTricks Training GCP Red Team Expert (GRTE)

Підтримати HackTricks

Last updated