81 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Heap Overflow
{{#include ../../banners/hacktricks-training.md}}
## Basic Information
Переповнення купи - це як [**переповнення стеку**](../stack-overflow/index.html), але в купі. В основному це означає, що деякий простір було зарезервовано в купі для зберігання деяких даних, і **збережені дані були більшими за зарезервований простір.**
У випадку переповнень стеку ми знаємо, що деякі регістри, такі як вказівник інструкцій або стековий фрейм, будуть відновлені зі стеку, і це може бути можливим для зловживання. У випадку переповнень купи **немає жодної чутливої інформації, що зберігається за замовчуванням** в частині купи, яка може бути переповнена. Однак це можуть бути чутливі дані або вказівники, тому **критичність** цієї вразливості **залежить** від **того, які дані можуть бути перезаписані** і як зловмисник може цим зловживати.
> [!TIP]
> Щоб знайти зсуви переповнення, ви можете використовувати ті ж шаблони, що й у [**переповненнях стеку**](../stack-overflow/index.html#finding-stack-overflows-offsets).
### Stack Overflows vs Heap Overflows
У переповненнях стеку розташування та дані, які будуть присутні в стеку в момент, коли вразливість може бути активована, є досить надійними. Це пов'язано з тим, що стек є лінійним, завжди збільшується в зіткненні пам'яті, у **конкретних місцях виконання програми стекова пам'ять зазвичай зберігає подібні дані** і має певну структуру з деякими вказівниками в кінці частини стеку, що використовується кожною функцією.
Однак у випадку переповнення купи використана пам'ять не є лінійною, а **використані частини зазвичай знаходяться в окремих позиціях пам'яті** (не одна біля одної) через **контейнери та зони**, які розділяють алокації за розміром, і через те, що **попередньо звільнена пам'ять використовується** перед алокацією нових частин. Це **ускладнює визначення об'єкта, який буде зіткненням з вразливим** до переповнення купи. Тому, коли виявляється переповнення купи, потрібно знайти **надійний спосіб зробити так, щоб бажаний об'єкт був наступним у пам'яті** після того, що може бути переповнене.
Одна з технік, що використовується для цього, - це **Heap Grooming**, яка використовується, наприклад, [**в цьому пості**](https://azeria-labs.com/grooming-the-ios-kernel-heap/). У пості пояснюється, як у ядрі iOS, коли зона вичерпується пам'яттю для зберігання частин пам'яті, вона розширюється на сторінку ядра, і ця сторінка ділиться на частини очікуваних розмірів, які будуть використовуватися в порядку (до версії iOS 9.2, потім ці частини використовуються випадковим чином, щоб ускладнити експлуатацію цих атак).
Отже, у попередньому пості, де відбувається переповнення купи, щоб примусити переповнений об'єкт зіткнутися з об'єктом жертви, кілька **`kallocs` примушуються кількома потоками, щоб спробувати забезпечити заповненість усіх вільних частин і створення нової сторінки**.
Щоб примусити це заповнення об'єктами певного розміру, **алокація поза лінією, пов'язана з iOS mach port**, є ідеальним кандидатом. Шляхом формування розміру повідомлення можна точно вказати розмір алокації `kalloc`, і коли відповідний mach port знищується, відповідна алокація буде негайно звільнена назад до `kfree`.
Тоді деякі з цих заповнювачів можуть бути **звільнені**. **Список вільних `kalloc.4096` звільняє елементи в порядку останній прийшов - перший вийшов**, що в основному означає, що якщо деякі заповнювачі звільнені, і експлуатація намагається алокувати кілька об'єктів жертви, намагаючись алокувати об'єкт, вразливий до переповнення, ймовірно, що цей об'єкт буде слідувати за об'єктом жертви.
### Example libc
[**На цій сторінці**](https://guyinatuxedo.github.io/27-edit_free_chunk/heap_consolidation_explanation/index.html) можна знайти базову емуляцію переповнення купи, яка показує, як перезаписуючи біт prev in use наступної частини та позицію prev size, можна **консолідувати використану частину** (зробивши її такою, що вона не використовується) і **потім знову алокувати її**, маючи можливість перезаписати дані, які використовуються в іншому вказівнику.
Ще один приклад з [**protostar heap 0**](https://guyinatuxedo.github.io/24-heap_overflow/protostar_heap0/index.html) показує дуже базовий приклад CTF, де **переповнення купи** може бути використано для виклику функції переможця, щоб **отримати прапор**.
У прикладі [**protostar heap 1**](https://guyinatuxedo.github.io/24-heap_overflow/protostar_heap1/index.html) можна побачити, як зловживаючи переповненням буфера, можна **перезаписати в сусідній частині адресу**, куди **будуть записані довільні дані від користувача**.
### Example ARM64
На сторінці [https://8ksec.io/arm64-reversing-and-exploitation-part-1-arm-instruction-set-simple-heap-overflow/](https://8ksec.io/arm64-reversing-and-exploitation-part-1-arm-instruction-set-simple-heap-overflow/) ви можете знайти приклад переповнення купи, де команда, яка буде виконана, зберігається в наступній частині від переповненої частини. Таким чином, можна змінити виконувану команду, перезаписавши її простим експлойтом, таким як:
```bash
python3 -c 'print("/"*0x400+"/bin/ls\x00")' > hax.txt
```
### Інші приклади
- [**Auth-or-out. Hack The Box**](https://7rocky.github.io/en/ctf/htb-challenges/pwn/auth-or-out/)
- Ми використовуємо вразливість цілочисельного переповнення, щоб отримати переповнення купи.
- Ми корумпуємо вказівники на функцію всередині `struct` переповненого блоку, щоб встановити функцію, таку як `system`, і отримати виконання коду.
### Приклад з реального світу: CVE-2025-40597 Неправильне використання `__sprintf_chk`
У прошивці SonicWall SMA100 версії 10.2.1.15 модуль зворотного проксі `mod_httprp.so` виділяє **0x80-байтовий** блок купи, а потім конкатенує кілька рядків у нього за допомогою `__sprintf_chk`:
```c
char *buf = calloc(0x80, 1);
/* … */
__sprintf_chk(buf, /* destination (0x80-byte chunk) */
-1, /* <-- size argument !!! */
0, /* flags */
"%s%s%s%s", /* format */
"/", "https://", path, host);
```
`__sprintf_chk` є частиною **_FORTIFY_SOURCE**. Коли він отримує **позитивний** параметр `size`, він перевіряє, що отриманий рядок поміщається в буфер призначення. Передаючи **`-1` (0xFFFFFFFFFFFFFFFF)**, розробники фактично **відключили перевірку меж**, перетворивши укріплений виклик назад у класичний, небезпечний `sprintf`.
Отже, надання надто довгого **`Host:`** заголовка дозволяє зловмиснику **переповнити 0x80-байтовий шматок і знищити метадані наступного шматка купи** (tcache / fast-bin / small-bin в залежності від аллокатора). Збій можна відтворити за допомогою:
```python
import requests, warnings
warnings.filterwarnings('ignore')
requests.get(
'https://TARGET/__api__/',
headers={'Host': 'A'*750},
verify=False
)
```
Практична експлуатація вимагатиме **heap grooming** для розміщення контрольованого об'єкта безпосередньо після вразливого блоку, але корінна причина підкреслює два важливі висновки:
1. **_FORTIFY_SOURCE не є панацеєю** неправильне використання може знищити захист.
2. Завжди передавайте **правильний розмір буфера** до сімейства `_chk` (або, ще краще, використовуйте `snprintf`).
## References
* [watchTowr Labs Stack Overflows, Heap Overflows and Existential Dread (SonicWall SMA100)](https://labs.watchtowr.com/stack-overflows-heap-overflows-and-existential-dread-sonicwall-sma100-cve-2025-40596-cve-2025-40597-and-cve-2025-40598/)
{{#include ../../banners/hacktricks-training.md}}