Translated ['src/binary-exploitation/libc-heap/unsorted-bin-attack.md']

This commit is contained in:
Translator 2025-08-28 17:04:56 +00:00
parent 816a920f22
commit c66ac3c3e9

View File

@ -2,54 +2,111 @@
{{#include ../../banners/hacktricks-training.md}}
## Basic Information
## Основна інформація
Для детальнішої інформації про те, що таке unsorted bin див. цю сторінку:
Для отримання додаткової інформації про те, що таке unsorted bin, перегляньте цю сторінку:
{{#ref}}
bins-and-memory-allocations.md
{{#endref}}
Unsorted lists можуть записувати адресу в `unsorted_chunks (av)` у адресі `bk` частини. Тому, якщо зловмисник може **модифікувати адресу вказівника `bk`** у частині всередині unsorted bin, він може **записати цю адресу в довільну адресу**, що може бути корисним для витоку адрес Glibc або обходу деяких захистів.
Unsorted lists можуть записати адресу в `unsorted_chunks (av)` у полі `bk` чанку. Тому, якщо атакуючий може **змінити адресу вказівника `bk`** у чанку всередині unsorted bin, він зможе **записати цю адресу за довільною адресою**, що може допомогти leak адреси Glibc або обійти деякі захисти.
Отже, в основному, ця атака дозволяє **встановити велике число за довільною адресою**. Це велике число є адресою, яка може бути адресою купи або адресою Glibc. Типовою мішенню є **`global_max_fast`**, щоб дозволити створювати fast bin bins з більшими розмірами (і перейти від атаки unsorted bin до атаки fast bin).
Отже, по суті ця атака дозволяє **встановити велике число за довільною адресою**. Це «велике число» — адреса, яка може бути heap-адресою або адресою Glibc. Традиційною ціллю був **`global_max_fast`**, щоб дозволити створювати fast bin з більшими розмірами (та перейти від unsorted bin attack до fast bin attack).
- Modern note (glibc ≥ 2.39): `global_max_fast` became an 8bit global. Blindly writing a pointer there via an unsorted-bin write will clobber adjacent libc data and will not reliably raise the fastbin limit anymore. Prefer other targets or other primitives when running against glibc 2.39+. See "Modern constraints" below and consider combining with other techniques like a [large bin attack](large-bin-attack.md) or a [fast bin attack](fast-bin-attack.md) once you have a stable primitive.
> [!TIP]
> T> акісуючи приклад, наведений у [https://ctf-wiki.mahaloz.re/pwn/linux/glibc-heap/unsorted_bin_attack/#principle](https://ctf-wiki.mahaloz.re/pwn/linux/glibc-heap/unsorted_bin_attack/#principle) і використовуючи 0x4000 і 0x5000 замість 0x400 і 0x500 як розміри частин (щоб уникнути Tcache), можна побачити, що **сьогодні** помилка **`malloc(): unsorted double linked list corrupted`** викликається.
> T> aking a look to the example provided in [https://ctf-wiki.mahaloz.re/pwn/linux/glibc-heap/unsorted_bin_attack/#principle](https://ctf-wiki.mahaloz.re/pwn/linux/glibc-heap/unsorted_bin_attack/#principle) and using 0x4000 and 0x5000 instead of 0x400 and 0x500 as chunk sizes (to avoid Tcache) it's possible to see that **nowadays** the error **`malloc(): unsorted double linked list corrupted`** is triggered.
>
> Отже, ця атака unsorted bin тепер (серед інших перевірок) також вимагає можливості виправити подвійну зв'язку, щоб це було обійдено `victim->bk->fd == victim` або не `victim->fd == av (arena)`, що означає, що адреса, куди ми хочемо записати, повинна мати адресу фальшивої частини в її позиції `fd`, а фальшива частина `fd` вказує на арену.
> Therefore, this unsorted bin attack now (among other checks) also requires to be able to fix the doubled linked list so this is bypassed `victim->bk->fd == victim` or not `victim->fd == av (arena)`, which means that the address where we want to write must have the address of the fake chunk in its `fd` position and that the fake chunk `fd` is pointing to the arena.
> [!CAUTION]
> Зверніть увагу, що ця атака пошкоджує unsorted bin (отже, маленькі та великі також). Тому ми можемо лише **використовувати алокації з fast bin зараз** (більш складна програма може виконувати інші алокації та аварійно завершитися), і щоб викликати це, ми повинні **алокувати той же розмір, інакше програма аварійно завершиться.**
> Зверніть увагу, що ця атака корумпує unsorted bin (а отже і small та large). Тому тепер ми можемо **використовувати лише алокації з fast bin** (складніша програма може робити інші алокації і впасти), і щоб тригернути це ми повинні **розподілити пам'ять того самого розміру, інакше програма впаде.**
>
> Зверніть увагу, що перезапис **`global_max_fast`** може допомогти в цьому випадку, довіряючи, що fast bin зможе впоратися з усіма іншими алокаціями, поки експлуатація не буде завершена.
> Зверніть увагу, що перезапис **`global_max_fast`** може допомогти в цьому випадку, довіряючи, що fast bin зможе обслужити всі інші алокації, поки експлойт не буде завершений.
Код від [**guyinatuxedo**](https://guyinatuxedo.github.io/31-unsortedbin_attack/unsorted_explanation/index.html) дуже добре пояснює це, хоча якщо ви модифікуєте malloc для алокації пам'яті достатнього розміру, щоб не закінчити в Tcache, ви можете побачити, що раніше згадана помилка з'являється, запобігаючи цій техніці: **`malloc(): unsorted double linked list corrupted`**
Код від [**guyinatuxedo**](https://guyinatuxedo.github.io/31-unsortedbin_attack/unsorted_explanation/index.html) добре це пояснює, хоча якщо змінити malloc-и так, щоб вони виділяли пам'ять достатню велику щоб не потрапляти в Tcache, можна побачити раніше згадану помилку, яка заважає цій техніці: **`malloc(): unsorted double linked list corrupted`**
### Як фактично відбувається запис
- The unsorted-bin write is triggered on `free` when the freed chunk is inserted at the head of the unsorted list.
- During insertion, the allocator performs `bck = unsorted_chunks(av); fwd = bck->fd; victim->bk = bck; victim->fd = fwd; fwd->bk = victim; bck->fd = victim;`
- If you can set `victim->bk` to `(mchunkptr)(TARGET - 0x10)` before calling `free(victim)`, the final statement will perform the write: `*(TARGET) = victim`.
- Later, when the allocator processes the unsorted bin, integrity checks will verify (among other things) that `bck->fd == victim` and `victim->fd == unsorted_chunks(av)` before unlinking. Because the insertion already wrote `victim` into `bck->fd` (our `TARGET`), these checks can be satisfied if the write succeeded.
## Сучасні обмеження (glibc ≥ 2.33)
Щоб надійно використовувати unsortedbin writes у сучасних glibc:
- Tcache interference: для розмірів, які потрапляють у tcache, frees перенаправляються туди і не торкаються unsorted bin. Варіанти:
- робити запити з розмірами > MAX_TCACHE_SIZE (≥ 0x410 на 64біт за замовчуванням), або
- заповнити відповідний tcache bin (7 записів), щоб додаткові frees діставалися до глобальних бінів, або
- якщо середовище контрольоване, відключити tcache (наприклад, GLIBC_TUNABLES glibc.malloc.tcache_count=0).
- Integrity checks on the unsorted list: на наступному шляху алокації, який перевіряє unsorted bin, glibc перевіряє (спрощено):
- `bck->fd == victim` and `victim->fd == unsorted_chunks(av)`; інакше воно завершується з `malloc(): unsorted double linked list corrupted`.
- Це означає, що адреса, яку ви цілите, має терпляче переносити два записи: спочатку `*(TARGET) = victim` під час free; пізніше, коли чанк буде видалятися, `*(TARGET) = unsorted_chunks(av)` (аллокатор перезаписує `bck->fd` назад на голову біну). Обирайте цілі, де просте примусове встановлення великого ненульового значення корисне.
- Типові стабільні цілі в сучасних експлоїтах
- Стан застосунку або глобальний стан, який трактує «великі» значення як прапори/ліміти.
- Побічні примітиви (наприклад, підготовка для наступного [fast bin attack]({{#ref}}fast-bin-attack.md{{#endref}}) або для переключення на пізніший writewhatwhere).
- Уникайте `__malloc_hook`/`__free_hook` у нових glibc: вони були видалені в 2.34. Уникайте `global_max_fast` на ≥ 2.39 (див. попередню примітку).
- Про `global_max_fast` у нещодавніх glibc
- У glibc 2.39+ `global_max_fast` став 8бітною глобальною змінною. Класичний трюк записати туди heapвказівник більше не працює коректно і, ймовірно, корумпуватиме сусідній стан аллокатора. Краще обирати інші стратегії.
## Minimal exploitation recipe (modern glibc)
Мета: досягти одного довільного запису heapвказівника за довільною адресою, використовуючи примітив вставки в unsortedbin, без аварійного завершення.
- Layout/grooming
- Виділіть A, B, C з розмірами, достатніми, щоб обійти tcache (наприклад, 0x5000). C запобігає консолідації з top chunk.
- Corruption
- Переповнення з A у заголовок B, щоб встановити `B->bk = (mchunkptr)(TARGET - 0x10)`.
- Trigger
- `free(B)`. Під час вставки аллокатор виконує `bck->fd = B`, отже `*(TARGET) = B`.
- Continuation
- Якщо ви плануєте продовжувати алокації й програма використовує unsorted bin, очікуйте, що аллокатор пізніше встановить `*(TARGET) = unsorted_chunks(av)`. Обидва значення зазвичай великі і можуть бути достатніми, щоб змінити семантику розмірів/лімітів у цілях, які лише перевіряють «велике».
Pseudocode skeleton:
```c
// 64-bit glibc 2.352.38 style layout (tcache bypass via large sizes)
void *A = malloc(0x5000);
void *B = malloc(0x5000);
void *C = malloc(0x5000); // guard
// overflow from A into Bs metadata (prev_size/size/.../bk). You must control B->bk.
*(size_t *)((char*)B - 0x8) = (size_t)(TARGET - 0x10); // write fake bk
free(B); // triggers *(TARGET) = B (unsorted-bin insertion write)
```
> [!NOTE]
> • Якщо ви не можете обійти tcache за допомогою size, заповніть tcache bin для обраного size (7 frees) перед тим як звільнити пошкоджений chunk, щоб free перейшов в unsorted.
> • Якщо програма одразу припиняє роботу при наступному виклику allocation через перевірки unsorted-bin, повторно перевірте, що `victim->fd` все ще дорівнює голові біну і що ваш `TARGET` містить точний вказівник на `victim` після першого запису.
## Unsorted Bin Infoleak Attack
Це насправді дуже базова концепція. Частини в unsorted bin будуть мати вказівники. Перша частина в unsorted bin насправді буде мати **`fd`** та **`bk`** посилання **вказуючи на частину основної арени (Glibc)**.\
Отже, якщо ви можете **помістити частину всередину unsorted bin і прочитати її** (використання після звільнення) або **знову алокувати її, не перезаписуючи принаймні 1 з вказівників**, щоб потім **прочитати** її, ви можете отримати **витік інформації Glibc**.
Насправді це дуже базова концепція. Чанки в unsorted bin будуть містити вказівники. Перший chunk в unsorted bin фактично має **`fd`** і **`bk`** посилання, **що вказують на частину main arena (Glibc)**.\
Тому, якщо ви можете **помістити chunk в unsorted bin і прочитати його** (use after free) або **виділити його знову без перезапису принаймні одного з вказівників**, щоб потім **прочитати** його, ви можете отримати **Glibc info leak**.
Схожа [**атака, використана в цьому описі**](https://guyinatuxedo.github.io/33-custom_misc_heap/csaw18_alienVSsamurai/index.html), полягала в зловживанні структурою з 4 частин (A, B, C та D - D лише для запобігання консолідації з верхньою частиною), тому для переповнення нульовим байтом у B було використано, щоб C вказувала, що B не використовується. Також у B дані `prev_size` були модифіковані, тому розмір замість розміру B був A+B.\
Потім C була звільнена і консолідована з A+B (але B все ще використовувалася). Була алокована нова частина розміру A, а потім адреси libc були записані в B, звідки вони були витіковані.
A similar [**attack used in this writeup**](https://guyinatuxedo.github.io/33-custom_misc_heap/csaw18_alienVSsamurai/index.html), was to abuse a 4 chunks structure (A, B, C and D - D is only to prevent consolidation with top chunk) so a null byte overflow in B was used to make C indicate that B was unused. Also, in B the `prev_size` data was modified so the size instead of being the size of B was A+B.\
Then C was deallocated, and consolidated with A+B (but B was still in used). A new chunk of size A was allocated and then the libc leaked addresses was written into B from where they were leaked.
## References & Other examples
- [**https://ctf-wiki.mahaloz.re/pwn/linux/glibc-heap/unsorted_bin_attack/#hitcon-training-lab14-magic-heap**](https://ctf-wiki.mahaloz.re/pwn/linux/glibc-heap/unsorted_bin_attack/#hitcon-training-lab14-magic-heap)
- Мета полягає в тому, щоб перезаписати глобальну змінну значенням, більшим за 4869, щоб було можливим отримати прапор, і PIE не увімкнено.
- Можна генерувати частини довільних розмірів, і є переповнення купи з бажаним розміром.
- Атака починається зі створення 3 частин: chunk0 для зловживання переповненням, chunk1 для переповнення та chunk2, щоб верхня частина не консолідувала попередні.
- Потім chunk1 звільняється, а chunk0 переповнюється, щоб вказівник `bk` частини 1 вказував на: `bk = magic - 0x10`
- Потім chunk3 алокуються з таким же розміром, як chunk1, що викликає атаку unsorted bin і змінює значення глобальної змінної, що робить можливим отримати прапор.
- Мета — перезаписати глобальну змінну значенням більше ніж 4869, щоб отримати flag; PIE не увімкнено.
- Можна згенерувати chunks довільних розмірів і існує heap overflow потрібного розміру.
- Атака починається зі створення 3 чанків: chunk0 для зловживання overflow, chunk1 — який буде переповнений, і chunk2, щоб top chunk не консолідував попередні.
- Потім chunk1 звільняють, і chunk0 переповнюють так, щоб `bk` вказував на: `bk = magic - 0x10`
- Потім виділяють chunk3 того ж розміру, що й chunk1, що спрацьовує unsorted bin attack і змінює значення глобальної змінної, дозволяючи отримати flag.
- [**https://guyinatuxedo.github.io/31-unsortedbin_attack/0ctf16_zerostorage/index.html**](https://guyinatuxedo.github.io/31-unsortedbin_attack/0ctf16_zerostorage/index.html)
- Функція злиття вразлива, оскільки якщо обидва передані індекси однакові, вона перерозподілить їх, а потім звільнить, але повертаючи вказівник на цю звільнену область, яку можна використовувати.
- Отже, **створюються 2 частини**: **chunk0**, яка буде злитою сама з собою, і chunk1, щоб запобігти консолідації з верхньою частиною. Потім **функція злиття викликається з chunk0** двічі, що викличе використання після звільнення.
- Потім **функція `view`** викликається з індексом 2 (який є індексом частини після звільнення), що **викликає витік адреси libc**.
- Оскільки бінарний файл має захисти, щоб лише malloc розміри більші за **`global_max_fast`**, тому жоден fastbin не використовується, буде використана атака unsorted bin для перезапису глобальної змінної `global_max_fast`.
- Потім можна викликати функцію редагування з індексом 2 (вказівник після звільнення) і перезаписати вказівник `bk`, щоб вказувати на `p64(global_max_fast-0x10)`. Потім, створюючи нову частину, буде використана раніше скомпрометована адреса (0x20), що **викличе атаку unsorted bin**, перезаписуючи `global_max_fast`, що є дуже великим значенням, що дозволяє тепер створювати частини в fast bins.
- Тепер виконується **атака fast bin**:
- Перш за все, виявляється, що можливо працювати з fast **частинами розміру 200** в місці **`__free_hook`**:
- Функція merge вразлива, бо якщо передані індекси однакові, вона зробить realloc на ньому, потім free, але поверне вказівник на звільнену область, який можна використовувати.
- Тому створюються **2 чанки**: **chunk0**, який буде merged з самим собою, і chunk1, щоб запобігти консолідації з top chunk. Потім **merge function викликається для chunk0** двічі, що призводить до use after free.
- Потім викликається функція **`view`** з індексом 2 (індекс use after free chunk), яка **leaks a libc address**.
- Оскільки бінар має захисти, що дозволяють malloc лише розміри більші за **`global_max_fast`**, тому fastbin не використовується, застосовується unsorted bin attack для перезапису глобальної змінної `global_max_fast`.
- Потім можна викликати edit з індексом 2 (вказівник use after free) і перезаписати вказівник `bk`, щоб він вказував на `p64(global_max_fast-0x10)`. Створення нового chunk використає попередньо скомпрометовану адресу free (0x20) і спровокує unsorted bin attack, перезаписавши `global_max_fast` на дуже велике значення, що дозволить тепер створювати chunks у fast bins.
- Тепер виконується **fast bin attack**:
- Насамперед з'ясовано, що можна працювати з fast **chunks розміру 200** у розташуванні **`__free_hook`**:
- <pre class="language-c"><code class="lang-c">gef➤ p &__free_hook
$1 = (void (**)(void *, const void *)) 0x7ff1e9e607a8 <__free_hook>
gef➤ x/60gx 0x7ff1e9e607a8 - 0x59
@ -58,16 +115,20 @@ gef➤ x/60gx 0x7ff1e9e607a8 - 0x59
0x7ff1e9e6076f <list_all_lock+15>: 0x0000000000000000 0x0000000000000000
0x7ff1e9e6077f <_IO_stdfile_2_lock+15>: 0x0000000000000000 0x0000000000000000
</code></pre>
- Якщо нам вдасться отримати fast chunk розміру 0x200 у цьому місці, буде можливим перезаписати вказівник функції, яка буде виконана.
- Для цього створюється нова частина розміру `0xfc`, і функція злиття викликається з цим вказівником двічі, таким чином ми отримуємо вказівник на звільнену частину розміру `0xfc*2 = 0x1f8` у fast bin.
- Потім функція редагування викликається в цій частині, щоб змінити адресу **`fd`** цього fast bin, щоб вказувати на попередню функцію **`__free_hook`**.
- Потім створюється частина розміру `0x1f8`, щоб отримати з fast bin попередню непотрібну частину, тому створюється ще одна частина розміру `0x1f8`, щоб отримати fast bin chunk у **`__free_hook`**, який перезаписується адресою функції **`system`**.
- І нарешті, частина, що містить рядок `/bin/sh\x00`, звільняється, викликаючи функцію видалення, що викликає функцію **`__free_hook`**, яка вказує на system з `/bin/sh\x00` як параметром.
- Якщо вдасться отримати fast chunk розміру 0x200 у цьому місці, стане можливим перезаписати вказівник на функцію, яка буде виконана.
- Для цього створюється новий chunk розміру `0xfc` і merge function викликається з тим вказівником двічі, таким чином ми отримуємо вказівник на звільнений chunk розміру `0xfc*2 = 0x1f8` у fast bin.
- Потім у цьому chunk викликається edit, щоб змінити адресу **`fd`** цього fast bin так, щоб вона вказувала на попередню функцію **`__free_hook`**.
- Потім створюється chunk розміру `0x1f8`, щоб вибрати з fast bin попередній марний chunk; після цього створюється ще один chunk розміру `0x1f8`, щоб отримати fast bin chunk у **`__free_hook`**, який перезаписується адресою функції **`system`**.
- І нарешті chunk, що містить рядок `/bin/sh\x00`, звільняється викликом delete, що спричиняє виклик **`__free_hook`**, яка тепер вказує на system з `/bin/sh\x00` як параметром.
- **CTF** [**https://guyinatuxedo.github.io/33-custom_misc_heap/csaw19_traveller/index.html**](https://guyinatuxedo.github.io/33-custom_misc_heap/csaw19_traveller/index.html)
- Ще один приклад зловживання переповненням на 1B для консолідації частин в unsorted bin і отримання витоку інформації libc, а потім виконання атаки fast bin для перезапису malloc hook з адресою одного гаджета.
- Інший приклад використання 1B overflow для консолідації чанків в unsorted bin і отримання libc infoleak, а потім виконання fast bin attack для перезапису malloc hook на адресу one gadget.
- [**Robot Factory. BlackHat MEA CTF 2022**](https://7rocky.github.io/en/ctf/other/blackhat-ctf/robot-factory/)
- Ми можемо алокувати лише частини розміру більше `0x100`.
- Перезаписати `global_max_fast`, використовуючи атаку Unsorted Bin (працює 1/16 разів через ASLR, оскільки нам потрібно модифікувати 12 біт, але ми повинні модифікувати 16 біт).
- Атака Fast Bin для модифікації глобального масиву частин. Це дає примітив довільного читання/запису, що дозволяє модифікувати GOT і вказувати деякі функції на `system`.
- Можна виділяти лише чанки розміру більше `0x100`.
- Перезаписати `global_max_fast` за допомогою Unsorted Bin attack (працює 1/16 разів через ASLR, бо потрібно змінити 12 бітів, але нам потрібно змінити 16).
- Fast Bin attack для модифікації глобального масиву чанків. Це дає примітив довільного читання/запису, що дозволяє змінювати GOT і вказати деяку функцію на `system`.
## References
- Glibc malloc unsorted-bin integrity checks (example in 2.33 source): https://elixir.bootlin.com/glibc/glibc-2.33/source/malloc/malloc.c
- `global_max_fast` and related definitions in modern glibc (2.39): https://elixir.bootlin.com/glibc/glibc-2.39/source/malloc/malloc.c
{{#include ../../banners/hacktricks-training.md}}