mirror of
https://github.com/HackTricks-wiki/hacktricks.git
synced 2025-10-10 18:36:50 +00:00
127 lines
10 KiB
Markdown
127 lines
10 KiB
Markdown
# Stack Overflow
|
||
|
||
{{#include ../../banners/hacktricks-training.md}}
|
||
|
||
## Що таке Stack Overflow
|
||
|
||
**Stack overflow** - це вразливість, яка виникає, коли програма записує більше даних у стек, ніж йому виділено. Ці надмірні дані **перезаписують сусідній простір пам'яті**, що призводить до пошкодження дійсних даних, порушення контролю потоку виконання та потенційного виконання шкідливого коду. Ця проблема часто виникає через використання небезпечних функцій, які не виконують перевірку меж на вхідних даних.
|
||
|
||
Основна проблема цього перезапису полягає в тому, що **збережений вказівник інструкцій (EIP/RIP)** та **збережений базовий вказівник (EBP/RBP)** для повернення до попередньої функції **зберігаються в стеці**. Тому зловмисник зможе перезаписати їх і **контролювати потік виконання програми**.
|
||
|
||
Вразливість зазвичай виникає, оскільки функція **копіює в стек більше байтів, ніж виділено для неї**, тим самим здатна перезаписати інші частини стека.
|
||
|
||
Деякі загальні функції, вразливі до цього, це: **`strcpy`, `strcat`, `sprintf`, `gets`**... Також функції, такі як **`fgets`**, **`read`** та **`memcpy`**, які приймають **аргумент довжини**, можуть бути використані в уразливий спосіб, якщо вказана довжина перевищує виділену.
|
||
|
||
Наприклад, наступні функції можуть бути вразливими:
|
||
```c
|
||
void vulnerable() {
|
||
char buffer[128];
|
||
printf("Enter some text: ");
|
||
gets(buffer); // This is where the vulnerability lies
|
||
printf("You entered: %s\n", buffer);
|
||
}
|
||
```
|
||
### Знаходження зсувів стекових переповнень
|
||
|
||
Найпоширеніший спосіб знайти стекові переповнення - це ввести дуже великий вхід з `A`s (наприклад, `python3 -c 'print("A"*1000)'`) і очікувати `Segmentation Fault`, що вказує на те, що **адресу `0x41414141` намагалися отримати доступ**.
|
||
|
||
Більше того, як тільки ви виявите, що існує вразливість стекового переповнення, вам потрібно буде знайти зсув, поки не стане можливим **перезаписати адресу повернення**, для цього зазвичай використовується **послідовність Де Брюйна.** Яка для даного алфавіту розміру _k_ і підпослідовностей довжини _n_ є **циклічною послідовністю, в якій кожна можлива підпослідовність довжини _n_ з'являється точно один раз** як безперервна підпослідовність.
|
||
|
||
Таким чином, замість того, щоб вручну з'ясовувати, який зсув потрібен для контролю EIP, можна використовувати в якості заповнювача одну з цих послідовностей, а потім знайти зсув байтів, які закінчилися перезаписом.
|
||
|
||
Для цього можна використовувати **pwntools**:
|
||
```python
|
||
from pwn import *
|
||
|
||
# Generate a De Bruijn sequence of length 1000 with an alphabet size of 256 (byte values)
|
||
pattern = cyclic(1000)
|
||
|
||
# This is an example value that you'd have found in the EIP/IP register upon crash
|
||
eip_value = p32(0x6161616c)
|
||
offset = cyclic_find(eip_value) # Finds the offset of the sequence in the De Bruijn pattern
|
||
print(f"The offset is: {offset}")
|
||
```
|
||
або **GEF**:
|
||
```bash
|
||
#Patterns
|
||
pattern create 200 #Generate length 200 pattern
|
||
pattern search "avaaawaa" #Search for the offset of that substring
|
||
pattern search $rsp #Search the offset given the content of $rsp
|
||
```
|
||
## Використання переповнень стеку
|
||
|
||
Під час переповнення (якщо розмір переповнення достатньо великий) ви зможете **перезаписати** значення локальних змінних всередині стеку, поки не досягнете збереженого **EBP/RBP та EIP/RIP (або навіть більше)**.\
|
||
Найпоширеніший спосіб зловживання цим типом вразливості - це **модифікація адреси повернення**, щоб, коли функція закінчується, **управління буде перенаправлено туди, куди вказав користувач** в цьому вказівнику.
|
||
|
||
Однак у інших сценаріях просто **перезапис деяких значень змінних у стеку** може бути достатньо для експлуатації (як у простих CTF викликах).
|
||
|
||
### Ret2win
|
||
|
||
У цьому типі CTF викликів є **функція** **всередині** бінарного файлу, яка **ніколи не викликається** і яку **вам потрібно викликати, щоб виграти**. Для цих викликів вам просто потрібно знайти **зсув для перезапису адреси повернення** та **знайти адресу функції**, яку потрібно викликати (зазвичай [**ASLR**](../common-binary-protections-and-bypasses/aslr/index.html) буде вимкнено), щоб, коли вразлива функція повертається, прихована функція буде викликана:
|
||
|
||
{{#ref}}
|
||
ret2win/
|
||
{{#endref}}
|
||
|
||
### Stack Shellcode
|
||
|
||
У цьому сценарії зловмисник може помістити shellcode у стек і зловживати контрольованим EIP/RIP, щоб стрибнути до shellcode і виконати довільний код:
|
||
|
||
{{#ref}}
|
||
stack-shellcode/
|
||
{{#endref}}
|
||
|
||
### ROP & Ret2... техніки
|
||
|
||
Ця техніка є основною основою для обходу основного захисту попередньої техніки: **Не виконавчий стек (NX)**. І вона дозволяє виконувати кілька інших технік (ret2lib, ret2syscall...), які закінчать виконання довільних команд, зловживаючи існуючими інструкціями в бінарному файлі:
|
||
|
||
{{#ref}}
|
||
../rop-return-oriented-programing/
|
||
{{#endref}}
|
||
|
||
## Переповнення купи
|
||
|
||
Переповнення не завжди буде в стеку, воно також може бути в **купі**, наприклад:
|
||
|
||
{{#ref}}
|
||
../libc-heap/heap-overflow.md
|
||
{{#endref}}
|
||
|
||
## Типи захисту
|
||
|
||
Існує кілька захистів, які намагаються запобігти експлуатації вразливостей, перевірте їх у:
|
||
|
||
{{#ref}}
|
||
../common-binary-protections-and-bypasses/
|
||
{{#endref}}
|
||
|
||
### Приклад з реального світу: CVE-2025-40596 (SonicWall SMA100)
|
||
|
||
Добра демонстрація того, чому **`sscanf` ніколи не слід довіряти для парсингу ненадійного вводу**, з'явилася в 2025 році в SonicWall’s SMA100 SSL-VPN пристрої. Вразлива рутина всередині `/usr/src/EasyAccess/bin/httpd` намагається витягти версію та кінцеву точку з будь-якого URI, який починається з `/__api__/`:
|
||
```c
|
||
char version[3];
|
||
char endpoint[0x800] = {0};
|
||
/* simplified proto-type */
|
||
sscanf(uri, "%*[^/]/%2s/%s", version, endpoint);
|
||
```
|
||
1. Перше перетворення (`%2s`) безпечно зберігає **два** байти в `version` (наприклад, `"v1"`).
|
||
2. Друге перетворення (`%s`) **не має специфікатора довжини**, тому `sscanf` буде копіювати **до першого байта NUL**.
|
||
3. Оскільки `endpoint` знаходиться на **стеку** і має **0x800 байт**, надання шляху довше ніж 0x800 байт пошкоджує все, що знаходиться після буфера ‑ включаючи **стековий канар** і **збережену адресу повернення**.
|
||
|
||
Однорядковий доказ концепції достатній, щоб викликати збій **до аутентифікації**:
|
||
```python
|
||
import requests, warnings
|
||
warnings.filterwarnings('ignore')
|
||
url = "https://TARGET/__api__/v1/" + "A"*3000
|
||
requests.get(url, verify=False)
|
||
```
|
||
Навіть якщо стекові канарки переривають процес, зловмисник все ще отримує **Denial-of-Service** примітив (і, з додатковими витоками інформації, можливо, виконання коду). Урок простий:
|
||
|
||
* Завжди вказуйте **максимальну ширину поля** (наприклад, `%511s`).
|
||
* Віддавайте перевагу більш безпечним альтернативам, таким як `snprintf`/`strncpy_s`.
|
||
|
||
## 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}}
|