First Fit

tip

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

Підтримайте HackTricks

First Fit

Коли ви звільняєте пам'ять у програмі, використовуючи glibc, різні "контейнери" використовуються для управління шматками пам'яті. Ось спрощене пояснення двох поширених сценаріїв: незасортовані контейнери та швидкі контейнери.

Unsorted Bins

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

Приклад:

  • Ви виділяєте 300 байт (a), потім 250 байт (b), потім звільняєте a і знову запитуєте 250 байт (c).
  • Коли ви звільняєте a, він потрапляє до незасортованого контейнера.
  • Якщо ви знову запитуєте 250 байт, аллокатор знаходить a на хвості і розділяє його, повертаючи частину, яка відповідає вашому запиту, і залишаючи решту в контейнері.
  • c буде вказувати на попереднє a і буде заповнено вмістом a.
c
char *a = malloc(300);
char *b = malloc(250);
free(a);
char *c = malloc(250);

Fastbins

Fastbins використовуються для малих обсягів пам'яті. На відміну від несортованих бінів, fastbins додають нові обсяги на початок, створюючи поведінку останній прийшов - перший пішов (LIFO). Якщо ви запитуєте малий обсяг пам'яті, аллокатор витягне з голови fastbin.

Example:

c
char *a = malloc(20);
char *b = malloc(20);
char *c = malloc(20);
char *d = malloc(20);
free(a);
free(b);
free(c);
free(d);
a = malloc(20);   // d
b = malloc(20);   // c
c = malloc(20);   // b
d = malloc(20);   // a

🔥 Сучасні розгляди glibc (tcache ≥ 2.26)

З glibc 2.26 кожен потік зберігає свій власний tcache, який запитується перед несортованим біном. Тому сценарій першого підходу буде досягнуто лише якщо:

  1. Запитуваний розмір є більшим за tcache_max (0x420 на 64-біт за замовчуванням), або
  2. Відповідний бін tcache є вже заповненим або очищеним вручну (шляхом виділення 7 елементів і їх використання).

У реальних експлойтах ви зазвичай додасте допоміжну рутину, таку як:

c
// Drain the tcache for a given size
for(int i = 0; i < 7; i++) pool[i] = malloc(0x100);
for(int i = 0; i < 7; i++) free(pool[i]);

Якщо tcache вичерпан, наступні звільнення потрапляють до несортованого бін і класична поведінка first-fit (пошук з хвоста, вставка з голови) може бути знову активована.


🚩 Створення UAF з перекриттям з first-fit

Фрагмент нижче (перевірено на glibc 2.38) показує, як спліттер у несортованому біні може бути зловжито використаний для створення 2 перекриваючих вказівників – потужного примітиву, який перетворює одне звільнення на запис-після-звільнення.

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

int main(){
setbuf(stdout, NULL);

/* 1. prepare 2 adjacent chunks and free the first one */
char *A = malloc(0x420);   // big enough to bypass tcache
char *B = malloc(0x420);
strcpy(A, "AAAA\n");
free(A);                   // A → unsorted

/* 2. request a *smaller* size to force a split of A */
char *C = malloc(0x400);   // returns lower half of former A

/* 3. The remainder of A is still in the unsorted bin.
Another 0x400-byte malloc will now return the *same*
region pointed to by B – creating a UAF/overlap. */
char *C2 = malloc(0x400);

printf("B  = %p\nC2 = %p (overlaps B)\n", B, C2);

// Arbitrary write in B is immediately visible via C2
memset(B, 'X', 0x10);
fwrite(C2, 1, 0x10, stdout);  // prints Xs
}

Рецепт експлуатації (поширений у недавніх CTF):

  1. Витягніть tcache для цільового розміру.
  2. Звільніть шматок, щоб він потрапив у несортований бін.
  3. Виділіть трохи менший розмір – аллокатор розділяє несортований шматок.
  4. Виділіть знову – залишкова частина перекривається з існуючим шматком, що використовується → UAF.
  5. Перезапишіть чутливі поля (вказівники на функції, FILE vtable тощо).

Практичний приклад можна знайти в Setjmp виклику 2024 HITCON Quals, де цей точний примітив використовується для переходу від UAF до повного контролю над __free_hook.{{#ref}} ../../../../references/2024_setjmp_firstfit.md {{#endref}}


🛡️ Заходи захисту та зміцнення

  • Безпечне з'єднання (glibc ≥ 2.32) захищає лише односпрямовані списки tcache/fastbin. Несортовані/малі/великі біни все ще зберігають сирі вказівники, тому перекриття на основі першого підходу залишаються життєздатними, якщо ви можете отримати витік з купи.
  • Шифрування вказівників купи та MTE (ARM64) поки не впливають на x86-64 glibc, але прапори зміцнення дистрибутиву, такі як GLIBC_TUNABLES=glibc.malloc.check=3, призупинять роботу при несумісних метаданих і можуть зламати наївні PoC.
  • Заповнення tcache при звільненні (запропоновано в 2024 для glibc 2.41) ще більше зменшить використання несортованих; слідкуйте за майбутніми випусками під час розробки загальних експлойтів.

Інші посилання та приклади

  • https://heap-exploitation.dhavalkapil.com/attacks/first_fit
  • https://8ksec.io/arm64-reversing-and-exploitation-part-2-use-after-free/
  • ARM64. Використання після звільнення: створіть об'єкт користувача, звільніть його, створіть об'єкт, який отримує звільнений шматок і дозволяє записувати в нього, перезаписуючи позицію user->password з попереднього. Повторно використайте користувача, щоб обійти перевірку пароля.
  • https://ctf-wiki.mahaloz.re/pwn/linux/glibc-heap/use_after_free/#example
  • Програма дозволяє створювати нотатки. Нотатка міститиме інформацію про нотатку в malloc(8) (з вказівником на функцію, яку можна викликати) і вказівник на інший malloc() з вмістом нотатки.
  • Атака полягатиме в створенні 2 нотаток (note0 і note1) з більшим вмістом malloc, ніж розмір інформації про нотатку, а потім звільнити їх, щоб вони потрапили в швидкий бін (або tcache).
  • Потім створіть ще одну нотатку (note2) з розміром вмісту 8. Вміст буде в note1, оскільки шматок буде повторно використано, де ми можемо змінити вказівник функції, щоб вказувати на функцію win, а потім використати Use-After-Free для виклику нового вказівника функції.
  • https://guyinatuxedo.github.io/26-heap_grooming/pico_areyouroot/index.html
  • Можливо виділити деяку пам'ять, записати бажане значення, звільнити його, повторно виділити, і оскільки попередні дані все ще там, їх буде оброблено відповідно до нової очікуваної структури в шматку, що робить можливим встановлення значення для отримання прапора.
  • https://guyinatuxedo.github.io/26-heap_grooming/swamp19_heapgolf/index.html
  • У цьому випадку потрібно записати 4 всередині конкретного шматка, який є першим, що виділяється (навіть після примусового звільнення всіх з них). У кожному новому виділеному шматку його номер у масиві індексується. Потім виділіть 4 шматки (+ початково виділений), останній буде містити 4 всередині, звільніть їх і примусьте повторне виділення першого, яке використовуватиме останній звільнений шматок, що містить 4 всередині.
  • 2024 HITCON Quals Setjmp звіт (Quarkslab) – практична атака на перекриття першого підходу / несортованого розподілу: https://ctftime.org/writeup/39355
  • Angstrom CTF 2024 heapify звіт – зловживання розподілом несортованого біна для витоку libc та отримання перекриття: https://hackmd.io/@aneii11/H1S2snV40

tip

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

Підтримайте HackTricks