Zadanie č.4

Nasledujúce zadanie je pokračovaním predchádzajúceho. Zatiaľ čo v minulom zadaní ste mohli škodlivý kód injektovať a následne spustiť priamo na zásobníku, v tomto zadaní už táto možnosť nebude. Budete totiž pracovať už so zapnutou NX ochranou. Účelom tejto ochrany je zabrániť operačnému systému aby mohla v pamäti existovať stránka (v jednom čase), ktorá bude mať oprávnenia na zápis a spustenie, t.j. Write a eXecute. Stále môžeme v prípade zraniteľnosti prepisovať dáta na zásobníku, avšak ak sa dostane programové počítadlo na niektorú z adries na zásobníku, program spadne. Ak už budete mať spustený server s ochranou, môžete vyskúšať spustiť exploit z predošlého zadania a pozorovať čo sa stane. Cieľom zadania bude, rovnako ako v predchádzajúcom zadaní, spustiť reverse shell na kompromitovanom počítači, avšak už so zapnutou NX ochranou.

Príprava prostredia

Server a prostredie na vývoj exploitu ostáva rovnaké ako v predchádzajúcom zadaní. Môžete dokonca využiť tú istú zraniteľnosť (ak to bude možné). Dôležité je spúšťať HTTP server s konfiguračným súborom, ktorý zabezpečí zapnutie ochrany (taktiež netreba zabudnúť vypnúť ASLR):

./fbankld fbank-nxstack.conf

V tomto zadaní však budeme potrebovať jeden nový nástroj, ktorý slúži na vyhľadávanie gadgetov(viď. ďalej) v binárnom súbore:

  1. Ropper; jednoduchý nástroj, ktorý je schopný vyhľadávať gadgety na základe regulárneho výrazu, konštruovať priamo reťazce gadgetov a pod. Návod na inštaláciu možno nájsť na githube.

Návratovo orientované programovanie

Technika, pomocou ktorej možno obísť ochranu NX sa nazýva návratovo orientované programovanie alebo ROP (Return Oriented Programming). Je nutné si uvedomiť že síce v tomto prípade už nevieme do procesu injektovať cudzí škodlivý kód, ktorý nie je súčasťou pôvodnej aplikácie, avšak môžeme využiť časti kódu, ktoré sa v aplikácii už nachádzajú. Keď sa zamyslíme nad tým, ako vyzerá taký program v pamäti, zistíme že sa jedná o postupnosť inštrukcii, kde každá sa nachádza na jedinečnej adrese a vykonáva určitú činnosť. To akú funkcionalitu vykonáva istá množina inštrukcií závisí od toho v akom poradí sa vykonávajú. Väčšinou sa vykonáva jedna za druhou, pokiaľ nepríde k podmienenému/nepodmienenému skoku, kde programové počítadlo skočí na nejakú adresu v pamäti a na nej znova začne vykonávať postupne inštrukcie až pokým znova nepríde ku skoku. Toto je funkcionalita, ktorú navrhol programátor pri vývoji aplikácie. Ak však máme ako útočníci pod kontrolou tok programu (napr. prepísaním návratovej adresy), vieme ovplyvniť aká postupnosť inštrukcií sa vykoná jednoduchým prepísaním návratovej adresy na adresu, kde sa tieto inštrukcie nachádzajú. V minulom zadaní ste pravdepodobne prepísali návratovú adresu tak, aby ukazovala na shellcode na zásobníku. V tomto prípade však prepíšeme návratovú adresu na adresu nejakej konkrétnej inštrukcie, ktorá sa nachádza v sekcii s kódom a je teda spustiteľná. Takým spôsobom vieme spustiť nami zvolenú inštrukciu a vykonať tak postupnosť inštrukcii, ktorú pôvodne programátor nezamýšľal a neporušíme tak NX ochranu. Hlavnou ideou je teda skonštruovať škodlivú funkcionalitu z častí kódu, ktoré sa v pôvodnom programe už nachádzajú a teda ju nemusíme injektovať. Otázkou však stále ostáva, ako prinútiť program aby vykonal sériu nami zvolených inštrukcií, ktoré sa môžu nachádzať na rôznych miestach v pamäti, v nami zvolenom poradí. Reťaziť inštrukcie vieme pomocou tzv. gadgetov. Ako gadget označujeme ľubovoľne dlhú množinu inštrukcií, kde posledná inštrukcia je RET. Príklady gadgetov:

