Stack Buffer Overflow – Vlastní Encoder

V tomto článku se zaměříme na problematiku vytváření exploitů vedoucích ke spuštění kódu, přesněji libovolných systémových příkazů, s využitím zranitelnosti Buffer Overflow (BOF). Cílem článku není detailně popisovat principy a technicky útoku BOF, ale nastínit další možný způsob, jak přistupovat k vytváření exploitů, pokud jsme omezeni na velmi malou znakovou sadu z důvodu limitace aplikace.

Prerekvizity:

  • Základní znalost principu útoků typu Buffer Overflow
  • Základní znalost tvorby exploitů
  • Základní znalost stacku, registrů, assembler instrukcí a opcodes

Princip BOF útoku spočívá v podvrhnutí neočekávaného vstupu aplikaci, kdy dojde k přetečení zásobníku, především k přepsání EIP registru, který je zodpovědný za držení ukazatele na následující instrukci, jenž bude v běhu aplikace vykonána. EIP registr tedy řídí tok aplikace. V případě, kdy jsme schopni do zmíněného registru zapsat libovolnou hodnotu (hodnotu v registru přepsat naším vstupem), jsme schopni ovlivnit chování aplikace a v ideálním případě i spouštět libovolné příkazy. Popsané chování také může vést k nedostupnosti služby (a častěji i vede), neboť EIP registr může obsahovat neočekávanou hodnotu, odkazovat na nelegitimní „operaci“ (např. data), ukazovat v paměti mimo vyhrazený prostor pro aplikaci, a tím způsobit access violation, atd.

Ukázka přetečení zásobníku neočekávaně dlouhým vstupem
Ukázka přetečení zásobníku neočekávaně dlouhým vstupem

V průběhu času byly vyvinuty a implementovány ochranné techniky a opatření k detekci a obraně proti útokům typu BOF. Mezi nejznámější patří randomizace adresního prostoru (ASLR), označení části paměti za nespustitelnou (DEP), strukturované výjimky a její odvozeniny (SEH, SEHOP, SafeSEH) a další. Způsoby, jak ochrany obcházet jsou veřejně dostupné a bylo již napsáno spousty kvalitních článků a tutoriálů na toto téma, např. od Corelan team (https://www.corelan.be/index.php/articles/) nebo Fuzzy security (https://www.fuzzysecurity.com/tutorials.html).

My se podíváme na případ trochu jiné ochrany. Nejedná se úplně o ochranu v pravém slova smyslu, spíše o limitaci ze strany aplikace, co se týče povolených znaků, které aplikace akceptuje. Běžnými nepovolenými znaky, tzv. bad chars, při načítání vstupu jsou: null byte (\x00) – standardně považováno za konec vstupu, line feed (\x0A) a carriage return (\x0D) – ASCII znaky pro „enter“, tudíž opět konec vstupu. Některé aplikace mohou odmítat i další znaky, např. speciální znaky (\x2f = „/“, \x3c = „<“, …), netisknutelné znaky, nebo také určité znaky jsou chováním a povahou aplikace zaměněny za jiné. V případě použití některého z bad chars při vytváření exploitu nedojde k očekávanému výsledku a aplikace s největší pravděpodobností skončí v nekonzistentním stavu, což způsobí její nedostupnost / ukončení.

V další části pomineme restrikce pro nalezení potřebné vhodné adresy pro přepsání EIP registru – pro ilustraci přepokládejme, že taková adresa existuje a neobsahuje žádný znak z bad chars – a dále se zaměříme jen na vytváření payloadu (části exploitu) zajišťující spuštění kódu. K vygenerování takového paylodu lze použít volně dostupných nástrojů, např. „msfvenom“ (součást Metasploit Frameworku). Nejjednodušším způsobem, jak omezení ohledně nepovolených znaků vyřešit, je při generování payloadu instruovat daný nástroj, aby se ve výsledném výstupu bad chars nevyskytovaly. K tomu může též pomoci i někte000rý z dostupných encoderů – v našem případě obecně považovaný za nejlepší „shikata_ga_nai“. Na obrázku níže je vyobrazen příklad použití nástroje msfvenom k vygenerování payloadu, který v operačním systému Windows spustí kalkulačku:

Vygenerování payloadu spouštějící kalkulačku, který neobsahuje nepovolené znaky \x00\x0a\x0d
Vygenerování payloadu spouštějící kalkulačku, který neobsahuje nepovolené znaky \x00\x0a\x0d

Pokud je množina povolených znaků dostatečně velká, není obtížné požadovaný payload vygenerovat. Méně přívětivá situace nastává v případě, kdy počet bad chars je tak velký, že payload již není možné vytvořit. V některých situacích může tento problém vyřešit zvýšení počtu iterací encoderu – při opakovaném ecodování výstupu existuje šance, že se ve výsledném payloadu nepovolený znak neobjeví. Co ale dělat v případě, kdy nepovolených znaků je tolik, že payload není možné vytvořit ani po 10, nebo třeba 100 iteracích? V příkladu níže je výčet nepovolených znaků následovný:


\x00\x0a\x0d\x21\x22\x23\x24\x26\x27\x28\x2a\x2b\x2c\x2e\x2f\x30\x32\x33\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f\x40\x41\x42\x43\x45\x46\x47\x48\x49\x4b\x4c\x4d\x4f\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5e\x60\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f\x70\x72\x73\x74\x76\x77\x79\x7a\x7b\x7c\x7d\x7e\x7f


Velká množina bad chars, která neumožňuje vygenerovat payload
Velká množina bad chars, která neumožňuje vygenerovat payload

Custom Encoder

Elegantní řešení může být vytvoření a použití vlastního „encoderu“ s využitím jen omezené sady znaků / instrukcí. Cílem je získat výsledný payload po dekódování (v paměti) obsahující bad chars pomocí základních matematických operací, jako je např. XOR, AND, SUB, ADD atd. (skládající se jen z povolených znaků). A jak tedy vlastní encoder funguje?

Stručný postup je následovný:

  1. Vynulování registru
  2. Uložení relativní adresy (ESP registr)
  3. Zarovnání zásobníku
  4. Encodovaný payload
    • Rozdělení payloadu na sady po 4 bytech
    • Reverzní pořadí
    • Vynulování registru
    • Základní matematické operace
    • Dosažení požadované hodnoty
    • Zapsání na zásobník

„V této ukázce bude pro všechny výpočty použit EAX registr (není podmínkou, lze využít i jiný registr).“

1) Nejprve EAX vynulujeme, můžeme použít např. operaci ADD s vhodně zvolenými hodnotami:

