autor Mladen Knežić, mladen.knezic@fer.hr
Buffer overflow ( preljev spremnika ) je :
Od svih incidenata s računalnom sigurnošću, buffer overflow je uzrok većini, a od svih exploita, većina ih se temelji na buffer overflowu.
Buffer overflow je uzrokovan programerskom nepažnjom, poglavito korištenjem funkcija za rad sa nizovima znakova – stringova – koje ne provjeravaju duljinu niza tj. ne provjeravaju da li je rezervirano dovoljno mjesta za zapis niza ( funkcije iz biblioteke string.h za C programe ) .
Funkcije koje ne provjeravaju duljinu niza su strcpy,sprintf,vsprintf,gets,strcat,...
U proceduralnom programiranju, program se većinom sastoji od poziva funkcija (procedura) koje obavljaju operacije nad podacima.
Poziv funkcija sastoji se od sljedećeg :
Nakon izvođenja funkcije, sa stoga se vraćaju BP i IP, te se izvođenje program nastavlja na adresi koja se nalazi u IP.
Ako bi funkcija prepisala pohranjenu vrijednost IP-a na stogu sa nekom drugom adresom, tada bi program nakon obnove sadržaja IP-a sa stoga nastavio s izvođenjem na toj drugoj adresi.
Primjer: program uzima argument iz naredbenog retka te ga predaje funkciji koja izvede operacije nad njim.
KOD: #include <string.h> void radi(char* argument){ char arg[4]; strcpy(arg,argument); } void main(int argc,char* argv){ radi(argv[1]); }
IZGLED STOGA PRIJE POZIVA FUNKCIJE adrese sadržaj 0xafbfcfdf IP 0xafbfcfdc BP 0xafbfcfd9 arg[4] .... ....
Funkcija ga kopira u lokalnu varijablu kako bi bilo lakše raditi s njim .
Ne provjerava se veličina argumenta kako bi se približili realnim programima, kod kojih može izostati takva provjera ( a sigurno ima takvih programa ). Primjer je trivijalan kako bi se što lakše mogao objasniti problem buffer overflowa .
Ako programu predamo argument veći od 3 znaka, funkcija će ga kopirati na stog (prostor za varijablu arg je rezerviran na stogu), ali će pritom prepisati BP i IP (ovisno o veličini argumenta).
Izvođenjem programa s argumentom većim od 3 znaka , dobit ćemo grešku – funkcija radi() će prepisati IP i pri povratku iz funkcije nećemo se vratiti u main() već na mjesto određeno dijelom argumenta koji prepisuje IP.
Ako bismo funkciji radi predali kao argment niz definiran kao char argv[]={'a','b','c','d','e','f','g','h',0xe2,0x1b,0x41,0}, funkcija bi njime upisala u varijablu arg niz znakova "abcd" (na mjesto terminatora bi došao znak d), na mjesto BP bi došao niz "efgh", a na mjesto IP bi se upisao niz {0xe2,0x1b,0x41,0x00}, što bi se shvatilo kao adresa 0x00411be2, te bi program izvođenje nakon funkcije radi() nastavio na adresi 0x00411be2 .
IZGLED STOGA NAKON PREPISIVANJA adrese sadržaj 0xafbfcfdf 0x0,0x41,0x1b,0xe2 0xafbfcfdc "efgh" 0xafbfcfd9 "abcd" .... ....
Ako bi na toj adresi bio neki program on bi se izveo i to je osnovna ideja iskorištavanja buffer overflowa.
Buffer overflow se iskorištava tako da se na stog stavlja kod zvani shellcode, a povratna adresa se prepisuje adresom tog koda. Obično se shellcode stavlja kao lokalna varijabla u funkciji u obliku niza znakova. Prilikom povratka iz funkcije program skače na početak shellcodea i počinje ga izvoditi (ne dolazi do prepisivanja shellcodea na stogu sa nekim drugim sadržajem) . U shellcode se upisuju procesorske instrukcije koje najčešće pozivaju ljusku operativnog sustava (shell, pa prema tome i naziv shellcode).
Tako se pri povratku iz funkcije program ne vraća na mjesto nakon mjesta s kojeg je ta funkcija pozvana već se izvršava shellcode koji pozove ljusku operativnog sustava, pod pravima korisnika koji je vlasnik procesa (a najčešće je to administrator) .
Sa gledišta sigurnosti to je ozbiljan problem jer se može pozvati program sa ovlastima administratora (roota) ukoliko smo iskoristili buffer overflow na programu koji je pozvan s administratorskim ovlastima .
Ukoliko je datoteka programa vlasništvo administratora, tada se taj program pokreće s administratorskim ovlastima te kao takav ima pristup svim datotekama i može ugroziti sigurnost sustava i podataka u sustavu.
Buffer overflow je vrlo težak problem , ali nije nerješiv.
Rješenja su u vidu patcheva za linux kernel odnosno service packova za windowse , specijalnih prevodioca (compiler) te kao tehnologija ugrađena u sklopovlje .
Isto tako postoje i Network Intrusion Detection System odnosno NIDS sustavi koji mogu detektirati pokušaj iskorištavanja buffer overflowa pomoću fingerprinta tj. otiska paketa. Oni prate promet na mreži i kada zapaze pojavu znakova u paketu, koji sliče znakovima koji predstavljaju shellcode, nop instrukcije i slično, automatski logiraju taj paket te je moguće otkriti pošiljatelja. Ali i NIDS je moguće zavarati korištenjem ADMutate biblioteke funkcija (autor K2, ADM crew) koja kriptira shellcode tako da ga nije moguće prepoznati.
AMD u svoje procesore Athlon 64 i Opteron ugrađuje tehnologiju nazvanu Execution protection koja bi trebala spriječavati iskorištavanje buffer overflowa . Programska podrška za nju postoji pod linuxom dok korisnici windowsa (verzije XP) trebaju pričekati service pack 2 .
Prevodioci koji rješavaju problem buffer overflowa su StackShield i StackGuard, no postoje tekstovi (Bypassing StackGuard and StackShield, autori Bulba i Kil3r lam3rz@hert.org , Phrack br. 56) s uputama kako zaobići njihova rješenja, te ih se nemože smatrati pouzdanom zaštitom od buffer overflowa.
Jedno od rješenja za linux korisnike je i Openwall project .
Projekt Openwall nalazi se na adresi www.openwall.com , a cilj mu je napraviti sigurnu linux distribuciju .
Koordinator i jedan od autora projekta je Solar Designer (autor poznatog password cracker programa John the Ripper ).
Među autorima projekta se nalaze razni hackeri koji poznaju sigurnosne probleme linux platforme te kao takvi mogu napraviti sigurnu linux distribuciju.
Čitava distribucija se nalazi na www.openwall.com/Owl , a moguće ju je pokrenuti izravno sa CD-roma bez ikakvog pisanja po tvrdom disku .
Najzanimljiviji produkt je svakako Openwall patch za linux kernel na adresi www.openwall.com/linux koji donosi :
Non-executable user stack je dodatak koji sprječava izođenje koda sa korisničkog stoga te dodatno otežava iskorištavanje buffer overflowa .
Primjer :
Ukoliko je ukernel uključen non-executable user stack dodatak , tada to nije moguće napraviti jer nije moguće izvršiti kod sa stoga
Non-executable user stack temelji se na provjeravanju povratne adrese prilikom povratka iz funkcija. Ako povratna adresa pokazuje natrag na stog , a ne na code segment (kako bi normalno trebala), detektira se pokušaj iskorištavanja buffer overflowa te se logiraju uid, ime procesa i pid .
Neki programi zahtjevaju mogućnost izvršavanja koda sa stoga, te je potrebno pri njihovom prevođenju uključiti mogućnosti detekcije i emulacije izvođenja koda na stogu ili pomoću programa chstk.c (dio Openwall patcha) omogućiti zasebno za svaki takav program izvođenje koda sa stoga .
Non-executable user stack je u prvotnoj verziji bilo moguće zaobići tako da se umjesto izvršavanja koda na stogu , pozove funkciju system() iz libc biblioteke, te pomoću nje pozvati ljusku sa administratorskim ovlastima (poziv system("/bin/sh")) . Ovaj propust je riješen tako da se funkcija system() uvijek mapira na adresu koja sadrži nulu (npr. 410B34h), a kako je 0 string terminator nije moguće prepisati povratnu adresu na stog adresom funkcije system() (može se samo zapisati prvi dio adrese 41, zatim dolazi string terminator te se sve iza njega zanemaruje).
Ostali dodaci služe sa sprječavanje napada koji se ne temelje na buffer overflowu .
StackGuard je prevodioc koji pokušava riješiti problem buffer overflowa postavljanjem mamca, uz minimalni gubitak preformansi.
Mamac je string koji se postavi na stog uz povratnu adresu funkcije. Kada funkcija završi sa radom, prije povratka u prigram iz kojeg je pozvana, provjerava se mamac (string) . Ako je string promijenjen, tada je došlo do pokušaja iskorištavanja buffer overflowa, pokušaj se logira i program se zaustavlja .
Da bi se onemogućilo čitanje stringa koji služi kao mamac, mamac se sastoji od znakova NULL(0x00), CR (0x0d), LF (0x0a) i EOF (0xff), koji terminiraju većinu operacija nad nizom znakova.
StackGuard se zaobilazi tako da se iskoristi buffer overflow nad pokazivačima na funkcije i skokovima (longjmp) koji niti ne moraju biti na stogu.
StackShield obavlja istu funkciju kao StackGuard ali na drugačiji način.
StackShield stvara dodatni stog koji sadrži kopiju povratne adrese funkcije, te se u program dodaju pozivi koji prilikom poziva funkcije stave kopiju povratne adrese na taj drugi stog, a prilikom povratka funkcije, prebace kopiju povratne adrese sa njega na stog tako da funkcija sigurno predaje tijek izvođenja pozivatelju.
Stvarna i pohranjena povratna adrese se ne uspoređuju te nema detekcije buffer overflowa već samo njegovo zaustavljanje.
Buffer overflow prvi je put iskorišten na Internetu u crvu, autora Roberta Morrisa mlađeg ,sina službenika u NSA ( američka agencija za nacionalnu sigurnost ) .
Prvi tekst o iskorištavanju buffer overflowa objavljen je 1995, pod naslovom How to write buffer overflows, a autor je Mudge .
1996. u Phracku (e-zine) br. 49 , objavljen je članak Smashing stack for fun and profit, autora Aleph1 .
U novije vrijeme je svoju popularnost doživio virus W32/SQLSlammer koji napada Microsotftov SQL server koristeći buffer overflow .
code segment – dio memorije u koju se pri pokretanju programa mapira kod programa odnosno procesorske instrukcije
exploit – program koji je napravljen za iskorištavanje propusta u drugom programu kako bi autor/korisnik programa ostvario određenu korist
pid – identifikator korisnika, svakom procesu (programu) koji se izvodi dodjeljuje se jedinstveni broj koji ga identificira
shellcode – procesorske instrukcije u obliku niza znakova ,najčešće za pokretanje ljuske operativnog sustava (shell) pa otuda i naziv, a primjeri shellcodea su :
( Za izradu shellcodea može se koristiti ShellForge, autora biondi@cartel-securite.fr, a može se dobiti sa adrese http://www.cartel-securite.fr/pbiondi/shellforge.html )
stog (stack)– dio memorije koji se koristi za predaju argumenata funkcijama, spremanje povratnih adresa funkcija, spremanje lokalnih varijabli funkcija, .... , a organiziran je kao FIFO struktura
uid – broj koji identificira korisnika, a pridjeljuje ga sustav