mirror of
https://github.com/HackTricks-wiki/hacktricks.git
synced 2025-10-10 18:36:50 +00:00
Translated ['src/binary-exploitation/libc-heap/heap-overflow.md', 'src/b
This commit is contained in:
parent
57943b792b
commit
9462185234
@ -6,28 +6,28 @@
|
||||
|
||||
Heap overflow jest jak [**stack overflow**](../stack-overflow/index.html), ale w stercie. Zasadniczo oznacza to, że pewna przestrzeń została zarezerwowana w stercie do przechowywania danych, a **przechowywane dane były większe niż zarezerwowana przestrzeń.**
|
||||
|
||||
W przypadku stack overflow wiemy, że niektóre rejestry, takie jak wskaźnik instrukcji czy ramka stosu, będą przywracane ze stosu i można to wykorzystać. W przypadku heap overflow **nie ma żadnych wrażliwych informacji przechowywanych domyślnie** w kawałku sterty, który może być przepełniony. Jednak mogą to być wrażliwe informacje lub wskaźniki, więc **krytyczność** tej podatności **zależy** od **tego, jakie dane mogą być nadpisane** i jak atakujący może to wykorzystać.
|
||||
W przypadku stack overflow wiemy, że niektóre rejestry, takie jak wskaźnik instrukcji czy ramka stosu, będą przywracane ze stosu i można to wykorzystać. W przypadku heap overflow **nie ma żadnych wrażliwych informacji przechowywanych domyślnie** w kawałku sterty, który może być przepełniony. Jednak mogą to być wrażliwe informacje lub wskaźniki, więc **krytyczność** tej podatności **zależy** od **tego, jakie dane mogą być nadpisane** i jak napastnik mógłby to wykorzystać.
|
||||
|
||||
> [!TIP]
|
||||
> Aby znaleźć przesunięcia przepełnienia, możesz użyć tych samych wzorców, co w przypadku [**stack overflow**](../stack-overflow/index.html#finding-stack-overflows-offsets).
|
||||
> Aby znaleźć przesunięcia przepełnienia, możesz użyć tych samych wzorców, co w [**stack overflow**](../stack-overflow/index.html#finding-stack-overflows-offsets).
|
||||
|
||||
### Stack Overflows vs Heap Overflows
|
||||
|
||||
W przypadku stack overflow układ i dane, które będą obecne na stosie w momencie, gdy podatność może zostać wyzwolona, są dość wiarygodne. Dzieje się tak, ponieważ stos jest liniowy, zawsze rośnie w kolidującej pamięci, w **konkretnych miejscach działania programu pamięć stosu zazwyczaj przechowuje podobny rodzaj danych** i ma pewną specyficzną strukturę z pewnymi wskaźnikami na końcu części stosu używanej przez każdą funkcję.
|
||||
|
||||
Jednak w przypadku heap overflow używana pamięć nie jest liniowa, ale **alokowane kawałki są zazwyczaj w oddzielnych pozycjach pamięci** (nie jeden obok drugiego) z powodu **koszyków i stref** oddzielających alokacje według rozmiaru oraz dlatego, że **wcześniej zwolniona pamięć jest używana** przed alokowaniem nowych kawałków. **Trudno jest wiedzieć, który obiekt będzie kolidował z tym, który jest podatny** na heap overflow. Dlatego, gdy znajdzie się heap overflow, konieczne jest znalezienie **wiarygodnego sposobu, aby pożądany obiekt był następny w pamięci** od tego, który może być przepełniony.
|
||||
Jednak w przypadku heap overflow używana pamięć nie jest liniowa, ale **alokowane kawałki są zazwyczaj w oddzielnych pozycjach pamięci** (nie jeden obok drugiego) z powodu **koszyków i stref** oddzielających alokacje według rozmiaru oraz dlatego, że **wcześniej zwolniona pamięć jest używana** przed alokowaniem nowych kawałków. Jest **skomplikowane, aby wiedzieć, jaki obiekt będzie kolidował z tym, który jest podatny** na przepełnienie sterty. Dlatego, gdy znajdzie się przepełnienie sterty, należy znaleźć **wiarygodny sposób, aby pożądany obiekt był następny w pamięci** od tego, który może być przepełniony.
|
||||
|
||||
Jedną z technik używanych do tego jest **Heap Grooming**, która jest używana na przykład [**w tym poście**](https://azeria-labs.com/grooming-the-ios-kernel-heap/). W poście wyjaśniono, jak w jądrze iOS, gdy strefa kończy się pamięcią do przechowywania kawałków pamięci, rozszerza ją o stronę jądra, a ta strona jest dzielona na kawałki oczekiwanych rozmiarów, które będą używane w kolejności (do wersji iOS 9.2, potem te kawałki są używane w sposób losowy, aby utrudnić wykorzystanie tych ataków).
|
||||
|
||||
Dlatego w poprzednim poście, w którym występuje heap overflow, aby wymusić kolizję przepełnionego obiektu z obiektem ofiary, kilka **`kallocs` jest wymuszanych przez kilka wątków, aby spróbować zapewnić, że wszystkie wolne kawałki są wypełnione i że nowa strona jest tworzona**.
|
||||
Dlatego w poprzednim poście, w którym występuje przepełnienie sterty, aby wymusić kolizję przepełnionego obiektu z obiektem ofiary, kilka **`kallocs` jest wymuszanych przez kilka wątków, aby spróbować zapewnić, że wszystkie wolne kawałki są wypełnione i że nowa strona jest tworzona**.
|
||||
|
||||
Aby wymusić to wypełnienie obiektami o określonym rozmiarze, **alokacja poza linią związana z portem mach iOS** jest idealnym kandydatem. Poprzez skonstruowanie rozmiaru wiadomości, można dokładnie określić rozmiar alokacji `kalloc`, a gdy odpowiedni port mach zostanie zniszczony, odpowiednia alokacja zostanie natychmiast zwolniona do `kfree`.
|
||||
Aby wymusić to wypełnienie obiektami o określonym rozmiarze, **alokacja poza linią związana z portem mach iOS** jest idealnym kandydatem. Poprzez dostosowanie rozmiaru wiadomości, można dokładnie określić rozmiar alokacji `kalloc`, a gdy odpowiedni port mach zostanie zniszczony, odpowiednia alokacja zostanie natychmiast zwolniona z powrotem do `kfree`.
|
||||
|
||||
Następnie niektóre z tych miejsc mogą być **zwolnione**. Lista wolnych elementów **`kalloc.4096` zwalnia elementy w kolejności ostatni wchodzi, pierwszy wychodzi**, co zasadniczo oznacza, że jeśli niektóre miejsca są zwolnione, a exploit próbuje alokować kilka obiektów ofiar, podczas gdy próbuje alokować obiekt podatny na przepełnienie, prawdopodobne jest, że ten obiekt będzie następował po obiekcie ofiary.
|
||||
Następnie niektóre z tych miejsc mogą być **zwolnione**. Lista zwolnień **`kalloc.4096` zwalnia elementy w kolejności ostatni wchodzi, pierwszy wychodzi**, co zasadniczo oznacza, że jeśli niektóre miejsca są zwolnione, a exploit próbuje alokować kilka obiektów ofiar, podczas gdy próbuje alokować obiekt podatny na przepełnienie, prawdopodobne jest, że ten obiekt będzie następował po obiekcie ofiary.
|
||||
|
||||
### Przykład libc
|
||||
|
||||
[**Na tej stronie**](https://guyinatuxedo.github.io/27-edit_free_chunk/heap_consolidation_explanation/index.html) można znaleźć podstawową emulację heap overflow, która pokazuje, jak nadpisanie bitu prev in use następnego kawałka i pozycji prev size pozwala na **konsolidację używanego kawałka** (sprawiając, że myśli, że jest nieużywany) i **następnie ponowne przydzielenie go**, będąc w stanie nadpisać dane, które są używane w innym wskaźniku.
|
||||
[**Na tej stronie**](https://guyinatuxedo.github.io/27-edit_free_chunk/heap_consolidation_explanation/index.html) można znaleźć podstawową emulację przepełnienia sterty, która pokazuje, jak nadpisując bit prev in use następnego kawałka i pozycję prev size, można **skonsolidować używany kawałek** (sprawiając, że myśli, że jest nieużywany) i **następnie ponownie go alokować**, będąc w stanie nadpisać dane, które są używane w innym wskaźniku.
|
||||
|
||||
Inny przykład z [**protostar heap 0**](https://guyinatuxedo.github.io/24-heap_overflow/protostar_heap0/index.html) pokazuje bardzo podstawowy przykład CTF, w którym **heap overflow** może być wykorzystany do wywołania funkcji zwycięzcy, aby **zdobyć flagę**.
|
||||
|
||||
@ -35,7 +35,7 @@ W przykładzie [**protostar heap 1**](https://guyinatuxedo.github.io/24-heap_ove
|
||||
|
||||
### Przykład ARM64
|
||||
|
||||
Na stronie [https://8ksec.io/arm64-reversing-and-exploitation-part-1-arm-instruction-set-simple-heap-overflow/](https://8ksec.io/arm64-reversing-and-exploitation-part-1-arm-instruction-set-simple-heap-overflow/) można znaleźć przykład heap overflow, w którym polecenie, które ma być wykonane, jest przechowywane w następnym kawałku z przepełnionego kawałka. Tak więc, możliwe jest modyfikowanie wykonywanego polecenia, nadpisując je prostym exploitem, takim jak:
|
||||
Na stronie [https://8ksec.io/arm64-reversing-and-exploitation-part-1-arm-instruction-set-simple-heap-overflow/](https://8ksec.io/arm64-reversing-and-exploitation-part-1-arm-instruction-set-simple-heap-overflow/) można znaleźć przykład przepełnienia sterty, w którym polecenie, które ma być wykonane, jest przechowywane w następnym kawałku z przepełnionego kawałka. Tak więc, możliwe jest modyfikowanie wykonywanego polecenia, nadpisując je prostym exploitem, takim jak:
|
||||
```bash
|
||||
python3 -c 'print("/"*0x400+"/bin/ls\x00")' > hax.txt
|
||||
```
|
||||
@ -45,4 +45,36 @@ python3 -c 'print("/"*0x400+"/bin/ls\x00")' > hax.txt
|
||||
- Wykorzystujemy podatność na przepełnienie całkowite, aby uzyskać przepełnienie sterty.
|
||||
- Korumpujemy wskaźniki do funkcji wewnątrz `struct` przepełnionego kawałka, aby ustawić funkcję taką jak `system` i uzyskać wykonanie kodu.
|
||||
|
||||
### Przykład z rzeczywistego świata: CVE-2025-40597 – Niewłaściwe użycie `__sprintf_chk`
|
||||
|
||||
W oprogramowaniu układowym SonicWall SMA100 w wersji 10.2.1.15 moduł reverse-proxy `mod_httprp.so` alokuje kawałek sterty o **0x80 bajtach** i następnie konkatenatuje kilka ciągów do niego za pomocą `__sprintf_chk`:
|
||||
```c
|
||||
char *buf = calloc(0x80, 1);
|
||||
/* … */
|
||||
__sprintf_chk(buf, /* destination (0x80-byte chunk) */
|
||||
-1, /* <-- size argument !!! */
|
||||
0, /* flags */
|
||||
"%s%s%s%s", /* format */
|
||||
"/", "https://", path, host);
|
||||
```
|
||||
`__sprintf_chk` jest częścią **_FORTIFY_SOURCE**. Gdy otrzymuje **pozytywny** parametr `size`, weryfikuje, czy wynikowy ciąg mieści się w buforze docelowym. Przekazując **`-1` (0xFFFFFFFFFFFFFFFF)**, deweloperzy skutecznie **wyłączyli sprawdzanie granic**, przekształcając wzmocnione wywołanie z powrotem w klasyczne, niebezpieczne `sprintf`.
|
||||
|
||||
Dostarczając zbyt długi nagłówek **`Host:`**, atakujący może **przepełnić 0x80-bajtowy kawałek i nadpisać metadane następnego kawałka sterty** (tcache / fast-bin / small-bin w zależności od alokatora). Awarię można powtórzyć za pomocą:
|
||||
```python
|
||||
import requests, warnings
|
||||
warnings.filterwarnings('ignore')
|
||||
requests.get(
|
||||
'https://TARGET/__api__/',
|
||||
headers={'Host': 'A'*750},
|
||||
verify=False
|
||||
)
|
||||
```
|
||||
Praktyczna eksploatacja wymagałaby **heap grooming** w celu umieszczenia kontrolowanego obiektu tuż po podatnym kawałku, ale przyczyna źródłowa podkreśla dwa ważne wnioski:
|
||||
|
||||
1. **_FORTIFY_SOURCE nie jest złotym środkiem** – niewłaściwe użycie może unieważnić ochronę.
|
||||
2. Zawsze przekazuj **prawidłowy rozmiar bufora** do rodziny `_chk` (lub, co jeszcze lepsze, użyj `snprintf`).
|
||||
|
||||
## References
|
||||
* [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/)
|
||||
|
||||
{{#include ../../banners/hacktricks-training.md}}
|
||||
|
@ -6,9 +6,9 @@
|
||||
|
||||
**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**.
|
||||
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 je nadpisać 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.
|
||||
Luka ta zazwyczaj występuje, ponieważ funkcja **kopiuje na stos więcej bajtów niż przydzielona ilość**, 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.
|
||||
|
||||
@ -27,7 +27,7 @@ Najczęstszym sposobem na znalezienie przepełnień stosu jest podanie bardzo du
|
||||
|
||||
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**, do tego zazwyczaj używa się **sekwencji De Bruijn.** Która dla danego alfabetu o rozmiarze _k_ i podsekwencji o długości _n_ jest **cykliczną sekwencją, w której każda możliwa podsekwencja o długości _n_ pojawia się dokładnie raz** jako ciągła 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.
|
||||
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 nadpisanie.
|
||||
|
||||
Można użyć **pwntools** do tego:
|
||||
```python
|
||||
@ -53,11 +53,11 @@ pattern search $rsp #Search the offset given the content of $rsp
|
||||
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żywania 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 (jak w łatwych wyzwaniach CTF).
|
||||
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 funkcji podatnej, ukryta funkcja została wywołana:
|
||||
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łączony), aby po powrocie z podatnej funkcji, ukryta funkcja została wywołana:
|
||||
|
||||
{{#ref}}
|
||||
ret2win/
|
||||
@ -87,7 +87,7 @@ Przepełnienie nie zawsze będzie miało miejsce w stosie, może również wyst
|
||||
../libc-heap/heap-overflow.md
|
||||
{{#endref}}
|
||||
|
||||
## Rodzaje ochrony
|
||||
## Typy ochrony
|
||||
|
||||
Istnieje kilka zabezpieczeń próbujących zapobiec wykorzystaniu podatności, sprawdź je w:
|
||||
|
||||
@ -95,4 +95,33 @@ Istnieje kilka zabezpieczeń próbujących zapobiec wykorzystaniu podatności, s
|
||||
../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`.
|
||||
|
||||
## References
|
||||
* [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/)
|
||||
|
||||
{{#include ../../banners/hacktricks-training.md}}
|
||||
|
Loading…
x
Reference in New Issue
Block a user