img4.PNG

Pro snadnější představu, proč byly zrovna vybrány tyto hodnoty zobrazíme čísla v binárním zápisu:

img5.PNG

Po provedení logické operace AND bude výsledek vždy nulový, i když EAX původně nabýval libovolnou hodnotu.

2) Pro výpočet offsetu k zarovnání zásobníku, je potřeba získat aktuální relativní adresu. Toho lze dosáhnout jednoduchými dvěma příkazy:

img6.PNG

Nejprve je hodnota ESP (aktuální adresa zásobníku) nahrána na zásobník a následně uložena do EAX registru.

3) V této fázi je nutné provést zarovnání zásobníku na pozici, kam se bude vkládat dékodovaný payload. Jelikož se provádí zápis v reverzním pořadí, musíme najít pozici pro poslední 4 byty, tudíž předem odhadnout velikost výsledného (encodovaného) payloadu, která ale není v tuto chvíli známa. Encodovaný payload je přibližně 5 – 6x větší než payload původní. V našem ukázkovém příkladě přepokládejme následující konfiguraci:

img7.PNG

Naším cílem je za pomocí základních matematických operaci, jako např. ADD nebo SUB skládající se pouze z povolených znaků získat výslednou hodnotu 0018F4FB a tu uložit do registru ESP. Jak nalézt potřebné hodnoty, které je potřeba od 0 odečíst k docílení požadovaného výsledku je detailněji popsáno níže. Z principu, jakým zásobník funguje je hledaná hodnota v obráceném pořadí.

img8.PNG

4) V tuto chvíli máme zásobník připravený a můžeme začít vytvářet payload s využitím vlastního encoderu. Pro ukázku řekněme, že chceme použít vlastní encoder na payload egghuntera (krátký kód, který prohledává adresní prostor a hledá „egg“ – krátký řetězec označující začátek většího payloadu):

img9.PNG

Původní 32 bytový payload egghuntera rozdělíme do 8 sad po 4 bytech. Vzhledem k reverznímu pořadí se bude nejprve encodovat poslední 4 byty, tzn. „\xE7\xFF\xE7\x75“, které se budeme snažit získat (po dekódovaní) v registru EAX, obdobně jako v bodu 3). Jinými slovy, provedeme opět základní matematické operace takové, aby výsledná hodnota EAX registru byla rovna „\xE7\xFF\xE7\x75. Nejprve EAX vynulujeme a následně k výpočtu využijeme následujícího matematického vztahu:

img10.PNG

Naší snahou je tedy nalézt taková čísla (jedno, dvě, tři, …), jejichž celková hodnota k odečtení činí \x1800188B a jsou tvořeny pouze z povolených znaků. Pro lepší pochopení lze říct i předchozí větu obráceně, hledáme taková čísla, jejichž součet je roven \x1800188B (pro připomenutí, vzhledem k vlastnostem a fungování zásobníku je hledaná hodnota opět v reverzním pořadí).

img11.PNG
Decodování posledních 4 bytů egghunter payloadu
Decodování posledních 4 bytů egghunter payloadu

Na obrázku výše je vyobrazeno:

  • V červeném rámečku dochází k zarovnání zásobníků – pro zachování přehlednosti byl zásobník posunut na hodnotu 0018EE90
  • V oranžovém rámečku probíhá decodování paylodu – na hodnotu \xE7\xFF\xE7\x75
  • Ve žlutém rámečku dochází k zápisu původního payloadu na zásobník

Tento postup se následně opakuje pro zbylých 7 sad (po 4 bytech). Jakmile je celý payload egghuntera zakódovaný vlastním encoderem, v době běhu exploitu bude v paměti dekódován na původní 32 bytový payload a spuštěn, čímž dojde k úspěšnému obejití restrikce na nepovolené znaky.

Výsledný encodovaný payload egghutera může vypadat např. následovně (158 bytů vs původních 32 bytů):

img13.PNG

V článku jsme si názorně představili způsob, jak může zkušenější nebo vytrvalejší útočník vytvořit funkční exploit jen s využitím velmi malé sady znaků. Potřebné znaky se liší pro konkrétní požadovaný payload, v případě egghuntera je možné encodovaný payload vytvořit za pomoci pouze 12 znaků.