5.2 KiB

Stack Overflow

{{#include ../../banners/hacktricks-training.md}}

Czym jest Stack Overflow

Stack overflow to luka, która występuje, gdy program zapisuje więcej danych na stosie, niż jest w stanie pomieścić. 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 je nadpisać i kontrolować przepływ wykonania programu.

Luka ta zazwyczaj pojawia się, 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 & 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:

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ęć Stack Overflow

Najczęstszym sposobem na znalezienie przesunięć stack overflow jest podanie bardzo dużego wejścia z As (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 Stack Overflow, 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_** występuje 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 nadpisanie.

Można użyć pwntools do tego:

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

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ż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 podatności (jak w łatwych wyzwaniach CTF).

Ret2win

W tego typu wyzwaniach CTF, istnieje funkcja wewnątrz binarnego, 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 będzie wyłączony), aby po powrocie z funkcji podatnej, 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:

{{#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}}

Rodzaje ochrony

Istnieje kilka ochron próbujących zapobiec wykorzystaniu podatności, sprawdź je w:

{{#ref}} ../common-binary-protections-and-bypasses/ {{#endref}}

{{#include ../../banners/hacktricks-training.md}}