中高生向けのCTF、picoCTF 2019 の warmup問題のwrite-up です。本番問題の write-up へのリンクはこちらを参照。
大会開催の一週間くらい前に、公式のtwitterアカウントから "Warmup Challenge" と題してtwitterで問題が出されていました。これのwriteupが見つからなかったので残しておきます。
解説を見ずに問題だけときたい場合はこちら。
- https://twitter.com/picoctf/status/1169647893088743425
- https://twitter.com/picoctf/status/1170014360510640128
- https://twitter.com/picoctf/status/1170460648746233856
- https://twitter.com/picoctf/status/1171113644475764736
- https://twitter.com/picoctf/status/1171428844739297282
- https://twitter.com/picoctf/status/1171796439846047744
Challenge 1
Challenge 1 | Message from A: 0x1afe93e83137e76d2226d97c40512040; d=31337; N=0x50618b968b8603e9e870e7d878e866e3
— picoCTF (@picoctf) September 5, 2019
Message from A: 0x1afe93e83137e76d2226d97c40512040; d=31337; N=0x50618b968b8603e9e870e7d878e866e3
message, d, N
ということなので、RSA暗号でしょう。
from Crypto.Util.number import long_to_bytes c = 0x1afe93e83137e76d2226d97c40512040 d = 31337 n = 0x50618b968b8603e9e870e7d878e866e3 #106844723640410863741046875242417907427 plain = pow(c, d, n) print(long_to_bytes(plain))
実行結果
$ python 1.py b'Are you awake?'
Challenge 2
Challenge 2 in our warmup series! Incoming Transmission: aHR0cHM6Ly93d3cucGFzdGViaW4uY29tL3Jhdy80VE5mQXZNSg==
— picoCTF (@picoctf) 2019年9月6日
Challenge 2 in our warmup series! Incoming Transmission: aHR0cHM6Ly93d3cucGFzdGViaW4uY29tL3Jhdy80VE5mQXZNSg==
base64っぽいですね。base64 decodeしてみます。ちょいなのでオンラインでdecodeしてもらいました。
https://www.pastebin.com/raw/4TNfAvMJ
urlが出てきた。
読めない。これ横から(Display傾けて)読んだら読めるやつとか?うーん?読めないが?
と思ってまるごと等幅フォント設定のテキストエディタに突っ込んだら読めた。
THE NAMELESS MUST BE STOPPED
Challenge 3
Challenge 3 | A suspicious transmission... pic.twitter.com/kTszQcMV6W
— picoCTF (@picoctf) 2019年9月7日
A suspicious transmission...
この文字列をテキストに起こすと
13 GVZR VF EHAAVAT BHG JR ARRQ LBHE URYC
定期的にスペースが入っていることから、換字暗号っぽいなーと予想。右上の13が ROT13 を連想させるので、ROT13にかけてみます。(アルファベットを13文字ずらして読む)
#!/usr/bin/env python3 # -*- coding:utf-8 -*- import string cipher = """GVZR VF EHAAVAT BHG JR ARRQ LBHE URYC""" alphabet = list(string.ascii_uppercase) def rot13(c): return alphabet[(alphabet.index(c) + 13) % 26] plain = '' for c in cipher: if c.isalpha(): plain += rot13(c) else: plain += c print(plain)
実行結果
$ python rot13.py TIME IS RUNNING OUT WE NEED YOUR HELP
Challenge 4
Challenge 4 - CORRECTION! We included the wrong URL in the posting of the challenge last night (our bad!). Here's the correct one | A flier with some faded writing. Maybe there is more to it... https://t.co/gcUwrlpu4T
— picoCTF (@picoctf) 2019年9月9日
A flier with some faded writing. Maybe there is more to it... https://t.co/R6YUHe9EXk
リンク先からはbmpが入手できます。
JOIN US だそうです。
のっぺりした小さめの画像は2値化するといいことが多いので、輝度255以外を全部黒塗りしてみると、こんな画像になりました。
赤線の部分が輝度254
のピクセルを抜き出したところです。バイト列の先頭がアヤシイ。ということで、先頭行だけ 245=1, それ以外の輝度=0 として2値化してみました。
111110010010101101100100001010100110101001111001001100000011001101100100001010100111101001111
これをモールス信号に見立ててみたり、01に変換して8個ずつ切り出してstringに変換してみたり、もしかしてバーコードかもということで縦に引き伸ばしてバーコード状の画像にして読み込んでみたり。
うーーん、なかなかflagに繋がりません。これを考えていたらpicoCTF本番が始まってしまいました。
悔しいので、前から気になっていたForensicsツールが揃っているこちらの環境を用意しました。
まずはDockerをinstall。
$ git clone git@github.com:DominicBreuker/stego-toolkit.git $ docker build -t <image_name> . $ docker run -it --rm -v <$pwd>/stego-toolkit/data:/data <image_name> /bin/bash
これでimageが起動しました。早い!楽ちん!そしてすぐにツールが使えます。上記ではstego-toolkit
フォルダに予め用意してあるdata
ディレクトリをマウントしました。
最初は/data
ディレクトリにいます。<$pwd>/stego-toolkit/data
ディレクトリに解析対象のdataファイルを突っ込みます。
/data# ls README.md suspicious_flier.bmp
README(data
ディレクトリではなくroot直下)に書いてあるツールを上から順にbmpに対応しているものをかけていきます。試したコマンドを。大体ホストにも入れてあるツールでしたが念の為。
# exiftool suspicious_flier.bmp # foremost suspicious_flier.bmp # identify -verbose suspicious_flier.bmp # stegoveritas suspicious_flier.bmp
この次に実行したzsteg
の時にurlが出てきました!
zsteg実行結果
# zsteg -a suspicious_flier.bmp b1,bgr,lsb,xy .. text: "!https://pastebin.com/raw/45SxeQkE" b2,rgb,lsb,xy .. file: SoftQuad DESC or font file binary b2,rgb,msb,xy .. file: VISX image file ...(略)
ツールにまるっと頼ってしまいましたが、Forensics不勉強なもので致し方なし。どうやって隠したんだろう?
https://pastebin.com/raw/45SxeQkE
に飛んでみると、flagっぽいテキストが現れました。(これも私のブラウザ環境では読めなかったので、等幅フォント設定のエディタに貼り付けてみました)
Are you up to the task? I must be sure that you are ready... Further instruction will follow. MOST Importantly: If you find yourself stuck, you must remember that prime numbers will help show you the way!
Challenge 5
Challenge 5 | P8ooboe.eu2rluM4Ortc8ef9ls4y3htrodeua3.tsoyy3UTgt4lt3iw4hoe!ok.M5y3rr8L3cL6oSN3ehehbiout3r5gsg5aeeaTtaaolwerl5oyaesf6rnseddtoiasy.ncs.p8gci pic.twitter.com/Ahd3HbT6dH
— picoCTF (@picoctf) 2019年9月10日
P8ooboe.eu2rluM4Ortc8ef9ls4y3htrodeua3.tsoyy3UTgt4lt3iw4hoe!ok.M5y3rr8L3cL6oSN3ehehbiout3r5gsg5aeeaTtaaolwerl5oyaesf6rnseddtoiasy.ncs.p8gci
scramble()
されたtextが問題文かな?ということで、このコードの逆をやってあげます。※元コードがpython2だったので、python3で scramble
関数も少し書き直しています。
warmupだから舐めてましたが、結構時間かかった…。プログラムも多分冗長になってしまった。もっとシュッと書けそうな気がする。。。
#!/usr/bin/env python3 # -*- coding:utf-8 -*- from functools import reduce ctext = "P8ooboe.eu2rluM4Ortc8ef9ls4y3htrodeua3.tsoyy3UTgt4lt3iw4hoe!ok.M5y3rr8L3cL6oSN3ehehbiout3r5gsg5aeeaTtaaolwerl5oyaesf6rnseddtoiasy.ncs.p8gci" def scramble(msg): ctext = "" words = msg.split() wi = 0 offs = list(range(len(words))) # wordsのindex配列 ctrs = [len(w) for w in words] # wordsの長さ配列 while reduce(lambda a,b: a+b, ctrs, 0): # ctrsの合計が0になるまで if ctrs[wi]: ctext += (words[wi][offs[wi] % len(words[wi])]) if not(offs[wi] % len(words[wi])): ctext += str(len(words[wi])) offs[wi] += 1 ctrs[wi] -= 1 wi = (wi + 1) % len(words) return ctext def descramble(txt): # wordsの数を数える words_len = 0 for c in txt: if c.isnumeric(): words_len += 1 print('words length: ' + str(words_len)) ctrs = [100] * words_len # ctrsを埋める wi = 0 for c in txt: if c.isnumeric(): ctrs[(wi - 1) % words_len] = int(c) else: # 全部出てきたwordは飛ばす。今何周目かと、ctrsリストの数値を比較 wi += 1 while ((wi - 1) // words_len) >= ctrs[(wi - 1) % words_len]: wi += 1 offs = list(range(words_len)) words = [] for i in range(words_len): words.append([''] * ctrs[i]) wi = 0 for c in txt: if not c.isnumeric(): while (wi // words_len) >= ctrs[wi % words_len]: wi += 1 idx = wi % words_len words[idx][(offs[idx]) % ctrs[idx]] = c offs[idx] = (offs[idx] + 1) % ctrs[idx] wi += 1 msg = '' for word in words: msg += ''.join(word) + ' ' return msg msg = descramble(ctext) print(msg) # confirm print('[*] confirm by reversing.') assert scramble(msg) == ctext
実行結果
$ python q5.py words length: 24 Progress looks good. Maybe you are ready... Let us proceed. Lastly you MUST NOT forget that clearing the fibonacci will show you the rest! [*] confirm by reversing.
逆変換のチェックも通ったので、答えは
Progress looks good. Maybe you are ready... Let us proceed. Lastly you MUST NOT forget that clearing the fibonacci will show you the rest!
FINAL WARM-UP CHALLENGE!
FINAL WARM-UP CHALLENGE! (look at solutions to challenges 4&5 for hints) | Could this be a secret message? pic.twitter.com/J7g7vNfCfj
— picoCTF (@picoctf) 2019年9月11日
(look at solutions to challenges 4&5 for hints) | Could this be a secret message?
Challenge 4, 5をヒントにしてね!だそうです。
Challenge4の答え
prime numbers will help
と、Challenge5の答え
Lastly you MUST NOT forget that clearing the fibonacci will show you the rest!
がヒントになりそう。
まずは、画像の不自然な16進を10進に直します。
s: 0x22(34) $75 a: 0x43(67) $10 i: 0xb(11) $39 e: 0x15(21) $5 i: 0x3d(61) $65 m: 0x2b(43) $145 P: 0x5(5) $55 k: 0x37(55) $15 R: 0x61(97) $12 w: 0xf(15) $25 L: 0x17(23) $99 g: 0x13(19) $120 j: 0x71(113) $5 e: 0x18(24) $35 F: 0x2f(47) $1 u: 0x3b(59) $189 H: 0xd(13) $20 h: 0x8(8) $65 F: 0x7(7) $13 r: 0x1f(31) $199 2: 0x29(41) $10 w: 0x3(14) $11
この中で素数は、左の列の11,43,19,59,31
と右の列の全部です。
左の列の対応する行の赤でハイライトされた文字をつなげると、imgur
a: 0x43(67) $10 i: 0xb(11) $39 i: 0x3d(61) $65 m: 0x2b(43) $145 P: 0x5(5) $55 R: 0x61(97) $12 L: 0x17(23) $99 g: 0x13(19) $120 j: 0x71(113) $5 F: 0x2f(47) $1 u: 0x3b(59) $189 H: 0xd(13) $20 F: 0x7(7) $13 r: 0x1f(31) $199 2: 0x29(41) $10
次に、画像の中にある数くらいまでのフィボナッチ数列は下記です。
1,1,2,3,5,8,13,21,34,55,89,144,233
clearing the fibonacci will show you the rest
ということなので、フィボナッチ数列に含まれる数があるものを除外します。
a: 0x43(67) $10 i: 0xb(11) $39 i: 0x3d(61) $65 m: 0x2b(43) $145 R: 0x61(97) $12 L: 0x17(23) $99 g: 0x13(19) $120 j: 0x71(113) $5 u: 0x3b(59) $189 F: 0x7(7) $13 r: 0x1f(31) $199 2: 0x29(41) $10
残った文字列+数が与えられていなかった/
を組み合わせるとこうなります。
imgur/a/iRLjFF2
なんとなくwarmupのflagは文章みたいなので、これはflagではなさそう。/
が入っていることから、urlっぽいな?
picoCTFのドメインにくっつけてhttps://2019game.picoctf.com/imgur/a/iRLjFF2
としてアクセスしてみても、404が返ってきます。
imgur
で検索してみると、こんなサイトが。
このテイスト、memeでよく画像・GIF置き場に使われてるサイトな気がします。ドメインimgur.com
らしいので、試しに先程の文字列に.com
を足してアクセスしてみます。
imgur.com/a/iRLjFF2
おおお!ありました。
If you made it here, you got real potential. It's up to you now. Find a way in. And SHUT IT DOWN.
これにてWarmupチャレンジは完了のようです。お疲れさまでした。ちなみにこの画像はpicoCTF2019のGamesの画像っぽい。