KOSEN セキュリティコンテスト 2017 に参加したおはなし
はじめに
KOSENセキュリティコンテスト2017にPwnPwnPainとして参加してきました。
参加は2度目で、今年は去年とは打って変わって「Jeopardy」と「KoH(King of Hill)」形式でのCTFでした(去年はサーバーの脆弱性を潰すHardening的な形式?でした)。
レベル感は全体的には低めに感じましたが、まだまだ力及ばずな問題もありました。
とりあえず、備忘録、復習の記録ってのも兼ねて、競技時間中に解けた問題と、終わってから解けた問題のWriteupをカテゴリーごとにゆるーく書きたいと思います。
また、Writeupにて、出力や入力が極端に長い場合、...
として省略させていただきます。
目次
Sample
00 _ サンプル _ 100
これはサンプル問題である。 今回出題される問題は、全て答えとなる「フラグ」が含まれている。
フラグは、必ずSCKOSEN{hoge}の形式になっている。
この問題のフラグはSCKOSEN{Let's enjoy}である。入力しろ。
Flag: SCKOSEN{Let's enjoy}
Binary
01 _ フラグを答えろ _ 100
正しいフラグを書けば正しいかどうかを判定してくれる、便利なアプリを開発した。
フラグを調べて解答せよ
a.out
という実行ファイルが配布されるので、何も考えずにstringsコマンド...
$ strings a.out /lib64/ld-linux-x86-64.so.2 {e#yd libc.so.6 puts __stack_chk_fail stdin printf strtok fgets strcmp __libc_start_main __gmon_start__ GLIBC_2.4 GLIBC_2.2.5 UH-` SCKOSEN{H h1dden_fH 1ag}
一応実行してみる。
# ./a.out Enter the FLAG: SCKOSEN{h1dden_f1ag} correct! the flag is SCKOSEN{h1dden_f1ag}
Flag: SCKOSEN{h1dden_f1ag}
02 _ ファイル名を探せ _ 100
フラグは簡単だ、ファイル名に隠した。
q
というファイルが渡されるので、何も考えずにfileコマンド...
$ file q q: POSIX tar archive (GNU)
tarファイルということなので、解凍。
$ tar xvf tmp x q/ x q/xNUMaohRhJu8EJTLRx0ivRfwXk3cc x q/y2Ce56H56EVYwO7tWgOXo726O0uXp x q/h14PySClySuaKQ6kxjLYWhq99ibhm x q/2bTq1nVLWdgX6aypu018Yl84ubSsE x q/VgiiZE4hEprvEWdLnsO4G8KbOHUTi x q/G0wmRiMSrE1uArCttTbnd1wefAFad x q/8Q0VABE4ellLGwRVQtb640YcDXCB2 x q/arRyqJbDkwFhzfSvDJEbpvlGYJy3q x q/BNpv17Z6xGJhMatl91SZbU5MRErTa ...
なんかqっていうディレクトリにファイルがたくさん(18888個)でてくる。
ファイルの中には何も書かれていないので、とりあえずファイル名をbase64デコードしてみるも、Incorrect Paddingと言われる。
冷静になって考えたら、18888個の中にフラグがあるのでは、と思ってgrepしてみる。
$ ls q | grep SCKOSEN SCKOSEN{ki_ha_mori_ni_kakuse}
Flag: SCKOSEN{ki_ha_mori_ni_kakuse}
03 _ ボスを倒せ _ 200
とあるゲームを見つけたのだが、ボスがあまりにも強すぎて一切倒せそうにない。
どうにか倒す方法はないだろうか?
ゲームへの接続方法: nc [hostname] [port] 例: nc www.ctfkit 8050
この問題は、チームメンバーが適当にBoFしようとしたら解けた問題。。
ちゃんとロジックを考えるために、競技終了間際に挙動をメモったので復習。
とりあえずncで接続。
$ nc score.kosensc2017.tech 40048 Input your name: AAAAA Player's HP: 1000 Boss's HP: 12345678 Next round=>^Z
たぶん、最初の入力にBoFがあるんだろうなあ、と考えてA
をいっぱい入れてみる。
$ nc score.kosensc2017.tech 40048 Input your name: AAAAAAAAAAAAAAAAAAAAAA Player's HP: 1094795585 Boss's HP: 16705 Next round=>^Z
お、減った。ということで、16075をpythonでhex(16075)
してみると見事'0x4141'
という出力に。
ここで、入力したAは22個なので、1個減らせばボスのHPは0x41
で65になるのでは、と思うものの65だとちょっとまだ強い()ので、スペースでBoF。(なぜかecho -eで\x01とかが流し込めなかった、、)
$ nc score.kosensc2017.tech 40048 Input your name: AAAAAAAAAAAAAAAAAAAA Player's HP: 1094795585 Boss's HP: 32 Next round=>1 Round 1/10 Player's Attack! Boss's HP: 21 (-11) Boss's Attack! Player's HP: 1094795419 (-166) Next round=> Round 2/10 Player's Attack! Boss's HP: 12 (-9) Boss's Attack! Player's HP: 1094795244 (-175) Next round=>1 Round 3/10 Player's Attack! Boss's HP: 3 (-9) Boss's Attack! Player's HP: 1094795069 (-175) Next round=> Round 4/10 Player's Attack! Boss's HP: 0 (-10) Boss's Attack! Player's HP: 1094794937 (-132) You win! Flag is SCKOSEN{buffer_over_flow!}
Flag: Flag is SCKOSEN{buffer_over_flow!}
04 _ OreNoFS _ 500
このファイル
raw.dmg
には、どうも独自のファイルシステムが構築されているらしい。 このファイルシステムに格納されたデータは1つ、そのデータを復元しよう。
このファイルシステムは、クラスタ単位で管理されており、1クラスタ=4096byteであることは分かっている。
また、AllocationTableというFATファイルシステムでいうFAT領域とディレクトリエントリ、データ格納領域に分けられているようだ。
また、GUID Partition Table(GPT)でフォーマットされているので、その分はクラスタの管理外である点に注意しなければならない。
ATは2byte*8192、ディレクトリエントリは32byteで下記の構造体で定義されているらしい。
struct directory_entry { unsigned char magic;(1byte) char filename[8]; char extension[3]; unsigned int size;(4byte) unsigned long offset_of_cluster;(2byte) char attribute[12]; unsigned long reserved;(2byte) };
解けなかったので他の方のwriteupを読んで解きたい。。
Crypto
05 _ 簡単な暗号化2 _ 100
ファイルからフラグを探せ
チームメンバーが、1日目の夜に解いてくれた問題。
問題ファイルにアクセスすると、base64っぽい文字列がずらーっと並んでいたので、コピペして、pythonに流し込んでdecodeしてみる。
$ python >> import base64 >> input = ''' NGI1MDA0MDMwMDE0MDgwODAwMDgyYzU2NGI1MzAwMDAwMDAwMDAwMDAwMDAwMDAw... ''' >>> base64.b64decode(("".join(input.split('\n')))) '4b5004030014080800082c564b53000000000000000000000000000b0000725f6c652f73722e6c65ad73dd92034a0c41ef...' >>> len(base64.b64decode(("".join(input.split('\n'))))) 8520
decodeしてみると、何かのファイルのバイナリっぽい何かが出てくる。
チームメンバーが解いたときリトルエンディアンうんぬん言っていたので、頑張ってリトルエンディアンにしてファイルに出力してみる。
>>> b = base64.b64decode(("".join(input.split('\n')))) >>> packed = "".join([pack("<L",int("0x"+b[i:i+8], 16)) for i in range(0, len(b), 8)]) >>> "".join([pack("<L",int("0x"+b[i:i+8], 16)) for i in range(0, len(b), 8)]) '\x03\x04PK\x08\x08\x14\x00V,\x08\x00\x00\x00SK\x00\x00\x00\x00...' >>> f = open("out", "wb")
出力したものをfileコマンドで調査。
$ file out out: data
なんかうまくいっていない。。
また冷静になって考えてみると、先頭4バイトが\x03\x04PK
となっていて、なんかみたことあるなあ、、とggると、ZIPファイルのシグネチャが0x504B0304(PK\003\004)
となっていた。
なるほど。ということで、2バイトでリトルエンディアン取り直すことに。
>>> "".join([pack("<H",int("0x"+b[i:i+4], 16)) for i in range(0, len(b), 4)]) 'PK\x03\x04\x14\x00\x08\x08\x08\x00V,SK\x00\x00\x00\x00...'
お、今度はちゃんとZIPファイルと同じシグネチャになった。ということで、ファイルとして出力してみる。
>>> packed = "".join([pack("<H",int("0x"+b[i:i+4], 16)) for i in range(0, len(b), 4)]) >>> f = open("out", "wb") >>> f.write(packed)
出力したファイルを調査すると、Microsoft社のWordファイルだということが分かるので開いてみる。
$ file out out: Microsoft Word 2007+ $ mv out.doc out.docx $ open out.docx
Flag: SCKOSEN{TEXT_BUT_NOT_PLAIN}
06 _ 解凍して解答せよ _ 100
ファイルからフラグを読み取れ!
flag.zipというファイルが渡されるので、解凍すると、flagというディレクトリの中にpngが2つ展開される。
$ unzip tmp.zip Archive: tmp.zip creating: flag/ inflating: flag/masks.png inflating: flag/xor.png
開いてみると、次のような画像が表示される。 画像をみると、xorしたらなんか得られそうだし、画像のファイル名もmasksとxorだし、ということで画像のxorをとってみる。
import cv2 masks = cv2.imread('./flag/masks.png') xor = cv2.imread('./flag/xor.png') flag = cv2.bitwise_xor(masks, xor) cv2.imwrite('flag.png', flag)
Flag: SCKOSEN{simple_visual_cryptography}
07 _ 簡単な符号化 _ 100
U0NLT1NFTntiYXNlNjRfaXNfdmVyeV9lYXN5fQ
base64デコードするだけ。
$ echo "U0NLT1NFTntiYXNlNjRfaXNfdmVyeV9lYXN5fQ" | base64 -D SCKOSEN{base64_is_very_easy
Flag: SCKOSEN{base64_is_very_easy}
Network
15 _ 寝坊気味のコンピュータ _ 100
ここにある通信をキャプチャしたファイルがある。この中から、フラグを見つけ出せ!
problem.pcapngというファイルが渡されるのでとりあえずstringsコマンド...
$ strings problem.pcapng Linux 4.10.0-37-generic Dumpcap (Wireshark) 2.2.6 (Git Rev Unknown from unknown) wlp4s0 Linux 4.10.0-37-generic SCKOSESCKOSESCKOSESCKOSESCKOSESCKOSESCKOSESCKOSESCKOSESCKOSESCKOSESCKOSESCKOSESCKOSESCKOSESCKOSE N{wakeN{wakeN{wakeN{wakeN{wakeN{wakeN{wakeN{wakeN{wakeN{wakeN{wakeN{wakeN{wakeN{wakeN{wakeN{wake _on_la_on_la_on_la_on_la_on_la_on_la_on_la_on_la_on_la_on_la_on_la_on_la_on_la_on_la_on_la_on_la n_is_an_is_an_is_an_is_an_is_an_is_an_is_an_is_an_is_an_is_an_is_an_is_an_is_an_is_an_is_an_is_a 1~_z lerm} lerm} lerm} lerm} lerm} lerm} lerm} lerm} lerm} lerm} lerm} lerm} lerm} lerm} lerm} lerm}
フラグっぽいのがみえるので手動で抽出。
ちなみに、Wake-on-LANパケットは、起動したいマシンのMACアドレスを16回繰り返したデータを含むそうです。
この問題では、フラグが6文字ずつMACアドレスとしてWoLパケットに埋め込まれているという問題でした。
Flag: SCKOSEN{wake_on_lan_is_alerm}
16 _ ログインしたいんだ! _ 100
ここにある通信をキャプチャしたファイルがある。この中から、フラグを見つけ出せ!
これもまたproblem.pcapngというファイルが渡されるのでまた何も考えずstringsコマンド...
$ strings problem.pcapng ... GET / HTTP/1.1 Host: 172.27.132.124 Connection: keep-alive Authorization: Basic YWRtaW46U0NLT1NFTntiYXNpY19pc191bnNlY3VyZX0= Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.102 Safari/537.36 Vivaldi/1.94.971.8 ...
ざーっと眺めるとしたのほうにbase64っぽい文字列を発見。 よく見るとBasic認証をしているので、base64デコードしてみる。
$ echo "YWRtaW46U0NLT1NFTntiYXNpY19pc191bnNlY3VyZX0=" | base64 -D admin:SCKOSEN{basic_is_unsecure}
Flag: SCKOSEN{basic_is_unsecure}
別解
たぶんこっちが正攻法。
WireSharkなどのパケットキャプチャツールでproblem.pcapngを開くと、401 Unauthorized
で認証に何度も失敗しているHTTP通信が見られるので、認証に成功している(200 OKな)パケットを探す。
48パケット目に200 OK
なパケットがあり、その直前のGET / HTTP/1.1
なパケットを見ると、Basic認証の情報があるので、パスワードがフラグになっている。
Flag: SCKOSEN{basic_is_unsecure}
17 _ ファイル送信 _ 200
これはファイルを送受信しているpcapです
file.pcapというファイルが与えられるので、とりあえずstrings...してもあまり嬉しい情報は見つからなかった。
素直にWireSharkで開くと、Lenna.png
と、lock.zip
がHTTP通信で送受信されているのがわかる。
ということで、WireSharkのFile -> Export Objects -> HTTP -> Save All
でファイルをエクスポート。
それぞれのfileコマンドの結果を見てみる。
$ file Lenna.png lock.zip Lenna.png: PNG image data, 512 x 512, 8-bit/color RGB, non-interlaced lock.zip: Zip archive data, at least v2.0 to extract
普通のファイルっぽいので、普通にunzipしてみる。
$ unzip lock.zip Archive: lock.zip [lock.zip] Lenna.png password: password incorrect--reenter: password incorrect--reenter: skipping: Lenna.png incorrect password [lock.zip] flag.txt password: password incorrect--reenter: password incorrect--reenter: skipping: flag.txt incorrect password
普通にパスワードがかかっててunzipできない。
ここでみんなで数時間悩んだ。
「Lenna.pngにパスワードが隠されているのでは?」「TCPパケットにパスワードが隠されているのでは?」「BruteForceでパスワードクラックするのでは?」
結論からいえば、既知平文攻撃でした。
1日目終了後、おうちに帰って冷静になってctf zip パスワード
とかでwriteupを適当にggっていたら、あ。これだ。
SECCON 2015 Online CTF Writeup
ということで、pkcrackをインストールし、早速実行。
$ zip Lenna.zip Lenna.png $ pkcrack -C lock.zip -c Lenna.png -P Lenna.zip -p Lenna.png -d flag.zip ... Done. Left with 90 possible Values. bestOffset is 298362. Stage 1 completed. Starting stage 2 on Sun Oct 22 23:51:16 2017 Ta-daaaaa! key0=e15a792b, key1=591e8679, key2=9dee3dba Probabilistic test succeeded for 175560 bytes. Ta-daaaaa! key0=e15a792b, key1=591e8679, key2=9dee3dba Probabilistic test succeeded for 175560 bytes. Stage 2 completed. Starting zipdecrypt on Sun Oct 22 23:51:19 2017 Decrypting Lenna.png (155edefb6ebeec35536b5a3d)... OK! Decrypting flag.txt (f38434531be49de337a96b0e)... OK! $ cat flag.txt SCKOSEN{k_p_t_a}
Flag: SCKOSEN{k_p_t_a}
おわりに
今回のKOSENセキュコンでは、圧倒的知識不足を感じました(毎回感じてる気がする)。
というのも、解きたい問題のメインテーマについてそこそこ知識が無いと解けない問題というものにどんどん直面してきてるように感じるので、もっと広く深く勉強したいです。
解けなかった問題についても、プロ各位のwriteupを見て勉強したいです。。
楽しい大会をありがとうございました!