Přetečení zásobníku zásobníku - Stack buffer overflow
V softwaru dochází k přetečení zásobníku vyrovnávací paměti nebo přetečení zásobníku zásobníku, když program zapisuje na adresu paměti v zásobníku volání programu mimo zamýšlenou datovou strukturu, což je obvykle vyrovnávací paměť pevné délky . Chyby přetečení vyrovnávací paměti zásobníku jsou způsobeny tím, že program zapíše do vyrovnávací paměti umístěné v zásobníku více dat, než kolik je pro tuto vyrovnávací paměť skutečně přiděleno. To má téměř vždy za následek poškození sousedních dat v zásobníku a v případech, kdy bylo přetečení spuštěno omylem, často způsobí selhání programu nebo nesprávné fungování. Přetečení vyrovnávací paměti zásobníku je typ obecnější programovací poruchy známé jako přetečení vyrovnávací paměti (nebo přetečení vyrovnávací paměti). Přeplnění vyrovnávací paměti v zásobníku pravděpodobně vykolejí spuštění programu než přeplnění vyrovnávací paměti na haldě, protože zásobník obsahuje zpáteční adresy pro všechna aktivní volání funkcí.
Přetečení vyrovnávací paměti zásobníku může být způsobeno záměrně jako součást útoku známého jako rozbíjení zásobníku . Pokud dotčený program běží se zvláštními oprávněními nebo přijímá data od nedůvěryhodných síťových hostitelů (např. Webového serveru ), je chyba potenciální chybou zabezpečení. Pokud je vyrovnávací paměť zásobníku naplněna daty dodanými od nedůvěryhodného uživatele, pak tento uživatel může zásobník poškodit takovým způsobem, aby do spuštěného programu vložil spustitelný kód a převzal kontrolu nad procesem. Jedná se o jednu z nejstarších a spolehlivějších metod, jak útočníci získat neoprávněný přístup k počítači.
Využití přetečení zásobníku vyrovnávací paměti
Kanonickou metodou využití přetečení vyrovnávací paměti založené na zásobníku je přepsat návratovou adresu funkce ukazatelem na data řízená útočníkem (obvykle v samotném zásobníku). To je znázorněno strcpy()na následujícím příkladu:
#include <string.h>
void foo(char *bar)
{
char c[12];
strcpy(c, bar); // no bounds checking
}
int main(int argc, char **argv)
{
foo(argv[1]);
return 0;
}
Tento kód převezme argument z příkazového řádku a zkopíruje jej do proměnné místního zásobníku c. To funguje dobře pro argumenty příkazového řádku menší než 12 znaků (jak vidíte na obrázku B níže). Jakékoli argumenty delší než 11 znaků budou mít za následek poškození zásobníku. (Maximální počet znaků, který je bezpečný, je o jeden menší než velikost vyrovnávací paměti, protože v programovacím jazyce C jsou řetězce zakončeny znakem s nulovým bajtem. Dvanáctimístný vstup tedy vyžaduje uložení třinácti bajtů, následovaný vstupem nulovým bajtem sentinu. Nulový bajt pak skončí přepsáním místa v paměti, které je o jeden bajt za koncem vyrovnávací paměti.)
Program se skládá z foo()různých vstupů:
Všimněte si na obrázku C výše, když je na příkazovém řádku foo()zadán argument větší než 11 bajtů, přepíše data místního zásobníku, ukazatel uloženého rámce a hlavně zpáteční adresu. Když se foo()vrátí, vyskočí zpáteční adresu ze zásobníku a skočí na tuto adresu (tj. Začne vykonávat pokyny z této adresy). Útočník tedy přepsal zpáteční adresu ukazatelem na vyrovnávací paměť zásobníku char c[12], která nyní obsahuje data dodaná útočníkem. Ve skutečném přetečení vyrovnávací paměti zásobníku by řetězec „A“ místo toho byl shellcode vhodný pro platformu a požadovanou funkci. Pokud měl tento program speciální oprávnění (např. Bit SUID nastavený tak, aby běžel jako superuživatel ), pak by útočník mohl tuto chybu zabezpečení použít k získání oprávnění superuživatele na postiženém počítači.
Útočník může také upravit hodnoty vnitřních proměnných, aby využil některé chyby. S tímto příkladem:
#include <string.h>
#include <stdio.h>
void foo(char *bar)
{
float My_Float = 10.5; // Addr = 0x0023FF4C
char c[28]; // Addr = 0x0023FF30
// Will print 10.500000
printf("My Float value = %f\n", My_Float);
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Memory map:
@ : c allocated memory
# : My_Float allocated memory
*c *My_Float
0x0023FF30 0x0023FF4C
| |
@@@@@@@@@@@@@@@@@@@@@@@@@@@@#####
foo("my string is too long !!!!! XXXXX");
memcpy will put 0x1010C042 (little endian) in My_Float value.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
memcpy(c, bar, strlen(bar)); // no bounds checking...
// Will print 96.031372
printf("My Float value = %f\n", My_Float);
}
int main(int argc, char **argv)
{
foo("my string is too long !!!!! \x10\x10\xc0\x42");
return 0;
}
Řada platforem má v implementaci zásobníku volání jemné rozdíly, které mohou ovlivnit způsob, jakým bude fungovat využití přetečení vyrovnávací paměti zásobníku. Některé architektury strojů ukládají návratovou adresu nejvyšší úrovně zásobníku volání do registru. To znamená, že jakákoli přepsaná zpáteční adresa bude použita až při pozdějším odvíjení zásobníku volání. Dalším příkladem detailu specifického pro stroj, který může ovlivnit výběr technik využití, je skutečnost, že většina architektur strojů ve stylu RISC neumožní nezarovnaný přístup do paměti. V kombinaci s pevnou délkou pro strojové operační kódy může toto omezení stroje způsobit, že je téměř nemožné implementovat přechod na ESP (s jedinou výjimkou, když program ve skutečnosti obsahuje nepravděpodobný kód, který má explicitně přeskočit do registru zásobníku).
Hromádky, které rostou
V rámci tématu přetečení vyrovnávací paměti zásobníku je často diskutovanou, ale zřídka viděnou architekturou architektura, ve které se zásobník zvětšuje v opačném směru. Tato změna architektury je často navrhována jako řešení problému s přetečením vyrovnávací paměti zásobníku, protože jakékoli přetečení vyrovnávací paměti zásobníku, ke kterému dochází ve stejném rámci zásobníku, nemůže přepsat ukazatel návratu. Další vyšetřování této nárokované ochrany shledává, že je to přinejlepším naivní řešení. Jakékoli přetečení, ke kterému dojde ve vyrovnávací paměti z předchozího rámce zásobníku, přesto přepíše ukazatel návratu a umožní škodlivé zneužití chyby. Například ve výše uvedeném příkladu nebude ukazatel návratu foopřepsán, protože k přetečení skutečně dojde v rámci zásobníku pro memcpy. Protože však vyrovnávací paměť, která přetéká během volání, je memcpyumístěna v předchozím rámci zásobníku, memcpybude mít ukazatel pro návrat číselně vyšší adresu paměti než vyrovnávací paměť. To znamená, že místo ukazatele návratu za foopřepsání bude přepsán ukazatel návratu za memcpy. Maximálně to znamená, že růst zásobníku v opačném směru změní některé detaily toho, jak lze přetečení zásobníku vyrovnávací paměti zneužít, ale výrazně to nesníží počet zneužitelných chyb.
Ochranná schémata
V průběhu let byla vyvinuta řada schémat integrity řídicího toku , které zabraňují zneužívání přetečení vyrovnávací paměti škodlivého zásobníku. Ty mohou být obvykle rozděleny do tří kategorií:
- Zjistěte, že došlo k přetečení zásobníku, a zabraňte tak přesměrování ukazatele instrukce na škodlivý kód.
- Zabraňte spuštění škodlivého kódu ze zásobníku, aniž byste přímo detekovali přetečení vyrovnávací paměti zásobníku.
- Randomizujte místo v paměti tak, aby nalezení spustitelného kódu bylo nespolehlivé.
Skládejte kanáry
Stack canaries, pojmenovaní pro svou analogii s kanárem v uhelném dole , se používají k detekci přetečení vyrovnávací paměti zásobníku, než může dojít ke spuštění škodlivého kódu. Tato metoda funguje tak, že umístí malé celé číslo, jehož hodnota je náhodně zvolena na začátku programu, do paměti těsně před ukazatel návratu zásobníku. Většina přetečení vyrovnávací paměti přepisuje paměť z nižších na vyšší adresy paměti, takže aby bylo možné přepsat návratový ukazatel (a převzít tak kontrolu nad procesem), musí být také přepsána hodnota kanárek. Tato hodnota je zkontrolována, aby se ujistila, že se nezměnila, než rutina použije ukazatel návratu na zásobníku. Tato technika může výrazně zvýšit obtížnost využití přetečení vyrovnávací paměti zásobníku, protože nutí útočníka získat kontrolu nad ukazatelem instrukce některými netradičními prostředky, jako je poškození jiných důležitých proměnných v zásobníku.
Neproveditelný zásobník
Dalším přístupem, jak zabránit zneužití přetečení vyrovnávací paměti zásobníku, je vynutit zásady paměti v oblasti paměti zásobníku, která neumožňuje spuštění ze zásobníku ( W^X , "Write XOR Execute"). To znamená, že aby mohl útočník spustit shellcode ze zásobníku, musí buď najít způsob, jak deaktivovat ochranu spouštění z paměti, nebo najít způsob, jak dát své užitečné zatížení shellcode do nechráněné oblasti paměti. Tato metoda je nyní stále populárnější, protože je u většiny desktopových procesorů k dispozici hardwarová podpora pro příznak no-execute.
I když tato metoda rozhodně způsobuje, že kanonický přístup k vytěžování přetečení zásobníku není úspěšný, není bez problémů. Za prvé, je běžné najít způsoby, jak ukládat shellcode v nechráněných oblastech paměti, jako je halda, a tak jen velmi málo je potřeba změnit způsob vykořisťování.
I kdyby tomu tak nebylo, existují i jiné způsoby. Nejvíce zatracující je takzvaná metoda návratu do libc pro vytváření shellcode. V tomto útoku zlomyslná užitečná zátěž načte zásobník nikoli pomocí shellcode, ale se správným zásobníkem hovorů, takže provádění bude vektorováno do řetězce standardních volání knihoven, obvykle s efektem deaktivace ochrany spouštění paměti a umožnění běžného spuštění kódu shellu. To funguje, protože spuštění ve skutečnosti nikdy nevektoruje samotný zásobník.
Varianta return-to-libc je návratově orientované programování (ROP), které nastavuje řadu návratových adres, z nichž každá vykonává malou sekvenci instrukcí stroje cherry-vybral v rámci stávajícího programového kódu nebo systémových knihoven, sekvence, která končí návratem. Tyto takzvané miniaplikace provádějí před návratem jednoduchou manipulaci s registrem nebo podobné provedení a jejich spojením dosáhnete útočníkových cílů. Je dokonce možné použít „návratové“ návratově orientované programování využíváním instrukcí nebo skupin instrukcí, které se chovají podobně jako návratová instrukce.
Randomizace
Namísto oddělení kódu od dat je další technikou zmírnění zavádění randomizace do paměťového prostoru provádějícího programu. Vzhledem k tomu, že útočník potřebuje určit, kde se nachází spustitelný kód, který lze použít, je k dispozici buď spustitelné užitečné zatížení (se spustitelným zásobníkem), nebo je vytvořeno pomocí opětovného použití kódu, například v ret2libc nebo programování orientovaném na návrat (ROP). Randomizace rozložení paměti jako koncept zabrání útočníkovi vědět, kde je jakýkoli kód. Implementace však obvykle nerozhodnou všechno; samotný spustitelný soubor je obvykle načten na pevnou adresu, a proto i když je ASLR (randomizace rozložení adresního prostoru) kombinována s nevykonatelným zásobníkem, může útočník použít tuto pevnou oblast paměti. Všechny programy by proto měly být kompilovány s PIE (spustitelné soubory nezávislé na poloze), aby i tato oblast paměti byla randomizována. Entropie randomizace se liší od implementace k implementaci a dostatečně nízká entropie může být sama o sobě problémem, pokud jde o hrubé vynucení randomizovaného paměťového prostoru.
Pozoruhodné příklady
- Červ Morris v roce 1988 rozšířil z části tím, že využívá přetečení zásobníku vyrovnávací paměti v systému Unix prst server. [1]
- Červ Slammer v rozpětí 2003 tím, že využívá přetečení zásobníku vyrovnávací paměti v Microsoft SQL serveru očím. [2]
- Blaster Červ v rozpětí 2003 tím, že využívá přetečení zásobníku vyrovnávací paměti v Microsoft DCOM služby.
- Witty červ v roce 2004 rozšířil tím, že využívá přetečení zásobníku vyrovnávací paměti v Internet Security Systems BlackICE Desktop Agent. [3]
- Existuje několik příkladů Wii, které umožňují spuštění libovolného kódu v neupraveném systému. „Twilight hack“, který zahrnuje pojmenování dlouhého jména koně hlavní postavy v The Legend of Zelda: Twilight Princess , a „Smash Stack“ pro Super Smash Bros. Brawl, který zahrnuje použití SD karty k načtení speciálně připraveného souboru do editor úrovně ve hře. Ačkoli lze oba použít ke spuštění libovolného kódu, druhý z nich se často používá k opětovnému načtení samotného Brawlu s použitými úpravami .
Viz také
- Randomizace rozložení adresního prostoru
- Přetečení zásobníku
- Zásobník hovorů
- Zabezpečení počítače
- ExecShield
- Spustitelná ochrana prostoru
- Exploit (zabezpečení počítače)
- Formátovat útok na řetězec
- Přetečení haldy
- Přetečení celého čísla
- Bit NX
- PaX
- Návratově orientované programování
- Zabezpečený Linux
- Přetečení zásobníku
- Porušení úložiště
- Zranitelnost (výpočetní technika)