この大会は2023/3/9 20:00(JST)~2023/3/10 20:00(JST)に開催されました。
今回もチームで参戦。結果は805点で433チーム中35位でした。
自分で解けた問題をWriteupとして書いておきます。
Read the Rules (misc)
ルールのページを見たら、フラグの例が書かれていた。
ENO{th1s_is_4n_eXample}
babyrand (misc)
100回以下で、9個の32ビット乱数を見て、次の32ビット乱数を当てることができれば、フラグが表示される。Mersenne Twisterの問題と思われる。ただし、毎回10個目の乱数がわからない。Mersenne Twisterは、インデックス0、1、397の値がわかると、インデックス624の値を算出できる。つまりインデックス5、6、402の値がわかると、インデックス629の値を算出できる。このことを使って、63回目の推測時に正解を出すことができ、フラグが得られる。
#!/usr/bin/env python3 import socket import random 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 def temper(st): y = st y ^= y >> 11 y ^= (y << 7) & 0x9d2c5680 y ^= (y << 15) & 0xefc60000 y ^= y >> 18 return y def get_st624(st0, st1, st397): n = st0 & 0x80000000 n += st1 & 0x7fffffff st624 = st397 ^ (n >> 1) if n % 2 != 0: st624 ^= 0x9908b0df return st624 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect(('52.59.124.14', 10011)) stats = [] for _ in range(62): data = recvuntil(s, b'\n').rstrip() print(data) for i in range(9): data = recvuntil(s, b'\n').rstrip() print(data) stats.append(untemper(int(data))) data = recvuntil(s, b'\n').rstrip() print(data) print('1') s.sendall(b'1\n') stats.append(1) st629 = get_st624(stats[5], stats[6], stats[402]) resp = temper(st629) data = recvuntil(s, b'\n').rstrip() print(data) for i in range(9): data = recvuntil(s, b'\n').rstrip() print(data) data = recvuntil(s, b'\n').rstrip() print(data) print(str(resp)) s.sendall(str(resp).encode() + b'\n') data = recvuntil(s, b'\n').rstrip() print(data)
実行結果は以下の通り。
: Hints: 752697820 1260326397 2518899433 24091564 2878452702 3656174986 713195470 1648754691 4100648055 Guess: 1 Hints: 3648537072 3497416159 3633087672 2894461108 746236807 3004193502 2623530859 1416149530 1267855297 Guess: 4138154364 FLAG ENO{U_Gr4du4t3d_R4nd_4c4d3mY!}
ENO{U_Gr4du4t3d_R4nd_4c4d3mY!}
pythopia (rev)
PythonのASTで書かれているので、Pythonコードに起こしてみる。
import os if not os.path.exists('license.txt'): exit('File does not exist.') with open('license.txt') as f: key = f.read() if not len(key) == 64: raise Exception('Wrong key') key1 = key[:16] key2 = key[16:32] key3 = key[32:48] key4 = key[48:64] class KeyChkr: def __init__(self, k): self.k = k def check(self): k = self.k.upper().lower() if k == 'you_solved_it!!}': return True else: return False ok = False if key[0] == 'E': if key[1] == 'N': if key[2] == 'O': if key[3] == '{': if key[4] == 'L': if key[5] == '1': if key[6] == '3': if key[7] == '3': if key[8] == '3': if key[9] == '3': if key[10] == '3': if key[11] == '3': if key[12] == '3': if key[13] == '3': if key[14] == '3': if key[15] == '3': ok = True else: ok = False else: ok = False else: ok = False else: ok = False else: ok = False else: ok = False else: ok = False else: ok = False else: ok = False else: ok = False else: ok = False else: ok = False else: ok = False else: ok = False else: ok = False else: ok = False vals = [36, 76, 96, 102, 99, 118, 97, 76, 119, 102, 99, 118, 97, 76, 124, 120] for i, k in enumerate(key2): v = ord(key2[i]) ^ 19 if v != vals[i]: ok = False def check_key(k): if k[::-1] != '_!ftcnocllunlol_': return False return True if not check_key(key3): ok = False chk = KeyChkr(key4) ok = chk.check() if ok: print('Correct license!') else: print('Nope')
このコードからkey1~key4を求め、結合してフラグとなる。
#!/usr/bin/env python3 key1 = 'ENO{L13333333333' vals = [36, 76, 96, 102, 99, 118, 97, 76, 119, 102, 99, 118, 97, 76, 124, 120] key2 = ''.join([chr(v ^ 19) for v in vals]) key3 = '_!ftcnocllunlol_'[::-1] key4 = 'you_solved_it!!}' key = key1 + key2 + key3 + key4 print(key)
ENO{L133333333337_super_duper_ok_lolnullconctf!_you_solved_it!!}
reguest (web)
クッキーの"role"が"admin"で、"really"が"yes"の場合にフラグが表示される。
$ curl http://52.59.124.14:10014/ -b "role=admin" -b "really=yes" Usage: Look at the code ;-) Overwriting cookies with default value! This must be secure! Prepared request cookies are: [('role', 'guest'), ('really', 'yes')] Sending request... Request cookies are: [('role', 'guest'), ('really', 'yes')] Someone's drunk oO Response is: Admin: ENO{R3Qu3sts_4r3_s0m3T1m3s_we1rd_dont_get_confused}
ENO{R3Qu3sts_4r3_s0m3T1m3s_we1rd_dont_get_confused}
bmpass (crypto)
AES-ECB暗号化なので、16バイトごとに同じデータは同じ暗号になる。BMP前提で、BMPヘッダのみ付け画像にする。
0000-0001 "BM" 0002-0005 サイズ(= 90 a8 1f 00) 0006-0009 00 00 00 00 000a-000d オフセット(適当に36 00 00 00で設定) 000e-0011 28 00 00 00 0012-0015 画像の幅 0016-0019 画像の高さ 001a-001b 01 00 001c-001d 1画素当たりのデータサイズ(= RGBA = 20 00) 001e-0035 仮で00で埋める。
幅がわからないので、ブルートフォースで読めそうな画像を探す。
#!/usr/bin/env python3 with open('flag.bmp.enc', 'rb') as f: data = f.read() head = b'BM' head += b'\x90\xa8\x1f\x00' head += b'\x00\x00\x00\x00' head += b'\x36\x00\x00\x00' head += b'\x28\x00\x00\x00' tail = (512).to_bytes(4, 'little') tail += b'\x01\x00\x18\x00' tail += b'\x00' * 24 tail += data[0x36:0x40] for i in range(0x40, len(data), 16): d = data[i:i+16] if d == b'\x6b\xe1\x48\x5d\xc7\xd9\x3a\x14\xc7\x18\x85\x5a\x80\xa1\x9a\x87': tail += b'\x00' * 16 else: tail += b'\xff' * 16 for i in range(1, 2049): fname = 'img/flag_%04d.bmp' % i w = (i).to_bytes(4, 'little') d = head + w + tail with open(fname, 'wb') as f: f.write(d)
推測しつつ、幅1280の画像からフラグが読めそう。
ENO{I_c4N_s33_tHr0ugH_3ncrYpt10n}
twin (crypto)
nが同じで、異なるeでフラグを暗号化している。Common Modulus Attackで復号する。ただし、e1とe2のGCDは17であるため、e1, e2を17で割った値で復号し、Low Public-Exponent Attackで復号する。
#!/usr/bin/env python3 from Crypto.PublicKey import RSA from Crypto.Util.number import * import gmpy2 def commom_modulus_attack(c1, c2, e1, e2, n): gcd, s1, s2 = gmpy2.gcdext(e1, e2) if s1 < 0: s1 = -s1 c1 = gmpy2.invert(c1, n) elif s2 < 0: s2 = -s2 c2 = gmpy2.invert(c2, n) v = pow(c1, s1, n) w = pow(c2, s2, n) x = (v*w) % n return x key1 = RSA.import_key(open('key1.pem','rb').read()) key2 = RSA.import_key(open('key2.pem','rb').read()) assert key1.n == key2.n n = key1.n e1 = key1.e e2 = key2.e e = GCD(e1, e2) e1 = e1 // e e2 = e2 // e with open('ciphers', 'r') as f: params = f.read().splitlines() c1 = int(params[0]) c2 = int(params[1]) m = commom_modulus_attack(c1, c2, e1, e2, n) m, success = gmpy2.iroot(m, e) assert success == True flag = long_to_bytes(m).decode() print(flag)
ENO{5har1ng_is_n0t_c4r1ng}