Povedzme že chceme vykonať sériu gadgetov: A, B a C. Teda chceme inkrementovať hodnotu v registri RAX (pôvodná hodnota nás nezaujíma), následne chceme načítať do RDI nami zvolenú hodnotu a nakoniec vykonať NOP operáciu, vo forme samotnej RET inštrukcie.  Zopakujme si čo robí inštrukcia RET a prečo je taká dôležitá v ROP. Inštrukcia načítava vždy hodnotu na vrchu zásobníka do registra RIP (programové počítadlo) a presunie vykonávanie programu na adresu, ktorú prečíta z RIP (po načítaní hodnoty z vrchu zásobníka sa samozrejme táto hodnota odstráni a novým vrchom zásobníka sa stane ďalšia hodnota v poradí). Vďaka tejto inštrukcii vieme reťaziť gadgety nachádzajúce sa na rôznych miestach v programe. Exploit, ktorý by vykonával vyššie uvedenú funkcionalitu by mohol vyzerať následovne (pozn. prvá hodnota po postupnosti znakov „A“ prepisuje návratovú adresu funkcie, ktorá obsahuje zraniteľnosť):

„AAAAAAAAAAAAAAA“ + adresa gadgetu A + adresa gadgetu B + hodnota do RDI + adresa gadgetu C

adresa gadgetu A prepíše návratovú adresu funkcie. To znamená, že keď funkcia skončí, skočí namiesto pôvodného miesta, ktoré volalo funkciu na adresu gadgetu A. Vykoná sa inštrukcia INC RAX a následne RET. Čo spraví inštrukcia RET? Načíta hodnotu z vrchu zásobníka a presunie vykonávanie programu na toto miesto. Na vrchu zásobníka sa v tomto okamihu nachádza adresa gadgetu B, čo znamená že sa RIP sa nastaví na inštrukciu POP RDI. Táto inštrukcia načíta do registra RDI hodnotu na vrchu zásobníka v tom momente, t.j. hodnota do RDI. Nasledujúca inštrukcia RET načíta znova hodnotu na vrchu zásobníka, t.j. adresa gadgetu C a na toto miesto sa presunie vykonávanie programu. Takýmto spôsobom vieme reťaziť rôzne časti programu.

Ako postupovať

Pred začatím vývoja znova odporúčam naštudovať si nasledujúci tutoriál, ktorý je pokračovaním tutoriálu z predchádzajúceho zadania a demonštruje jednoduchý exploit. Tutoriál demonštruje niekoľko kľúčových vecí, ktoré môžete využiť v riešení zadania. Keďže cieľom je spustiť reverse shell, skonštruovanie takej postupnosti gadgetov by bolo príliš komplikované (aj keď nie nemožné). Tu môžeme využiť fakt že reverse shell môžeme spustiť v príkazovom riadku a tak nám stačí pomocou gadgetov nasimulovať volanie knižničnej funkcie system(), ktorá spúšťa príkazy. Jedná sa o tzv. ret2libc útok. V takom prípade stačí jeden gadget na naplnenie vstupného argumentu a jeden na zavolanie funkcie system() (taký gadget už samozrejme nepotrebuje byť ukončený inštrukciou RET). Viac o útoku a registroch, ktoré sa používajú na vstupné argumenty sa možno dočítať vo vyššie spomínanom tutoriáli. Zaujímavou úlohou, ktorú treba v zadaní riešiť je taktiež otázka ako dostať príkaz na pripojenie k shellu vo forme reťazca, do programu. Prvý nápad, ktorý vás môže pochopiteľne napadnúť je zapísať reťazec na zásobník a následne sa naňho odkazovať. Z minulého zadania však viete, že zásobník sa zvykne posúvať v závislosti od premenných prostredia. V prípade kódu to nevadí, keďže vieme použiť sled NOP inštrukcií a zabezpečiť tak aby sa programové počítadlo postupne „zošmyklo“ na shell kód. Takéto niečo v prípade reťazca použiť nemôžeme. Aby reťazec korektne fungoval ako vstupný parameter, musíme vedieť presnú adresu a taktiež musí byť ukončený nulovým bajtom. Z toho dôvodu si odporúčam znova prezrieť zdrojové kódy a nájsť nejaké miesto v pamäti kam sa kopírujú naše dáta a vieme s úplnou presnosťou identifikovať ich adresu.

Dokumentácia

Pri zadaní sa bude okrem funkčnosti exploitu primárne hodnotiť kvalita dokumentácie. V dokumentácii je nutné popísať nasledujúce body:

  1. Detailný popis metodiky akou ste vyvíjali exploit.
  2. Zhodnotenie funkčnosti exploitu. Ak exploit funguje iba v špecifických prípadoch, treba uviesť dôvody prečo tomu tak je.

Okrem dokumentácie treba odovzdať aj skript s exploitom.

Deadline

27.4. 2020 15:00