この大会は2024/6/15 1:00(JST)~2024/6/16 1:00(JST)に開催されました。
今回もチームで参戦。結果は2165点で964チーム中98位でした。
自分で解けた問題をWriteupとして書いておきます。
Sanity Check (web)
ブラウザ上で右クリックやデベロッパーツールが使えないので、curlでアクセスする。
$ curl https://sanity-check.vsc.tf/ <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Sanity Check</title> <style> body { background-color: #000021; font-family: "JetBrains Mono", monospace; } h1 { background: linear-gradient(to right, #fe5d00, #fe8c00); background-clip: text; -webkit-background-clip: text; color: transparent; text-align: center; } .centered { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); } </style> <script> setInterval("debugger", 10) </script> </head> <body oncontextmenu="return false"> <script> document.addEventListener('contextmenu', (e) => { e.preventDefault(); } ); document.onkeydown = function (e) { if (event.keyCode == 123) { return false; } if (e.ctrlKey && e.shiftKey && e.keyCode == 'I'.charCodeAt(0)) { return false; } if (e.ctrlKey && e.shiftKey && e.keyCode == 'C'.charCodeAt(0)) { return false; } if (e.ctrlKey && e.shiftKey && e.keyCode == 'J'.charCodeAt(0)) { return false; } if (e.ctrlKey && e.keyCode == 'U'.charCodeAt(0)) { return false; } } </script> <div class="centered"> <h1>you know what to do</h1> <!-- vsctf{c0ngratulati0ns_y0u_viewed_the_s0urc3!...welcome_to_vsctf_2024!} --> </div> </body> </html>
コメントにフラグが書いてあった。
vsctf{c0ngratulati0ns_y0u_viewed_the_s0urc3!...welcome_to_vsctf_2024!}
Discord (misc)
Discordに入り、#announcementsチャネルのメッセージを見たら、フラグが書いてあった。
vsctf{welcome_to_vsctf_2024!}
baby-game (algo)
1行目にセルの縦の長さ=横の長さが表示される。次の行にCaring Koalaの位置(x1, y1)、Red Pandaの位置(x2, y2)としてx1 y1 x2 y2が表示される。Caring Koalaから縦横に交互に動き、同じ位置に追いついたら、勝ちになるので、勝つ方を表示するプログラムをSubmitして正しければ、フラグが表示される。
お互いに近づいて行って、どうなるかを見てみる。
2x2で以下の左端の配置の場合、以下のようにC(Caring Koala)から進み、R(Red Panda)が相手のマスに入り、勝つ。
_ C C _ R _ R _
3x3の以下の左端の配置の場合、以下のようにC(Caring Koala)から進み、C(Caring Koala)が相手のマスに入り、勝つ。
_ C _ C _ _ C _ _ _ _ _ _ _ _ R _ _ R _ _ R _ _ _ _ _
初期位置が(x1, y1), (x2, y2)で、|x1 - x2| + |y1 - y2|が偶数の場合Red Pandaが勝ち、奇数の場合Caring Koalaが勝つ。このことを前提にプログラムを作成する。
以下のコードをSubmitしたら、フラグが表示された。
#include <iostream> using namespace std; int main() { int n; int x1, x2, y1, y2, diff; scanf("%d", &n); scanf("%d %d %d %d", &x1, &y1, &x2, &y2); diff = x1 - x2 + y1 - y2; if (diff % 2 == 0) { cout << "Red Panda" << endl; } else { cout << "Caring Koala" << endl; } return 0; }
vsctf{baby_game_pure_luck_uwu}
intro-reversing (rev)
Ghidraでデコンパイルする。
undefined8 main(void) { int local_c; for (local_c = 0; local_c < 0x8ae; local_c = local_c + 0xca) { printf("%.*s\n",0xca,flag + local_c); sleep(0xb1aaf); } return 0; }
sleepの該当箇所のアセンブリは以下のようになっている。
001011a6 bf af 1a MOV EDI,0xb1aaf 0b 00 001011ab e8 c0 fe CALL <EXTERNAL>::sleep uint sleep(uint __seconds) ff ff
sleepの時間が長いので、バイナリの該当箇所を以下のように書き換えて、0にする。
bf af 1a 0b 00 -> bf 00 00 00 00
書き換えたバイナリを実行すると、ASCIIアートでフラグが現れた。
$ ./chall_mod /$$ /$$$$$$ /$$$ /$$ /$$$$$$$$ /$$$$$$ /$$$$$$ /$$$$$$ /$$$$$$$ /$$$$$$$ /$$ /$$ /$$$ | $$ /$$__ $$ /$$_/ /$$$$ |__ $$__/ /$$$_ $$ /$$__ $$ /$$__ $$| $$__ $$| $$____/ /$$$$ | $$|_ $$ /$$ /$$ /$$$$$$$ /$$$$$$$ /$$$$$$ | $$ \__/| $$ |_ $$ /$$$$$$$ | $$ /$$$$$$ | $$$$\ $$ /$$$$$$|__/ \ $$ /$$ /$$|__/ \ $$| $$ \ $$| $$ |_ $$ /$$$$$$$ /$$$$$$ | $$ | $$ | $$ /$$//$$_____/ /$$_____/|_ $$_/ | $$$$ /$$$ | $$ | $$__ $$| $$ /$$__ $$| $$ $$ $$ /$$__ $$ /$$$$$/| $$ /$$/ /$$$$$/| $$$$$$$/| $$$$$$$ | $$ | $$__ $$ /$$__ $$| $$ | $$$ \ $$/$$/| $$$$$$ | $$ | $$ | $$_/ | $$ | $$ | $$ \ $$| $$| $$ \__/| $$\ $$$$ | $$ \__/ |___ $$ \ $$/$$/ |___ $$| $$__ $$|_____ $$ | $$ | $$ \ $$| $$ \ $$|__/ | $$/ \ $$$/ \____ $$| $$ | $$ /$$| $$ \ $$ | $$ | $$ | $$| $$| $$ | $$ \ $$$ | $$ /$$ \ $$ \ $$$/ /$$ \ $$| $$ \ $$ /$$ \ $$ | $$ | $$ | $$| $$ | $$ | $$ \ $/ /$$$$$$$/| $$$$$$$ | $$$$/| $$ | $$$ /$$$$$$| $$ | $$| $$| $$ | $$$$$$/ | $$ | $$$$$$/ \ $/ | $$$$$$/| $$ | $$| $$$$$$//$$$$$$| $$ | $$| $$$$$$$ /$$ /$$$/ \_/ |_______/ \_______/ \___/ |__/ \___/|______/|__/ |__/|__/|__/ \______//$$$$$$|__/ \______/ \_/ \______/ |__/ |__/ \______/|______/|__/ |__/ \____ $$|__/|___/ |______/ /$$ \ $$ | $$$$$$/ \______/
vsctf{1nTr0_r3v3R51ng!}
not-quite-caesar (crypto)
flagの各文字のASCIIコードxに対して、x+3, x-3, x*3, x^3のいずれかをランダムに行っている。ただし、seedを使っているので、どの計算をしているのかがわかり、逆算してフラグがわかる。
#!/usr/bin/env python3 import random random.seed(1337) rev_ops = [ lambda x: x - 3, lambda x: x + 3, lambda x: x // 3, lambda x: x ^ 3, ] with open('out.txt', 'r') as f: out = eval(f.read()) flag = '' for o in out: flag += chr(random.choice(rev_ops)(o)) print(flag)
vsctf{looks_like_ceasar_but_isnt_a655563a0a62ef74}
aes-but-twice (crypto)
サーバの処理概要は以下の通り。
・nonce: ランダム8バイト文字列 ・iv: ランダム16バイト文字列 ・key: ランダム16バイト文字列 ・CTR_ENC: key, nonceを使ったAES CTRモード暗号化オブジェクト ・CBC_ENC: key, ivを使ったAES CBCモード暗号化オブジェクト ・flag: flagをパディング ・ctr_encrypt(flag)を表示 ・flagをパディングし、CTR_ENCでAES CTRモード暗号化し、16進数表記にしたものを表示 ・cbc_encrypt(flag)を表示 ・flagをパディングし、CBC_ENCでAES CBCモード暗号化し、16進数表記にしたものを表示 ・nonceを16進数表記で表示 ・以下繰り返し ・inp: 入力 ・inpが"exit"の場合、繰り返し終了 ・data: inpをhexデコード ・ctr_encrypt(data)を表示 ・cbc_encrypt(data)を表示
$ nc vsc.tf 5000 f1d134fdbf36b95cbd1fc416b39d8f05e5f6963f4421588742386cfbccb3e7bc3a2df0e071c3f5c70b92dae860c612dc4c2360eaf3b8724c9fc1ab2e7ccc89de d85b2aebd762788b3673e2ea899c66a18f3db18e81d6650072268d095d71fd93474a496a9606abcdfc5265237a2e90301d935afe61538070fc37f43489c47a81 7a79e0630b0f0d06 11111111 a47d282d91e4ded1a5fbc982f37211e9 54b915c2c6ba7bd6f7fea68c73c02ee0 11111111 867e7ff320220a03e80502273f17760f 51c8d1869666406a2e58f9c9c43f373f
flagをパディングしたものをFFF...FFF(48バイト)とすると、FFF...FFF(48バイト)をパディングしたものは以下の通りとなる。
FLAG = FFF...FFF(48バイト) + "\x10" * 16
NNNNNNNNをnonce、FLAGnをFLAGのnブロック目とすると以下のようなイメージになる。
AES暗号化(CTRモード) NNNNNNNN\x00....\x00\x00 --(AES暗号化)--> TMP_CT0 ^ FLAG0 = CT1_0 NNNNNNNN\x00....\x00\x01 --(AES暗号化)--> TMP_CT1 ^ FLAG1 = CT1_1 NNNNNNNN\x00....\x00\x02 --(AES暗号化)--> TMP_CT2 ^ FLAG2 = CT1_2 NNNNNNNN\x00....\x00\x03 --(AES暗号化)--> TMP_CT3 ^ FLAG3 = CT1_3 AES暗号化(CBCモード) FLAG0 ^ IV --(AES暗号化)--> CT2_0 FLAG1 ^ CT2_0 --(AES暗号化)--> CT2_1 FLAG2 ^ CT2_1 --(AES暗号化)--> CT2_2 FLAG3 ^ CT2_2 --(AES暗号化)--> CT2_3
NNNNNNNN\x00....\x00\x00のAES暗号化を求めれば、XORでFLAG0を求めることができる。
CBCモードは最後のブロックの暗号化がIVとなって、次のブロックを暗号化するので、以下の通りとなるよう平文を指定すれば、その暗号化データを取得できる。
PT0 ^ IV = NNNNNNNN\x00....\x00\x00
同様に他のブロックも計算すれば、FLAGを求めることができ、アンパディングすればflagになる。
#!/usr/bin/env python3 import socket from Crypto.Util.strxor import strxor from Crypto.Util.Padding import unpad def recvuntil(s, tail): data = b'' while True: if tail in data: return data.decode() data += s.recv(1) s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect(('vsc.tf', 5000)) data = recvuntil(s, b'\n').rstrip() print(data) ctr_enc_flag = bytes.fromhex(data) data = recvuntil(s, b'\n').rstrip() print(data) cbc_enc_flag = bytes.fromhex(data) data = recvuntil(s, b'\n').rstrip() print(data) nonce = bytes.fromhex(data) iv = cbc_enc_flag[-16:] flag = b'' for i in range(len(ctr_enc_flag) // 16 - 1): pt = strxor(nonce + i.to_bytes(8, 'big'), iv).hex() print(pt) s.sendall(pt.encode() + b'\n') data = recvuntil(s, b'\n').rstrip() print(data) data = recvuntil(s, b'\n').rstrip() print(data) tmp_ct = bytes.fromhex(data[:32]) iv = bytes.fromhex(data[32:]) flag_part = strxor(tmp_ct, ctr_enc_flag[i*16:i*16+16]) flag += flag_part flag = unpad(flag, 16).decode() print(flag)
実行結果は以下の通り。
d8105edd4cfe36caf245a2315f71269594c2fbaa9fa8c907e8e12309dd05014d973df1c66f8470adfbd8b6aa644abca6cddeba94208f8ba25403ea8c2bc6bc28 f7f5aa05293e5b001a9e2b33fda03f911d290764dc4743cb622114804db03d528bee586e44e145f7cca8406b1210fb1330d7571c3decb78ffe32a6f8026dcc04 f14df5a7c6efb154 c19aa2bbfb0306dbfe32a6f8026dcc04 ecc42332313815abd726875466c6a6f7613ca4032838cbdcaa96657f292b5663 ae633da92a855bafad32c75f001244f6de1f1d57a6cdb16edb136e55cd7e5bcf 2f52e8f06022003adb136e55cd7e5bce 3344b25be8b6483b977d6e342a72ff6ca986c6eb327babcf0c921542871f0d59 cbf49892aa9dfd32dbd01438eb3639290df05d396349386377c89d5dcc6b65e2 fcbda89ea5a6893777c89d5dcc6b65e0 aa3001cb9f76194d774a3b15d51d48344c191d8cc331eabc4c680711347fa6ac a240ffc8618a7ea3f5d6b8a46a44b2a820108a480b351a0a1fb3cb6261a68cfd vsctf{me_wen_cbc_6c855453171638d5}
vsctf{me_wen_cbc_6c855453171638d5}
dream (crypto)
サーバの処理概要は以下の通り。
・seed: seed.txtの内容 ・seed: seedを数値化したもの ・seedをシードとしてランダム設定 ・idxs: 配列入力 ・idxsの長さが8より大きい場合、終了 ・idxが0~623に対して、以下を実行 ・rand_out: ランダム32ビット整数 ・idx が idxs に含まれている場合、rand_outを表示 ・key: ランダム256ビット整数 ・nonce: ランダム256ビット整数 ・aes_key: keyを文字列としたsha256ダイジェストの先頭16バイト ・aes_nonce: nonceを文字列としたsha256ダイジェストの先頭16バイト ・aes_key, aes_nonceを使ってパディングしたフラグをAES GCMモード暗号化して16進数表記で表示
78回アクセスすれば、必要なランダム値を入手できるので、Mersenne Twisterの特性からkey, nonceを取得できる。あとはそれを使って、AES GCMモードの復号をすれば、フラグがわかる。
#!/usr/bin/env python3 import socket import random from Crypto.Cipher import AES from Crypto.Util.Padding import unpad from hashlib import sha256 def recvuntil(s, tail): data = b'' while True: if tail in data: return data.decode() data += s.recv(1) 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 N = 624 state = [] for i in range(624 // 8): s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect(('vsc.tf', 5001)) idxs = [j for j in range(i * 8, i * 8 + 8)] data = recvuntil(s, b'>>> ') print(data + str(idxs)) s.sendall(str(idxs).encode() + b'\n') for j in range(8): data = recvuntil(s, b'\n').rstrip() print(data) state.append(untemper(int(data))) if len(state) == N: data = recvuntil(s, b'\n').rstrip() print(data) enc_flag = bytes.fromhex(data) s.close() state.append(N) random.setstate([3, tuple(state), None]) key = random.getrandbits(256) nonce = random.getrandbits(256) aes_key = sha256(str(key).encode()).digest()[:16] aes_nonce = sha256(str(nonce).encode()).digest()[:16] cipher = AES.new(aes_key, AES.MODE_GCM, nonce=aes_nonce) flag = unpad(cipher.decrypt(enc_flag), 16).decode() print(flag)
実行結果は以下の通り。
>>> [0, 1, 2, 3, 4, 5, 6, 7] 475736381 2599178730 639420308 1400092731 2055728736 3311659743 3349287473 3292155355 >>> [8, 9, 10, 11, 12, 13, 14, 15] 3972270152 3453764163 774805574 1416637968 1686108321 2690133874 2498232095 247303664 >>> [16, 17, 18, 19, 20, 21, 22, 23] 4130893192 3985880470 1585398883 1755674673 3083186055 1418857434 736357969 3074716874 : : 379443684 2558372521 705855492 292749589 3239662607 3073875086 3509591978 458422530 >>> [608, 609, 610, 611, 612, 613, 614, 615] 1938171477 2450633279 3509420931 1402183533 2579036895 182179989 799027111 2248812622 >>> [616, 617, 618, 619, 620, 621, 622, 623] 1171136879 634080450 3657292713 1917859602 2761658247 93487198 327330884 1608754247 cb3cc14d2f5eeac6b5645bb66fa268d88399d1654df20668110e1d04bf135db71930985b5eba307c0197b035f2e9203f vsctf{dream_luck???_5e3ec2f2d338fc9f}
vsctf{dream_luck???_5e3ec2f2d338fc9f}