mirror of
https://github.com/HackTricks-wiki/hacktricks.git
synced 2025-10-10 18:36:50 +00:00
81 lines
11 KiB
Markdown
81 lines
11 KiB
Markdown
# 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}}
|