6. Format String
Format string napad javio se tijekom 2000 godine. Ova tehnika omogućuje da neovlašteni korisnik napada lokalno a tako i udaljeno računalo. Prethodno tome, prelivanje spremnika je bio glavni sigurnosni problem. Mnogi su bili iznenađeni novim propustom, koji je uništio OpenBSD zapise bez lokalne root rupe. Za razliku od preljeva spremnika, ovdje nisu bili prepisani podaci na stogu ili gomili u velikoj količini. Zbog toga je u stdarg ( lista ulaznih argumenata), moguće je prepisati određenu adresu u memoriji. Neke od često korištenih format string funkcija su printf(), sprintf(), fprintf() i syslog(). Te funkcije kao ulazne argumente uzimaju tzv. formatirani string.
Format string je korišten u varijablama kao argumenti funkciji kao na primjer printf(), fprintf() i syslog(). Ovi format stringovi su korišteni kako bi pravilno formatirali podatke kada se ispisuju na izlaz.
Primjer 6.1 prikazuje program s format string ranjivošću
#include <stdio.h> int main(int argc, char **argv) |
Pogledajmo označenu liniju koda u primjer 6.1 ranjivog programa. Vidimo da u funkciji printf() nisu upotrebljeni specifikatori za konverziju. Budući da nema specifikatora za konverziju, argumenti spremnika su interpretirani i ako se nađe bilo koji specifikator za konverziju u spremniku on će biti normalno procesiran. Pogledajmo što će se dogoditi kad pokrenemo ranjivi program:
$ gcc –o example example.c |
Drugi puta kad pokrenemo program, unijeli smo znak za konverziju %x koji vrši konverziju u heksadekatski format. Izlaz je ispis dijela memorije na stogu. Vrijednost 41414141 predstavlja četiri znaka slova 'A' koje smo unijeli kao ulazni argument programu. Te vrijednosti koje smo unijeli kao argument programu su stavljene na stog i korištene su kao argument funkciji printf() u liniji 5 primjera 6.1. Kao što vidimo, može se staviti željena vrijednost na stog, ali kako se može modificirati memorija na taj način ?
Odgovor je u znaku za konverziju %n. Većina format string znakova korištena su kako bi formatirali izlaz nekog određenog tipa podatka kao na primjer cijeli broj, realni broj, znakovni niz, drugi znakovi omogućuju da se iskoriste propusti format string propusta. Specifikator za konverziju %n sprema broj koji se želi ispisat u varijablu.
Primjer 10.3. pokazuje kako se koristi %n:
printf("hello%n\n", &number) |
U prvoj liniji, varijabla number sadrži vrijednost 5 zato što riječ "hello" ima 5 znakova. Specifikator za konverziju %n ne sprema broj znakova zapravo u liniji gdje se nalazi printf(), već se to događa kad je broj ispisan na izlaz. Posljedica toga je, da u drugoj liniji varijabla number sadrži vrijednost 105, znači broj znakova u riječi “hello” plus %100d.
Budući da sad možemo kontrolirati argumente koje prenosimo funkciji, možemo sada ubaciti željenu vrijednost kako bi prepisali određenu adresu uz pomoć specifikatora za konverziju %n. Kako bi stvarno prepisali vrijednost pokazivača na stog, moramo definirati adresu koju želimo prepisat i koristiti znak za konverziju kako bi pisali na željenu adresu. Pokušajmo prepisati vrijednost varijable number. Prvo, znamo kad pozivamo program s ulaznim argumentima s dužinom 10, varijabla se nalazi na stogu na adresi 0xbffffc18. Primjer prepisivanja varijable number:
$ ./example `printf "\x18\xfc\xff\xbf"`%x%x%n |
Vidimo da varijabla number sadrži veličinu koju smo unijeli kao argument pri pokretanju programa. Znamo koristit znak za konverziju %n kako bi pisali na željenu adresu, ali kako zapisati korisnu vrijednost ? Punjenjem spremnika s znakovima kao %100d, možemo specificirat veliku vrijednost bez da je unosimo u program. Ako je potrebno specificirat malu vrijednost, može se ubaciti na adresu koja treba biti prepisana, i zapisati svaki oktet kao 4 bajta adrese odvojeno. Na primjer, ako je potrebno prepisati adresu s vrijednosti 0xbffff710 (-1073744112), možemo je podijeliti u par od dva okteta. Te dvije vrijednosti—0xbfff i 0xf710— sada predstavljaju pozitivne vrijednosti koje je moguće ubaciti koristeći %d tehniku. Zapisujući dvije %n vrijednosti na donju polovicu i gornju polovicu povratne adrese, može se uspješno prepisat povratna adresa. Kada je vješto ubačena i shell kod je stavljen u adresni prostor ranjive aplikacije, bit će uspješno izvršen.
6.1 Što je to Format String Bug?
Format string bug se javlja kad korisnik ubacuje podatke u formatu koje koristi printf() familija funkcija:
printf
fprintf
sprintf
snprintf
vfprintf
vprintf
vsprintf
vsnprintf
ili bilo koju drugu funkciju na operacijskom sustavu koja sadrži kao argument C-stil format specifikaciju, kao na primjer wprintf() funkciju na Windows platformi. Napadač dobavlja broj od specifikatora za konverziju koji nema odgovarajuće argumente na stogu, i vrijednosti sa stoga na njegovom mjestu. To vodi do dobivanja neovlaštenih informacija i izvršavanja zlonamjernog koda.
Primjer 6.2
#include <stdio.h> |
Ako prevedemo ovaj izvorni kod na ovaj način:
cc fmt.c -o fmt |
i pokrenemo program s ovim ulaznim argumentima:
./fmt "%x %x %x %x" |
na ovaj način smo dobili efektivno ovo kao rezultat:
printf( "%x %x %x %x" ); |
Važna stvar, mi smo za ulazne argumente unijeli znakove za konverziju, što će printf() funkcija interpretirati kao string i ispisati na izlaz, ali na izlazu smo dobili kao rezultat sljedeće:
4015c98c 4001526c bffff944 bffff8e8 |
printf() funkcija je ispisala četiri vrijednosti s neke memorijske lokacije. Ali te vrijednosti u biti dolaze s programskog stoga. Ovo na prvi pogled ne izgleda kao problem, ali neovlašteni korisnik može na ovaj način vidjeti sadržaj stoga, Što to znači?
Na ovaj način moguće je vidjeti korisničko ime ili zaporku na sistemu, ali problem ide puno dublje od toga. Ako pokušamo izvršiti veliki broj znakova za konverziju %x kao u ovom primjeru:
./fmt |
rezultat je:
./fmt |
Kao što se vidi, ispisali smo mnogo podataka s stoga, kako idemo prema kraju stringa vidimo i naš ulazni niz u heksadecimalnoj notaciji:
41414141414141 |
ovaj rezultat je slučajan, ali ima smisla ako uzmemo u obzir da je format string zadržan na stogu, segment od 4 okteta od ulaznog niza je ubačen kao "broj" kako bi bio zamijenjen s nizom znakova. Tako smo dobili izlaz u hex formatu. Ako pogledamo u pomoćnim man stranicama za funkciju sprintf(), vidjet ćemo da funkcija sprintf() a isto tako i slične funkcije njoj, imaju mnogo specifikatora konverzije – d, i, o, u i x za cijele brojeve, zatim e, f, g za realne brojeve i c za znakove. Ima još nekih zanimljivih specifikatora konverzije, kao na primjer:
%s – ovaj specifikator tretira se kao pokazivač na znakovni niz.
%n – ovaj specifikator se tretira kao pokazivač na cijeli broj ili neki tip sličan cijelom broju, kao na primjer mali cijeli broj ( engl. short ). Broj znakova će biti ispisan na izlaz od niza na koji pokazuje pokazivač.
Ako upotrijebimo specifikator konverzije %n u ulaznom nizu, broj znakova će biti zapisano na lokaciju određenu ulaznim argumentom:
./fmt "AAAAAAAAAAAAAAAAAAA%n%n%n%n%n%n%n%n%n%n%n" |
Zaključak ovog primjera je da uz pomoć printf() ili slične funkcije uz korištenje %n specifikatora konverzije, moguće je zapisati željenu vrijednost na željenu memorijsku lokaciju, zato što %n očekuje adresu argumenta. To znači da možemo prepisati gotovo bilo koju memorijsku lokaciju. Ako na primjer želimo ispisati 50 znakova, koristit ćemo %050x što će nam dati heksadecimalni ispis 50 znakova.
Koristeći ove činjenice, moguće je izvršiti zlonamjerni kod zbog ovih uvjeta:
- Možemo kontrolirati vrijednosti argumenata, i zapisati broj znakova na izlazu bilo gdje u memoriju.
- Specifikator širine omogućuje nam da napunimo izlaz na neku vrijednost po svojoj želji – naravno do 255 znakova. Možemo prepisati jedan oktet s željenom vrijednosti.
- Možemo to učiniti 4 puta, tako da prepišemo vrijednosti po svom izboru. Prepisivanjem 4 bajta omogućuje napadaču da prepiše adresu. Možemo imati problema pri pisanju adresa sa 00 bajtom zato što 00 u C programskom jeziku označava kraj niza. To se može izbjeći tako da napišemo 2 bajta koja počinju ispred adrese koja nama treba.
- Zato što možemo pogoditi adresu pokazivača na funkciju (pohranjena povratna adresa, C++ vtable) možemo ubaciti niz znakova koje možemo izvršiti.
Najgora je stvar što ima nekoliko zabluda vezanih uz format string napad:
- UNIX sustavi nisu ranjivi na takve napade.
- Nisu striktno bazirani na stogu.
- Mehanizmi za zaštitu stoga nisu učinkoviti protiv njih.
- Mogu biti detektirani s alatima za statičku analizu koda
6.2 Napadanje servisa
Kada se vrši napad na neku mrežu, cilj je napasti neki određeni servis. Recimo ako se napada Domain Name System tada će se naravno napadati DNS poslužitelj. Ako je taj poslužitelj ranjiv na format string, vrlo lako ćemo postići željeni rezultat i srušiti servis.
Pogledajmo primjer napada na wu-ftpd server. Washington University FTP server verzije 2.6.0 (ili ranije) je ranjiv na tipični format string propust u site exec naredbi. Primjer sjednice kako izgleda napad:
Kao što se vidi uz pomoć specifikatora konverzije %x u site exec i još mnogo interesantnije naredbe site index, moguće je ispisati vrijednosti sa stoga na način prikazan u primjeru gore. Kada izvršimo sljedeću naredbu:
site index %n%n%n%n |
wu-ftpd će htjeti zapisati cijeli broj 0 (nula) na adresu 0x8, 0x8, 0xbfffcacc, i 0x0, uzrokujući grešku "segmentation fault" zato što adrese 0x8 i 0x0 nisu predviđene za pisanje. Kad izvršimo dolje navedenu naredbu dobit ćemo:
site index %n%n%n%n Connection closed by foreign host. |
Slučajno, nije mnogo ljudi znalo da je naredba site index ranjiva na takav napad, a posljedica toga je da velika većina IDS-a neće tragati za takvom signaturom. U vrijeme kad je taj propust bio aktualan, jedino je Snort znao prepoznati site exec napad.
6.3 Uklanjanje Format String propusta
Pronalaženje i popravljanje format string propusta je vrlo jednostavno. Format string propust je nazočan kada nema specifikatora za konverziju specificiranih za argumente funkciji koji su unošeni u program preko liste standardnog ulaza va_arg. U gornjem primjeru 6.1 i primjeru 6.2, propust je vidljiv u liniji printf(argv[1]). Brzo rješenje tog problema bilo bi kad bi ubacili specifikator za konverziju “%s” umjesto ulaznog argumenta argv[1].Tada bi dobili liniju koda koja izgleda ovako printf(“%s”, argv[1]). Ovaj specifikator za konverziju ne dopušta da se ispiše bilo koji drugi specifikator za konverziju koji je stavljen kao ulazni argument u argv[1]. Postoje pretraživači izvornog koda, koji traže takve propuste po izvornom kodu. Jedan od njih je pscan (www.striker.ottawa.on.ca/~aland/pscan/), on traži po linijama koda format string funkcije koje nemaju u sebi specifikatore za konverziju i na taj način čini izvorni kod, odnosno program sigurnijim za korištenje.