この大会は2022/5/15 0:00(JST)~2022/5/16 0:00(JST)に開催されました。
今回もチームで参戦。結果は352点で176チーム中82位でした。
自分で解けた問題をWriteupとして書いておきます。
Find the flag (misc)
ブラウザのタイトルにURL表示の他に1文字が次々に表示される。文字を追うのはきついので、HTMLソースを見て、リンクされているjsを見ていく。
http://flag-title.q.2022.volgactf.ru:3000/_next/static/chunks/pageindex-f6df80d9480f611f.jsにbase64文字列が含まれているので、デコードしていく。
$ echo 4oCcV2lubmluZyBpZ25pdGVzIGEgc2VsZi1jb25zY2lvdXMgYXdhcmVuZXNzIHRoYXQgb3RoZXJzIGFyZSA= | base64 -d “Winning ignites a self-conscious awareness that others are $ echo d2F0Y2hpbmcuIEl04oCZcyBhIGxvdCBlYXNpZXIgdG8gbW92ZSB1bmRlciB0aGUgcmFkYXIgd2hlbiBubyBvbmU= | base64 -d watching. It’s a lot easier to move under the radar when no one $ echo d2F0Y2hpbmcuIEl04oCZcyBhIGxvdCBlYXNpZXIgdG8gbW92ZSB1bmRlciB0aGUgcmFkYXIgd2hlbiBubyBvbmU= | base64 -d watching. It’s a lot easier to move under the radar when no one $ echo YmUgcm91Z2ggYW5kIGdldCBkaXJ0eSBiZWNhdXNlIG5vIG9uZSBldmVuIGtub3dzIHlvdeKAmXJlIHRoZXJlLiA= | base64 -d be rough and get dirty because no one even knows you’re there. $ echo QnV0IGFzIHNvb24gYXMgeW91IHN0YXJ0IHRvIHdpbiwgYW5kIG90aGVycyBzdGFydCB0byBub3RpY2UsIHlvdeKAmXJl | base64 -d But as soon as you start to win, and others start to notice, you’re $ echo c3VkZGVubHkgYXdhcmUgdGhhdCB5b3XigJlyZSBiZWluZyBvYnNlcnZlZC4gWW914oCZcmUgYmVpbmcganVkZ2VkLiA= | base64 -d suddenly aware that you’re being observed. You’re being judged. $ echo WW91IHdvcnJ5IHRoYXQgb3RoZXJzIHdpbGwgZGlzY292ZXIgeW91ciBmbGF3cyBhbmQgd2Vha25lc3Nlcyw= | base64 -d You worry that others will discover your flaws and weaknesses, $ echo YW5kIHlvdSBzdGFydCBoaWRpbmcgeW91ciB0cnVlIHBlcnNvbmFsaXR5LCBzbyB5b3UgY2FuIGJlIGEgZ29vZCA= | base64 -d and you start hiding your true personality, so you can be a good $ echo cm9sZSBtb2RlbCBhbmQgZ29vZCBjaXRpemVuIGFuZCBWb2xnYUNURntEWU40TTFDXzcxN0wz | base64 -d role model and good citizen and VolgaCTF{DYN4M1C_717L3 $ echo X1IzNEM3XzQ4MzF9IGEgbGVhZGVyIHRoYXQgb3RoZXJzIGNhbiByZXNwZWN0LiBUaGVyZSBpcyBub3RoaW5n | base64 -d _R34C7_4831} a leader that others can respect. There is nothing :
フラグが現れた。
VolgaCTF{DYN4M1C_717L3_R34C7_4831}
Poem (crypto)
暗号処理の概要は以下の通り。
・generator = LCG(a, b, m, x0) LCGパラメータ設定(各パラメータ不明) ・cipertext = b'' ・poem.txtの各行について以下を実行 ・iv: generator.get_byte()の結果を16回結合 ・x0 = (a * x0 + b) % m ・key: generator.get_byte()の結果を16回結合 ・x0 = (a * x0 + b) % m ・ct_block = encrypt(key, iv, line.strip().encode()) ・plaintext: "\x00"でパディング ・ctr: keyでivをAES-ECB暗号化 ・ctr_int: ctrの数値化 ・pt_blocks: plaintextの16バイトのブロック配列 ・pt_blocksの各ブロックについて以下を実行 ・block_int: ブロックの文字列の数値化 ・ct_block_int: block_intとctr_intのXOR ・ct_block: ct_block_intの文字列化 ・ctr_int = (ctr_int + 1) % (2 ** 128) ・ct_blockを結合した文字列を返却
機能的にLCGパラメータの各値は256未満。mを256に固定し、a, b, x0でブルートフォースし、平文と暗号文の関係を満たすものを探す。パラメータを割り出したら、それを元に暗号文を復号すると、最終行にフラグがあるはず。
#!/usr/bin/env python3 from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes from Crypto.Util.strxor import strxor class LCG(object): def __init__(self, a, b, m, x0): self.a = a self.b = b self.m = m self.x0 = x0 def get_byte(self): self.x0 = (self.a * self.x0 + self.b) % self.m return self.x0.to_bytes(1, byteorder='big') def encrypt(key, iv, plaintext): encryptor = Cipher(algorithms.AES(key), modes.ECB()).encryptor() ciphertext = b'' plaintext = plaintext + b'\x00' * (16 - len(plaintext) % 16) ctr = encryptor.update(iv) + encryptor.finalize() ctr_int = int.from_bytes(ctr, byteorder='big') pt_blocks = [] for i in range(0, len(plaintext), 16): pt_blocks.append(plaintext[i:i+16]) for block in pt_blocks: block_int = int.from_bytes(block, byteorder='big') ct_block_int = block_int ^ ctr_int ct_block = ct_block_int.to_bytes(16, byteorder='big') ctr_int = (ctr_int + 1) % (2 ** 128) ciphertext += ct_block return ciphertext def decrypt(key, iv, ciphertext): encryptor = Cipher(algorithms.AES(key), modes.ECB()).encryptor() plaintext = b'' ctr = encryptor.update(iv) + encryptor.finalize() ct_blocks = [] for i in range(0, len(ciphertext), 16): ct_blocks.append(ciphertext[i:i + 16]) for block in ct_blocks: block_int = int.from_bytes(block, byteorder='big') ctr_int = int.from_bytes(ctr, byteorder='big') pt_block_int = block_int ^ ctr_int pt_block = pt_block_int.to_bytes(16, byteorder='big') ctr = ((ctr_int + 1) % (2 ** 128)).to_bytes(16, byteorder='big') plaintext += pt_block.rstrip(b'\x00') return plaintext with open('poem.txt', 'rb') as f: lines = f.read().splitlines() with open('poem.txt.enc', 'rb') as f: ct = f.read() ct_size = [] for line in lines: ct_size.append((len(line) // 16 + 1) * 16) ct_size[-1] = len(ct) - sum(ct_size[:-1]) pt0 = lines[0] ct0 = ct[:ct_size[0]] m = 256 found = False for a in range(256): for b in range(256): for x0 in range(256): generator = LCG(a, b, m, x0) iv, key = b'', b'' for _ in range(16): iv += generator.get_byte() for _ in range(16): key += generator.get_byte() ct_block = encrypt(key, iv, pt0) if ct_block == ct0: found = True print('[+] a =', a) print('[+] b =', b) print('[+] m =', m) print('[+] x0 =', x0) break if found: break if found: break generator = LCG(a, b, m, x0) index = 0 for size in ct_size: iv, key = b'', b'' for _ in range(16): iv += generator.get_byte() for _ in range(16): key += generator.get_byte() pt_block = decrypt(key, iv, ct[index:index + size]).decode() print(pt_block) index += size
実行結果は以下の通り。
[+] a = 113 [+] b = 7 [+] m = 256 [+] x0 = 13 Over hill, over dale, Thorough bush, thorough brier, Over park, over pale, Thorough flood, thorough fire! I do wander everywhere, Swifter than the moon's sphere; And I serve the Fairy Queen, To dew her orbs upon the green; The cowslips tall her pensioners be; In their gold coats spots you see; Those be rubies, fairy favours; In those freckles live their savours; I must go seek some dewdrops here, And hang a pearl in every cowslip's ear. tw0_t1m3_p4d_1s_st1ll_tw0_t1m3_p4d
tw0_t1m3_p4d_1s_st1ll_tw0_t1m3_p4d