diff --git a/src/binary-exploitation/libc-heap/unsorted-bin-attack.md b/src/binary-exploitation/libc-heap/unsorted-bin-attack.md index 9d6e21382..82c86edc3 100644 --- a/src/binary-exploitation/libc-heap/unsorted-bin-attack.md +++ b/src/binary-exploitation/libc-heap/unsorted-bin-attack.md @@ -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 8‑bit 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) + +Щоб надійно використовувати unsorted‑bin 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}}) або для переключення на пізніший write‑what‑where). +- Уникайте `__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‑вказівника за довільною адресою, використовуючи примітив вставки в unsorted‑bin, без аварійного завершення. + +- 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.35–2.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 B’s 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`**: -
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 : 0x0000000000000000 0x0000000000000000
0x7ff1e9e6077f <_IO_stdfile_2_lock+15>: 0x0000000000000000 0x0000000000000000
-- Якщо нам вдасться отримати 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}}