Stack (koncept)
Co je to stack?
Stack (zásobník) je oblast paměti používaná pro ukládání dočasných dat při běhu programu. Funguje na principu LIFO (Last In, First Out) – poslední vložená hodnota je první odebraná.
Používá se zejména pro:
- lokální proměnné funkcí
- návratové adresy
- ukládání registrů
Push / Pop
Základní operace nad stackem:
push– vloží hodnotu na stackpop– odebere hodnotu ze stacku
Stack frame
Každé volání funkce vytváří tzv. stack frame – blok paměti obsahující:
- lokální proměnné
- uložený base pointer (
rbp) - návratovou adresu
Důležité registry
Na Linuxu (x86-64):
rsp– stack pointer (ukazuje na vrchol stacku)rbp– base pointer (začátek stack frame)rip– instruction pointer (kam se pokračuje v kódu)
Proč je stack důležitý pro exploitation?
Buffer overflow na stacku může přepsat:
- lokální proměnné
- uložený
rbp - návratovou adresu (return address)
To je klíčové, protože instrukce ret vezme adresu ze stacku a skočí na ni.
De Bruijn sekvence (cyclic pattern)
Pro nalezení přesného offsetu při overflow se používá de Bruijn sekvence (cyclic pattern).
Výhoda:
- žádný podřetězec se neopakuje
- lze přesně určit pozici v bufferu
Generování (pwntools):
$ cyclic 200
aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabe...
Crash a nalezení offsetu
Spustíme program v GDB a pošleme pattern:
gef➤ run
Input: <cyclic pattern>
Po pádu programu:
gef➤ x $rsp
0x7fffffffd708: 0x6161616f
Hodnota 0x6161616f je část našeho patternu.
Zjistíme offset:
$ cyclic -l 0x6161616f
56
Tento výstup znamená že stack offset je 56.
Stack padding
Abychom například změnili návratovou adresu, musíme vyplnit buffer přesně správným množstvím dat:
payload = b"A" * 56 + p64(addr)
A * 56→ paddingp64(addr)→ adresa v paměti
Shrnutí
- stack ukládá data funkcí (proměnné, návratové adresy)
- overflow může přepsat návratovou adresu
retinstrukce použije hodnotu ze stacku jako novýrip- de Bruijn sekvence pomáhá najít přesný offset
- stack padding umožňuje přesně zasáhnout cílová data