この大会は2022/6/25 10:00(JST)~2022/6/27 10:00(JST)に開催されました。
今回もチームで参戦。結果は980点で310チーム中53位でした。
自分で解けた問題をWriteupとして書いておきます。
signin (Misc)
$ file flag flag: bzip2 compressed data, block size = 900k $ mv flag flag.bz2 $ bzip2 -d flag.bz2 $ file flag flag: XZ compressed data $ xz -d flag.xz xz: flag: ファイルのパーミッションを設定できません: 定義されたデータ型に対して値が大きすぎます $ file flag flag: bzip2 compressed data, block size = 900k $ mv flag flag.bz2 $ bzip2 -d flag.bz2 $ file flag flag: LZMA compressed data, streamed $ mv flag flag.lzma $ xz --format=lzma --decompress flag.lzma xz: flag: ファイルのパーミッションを設定できません: 定義されたデータ型に対して値が大きすぎます $ file flag flag: gzip compressed data, was "flag", last modified: Thu Jun 23 10:23:44 2022, from Unix $ mv flag flag.gz $ gzip -d flag.gz $ file flag flag: LZMA compressed data, streamed $ mv flag flag.lzma $ xz --format=lzma --decompress flag.lzma xz: flag: ファイルのパーミッションを設定できません: 定義されたデータ型に対して値が大きすぎます $ file flag flag: gzip compressed data, was "flag", last modified: Thu Jun 23 10:23:44 2022, from Unix :
さまざまな圧縮方式で何重にも圧縮されているので、fileコマンドで確認しながら、解凍していく。
#!/usr/bin/env python3 import subprocess import os import time cmd_file = 'file %s' cmd_bz2 = 'bzip2 -d %s' cmd_xz = 'xz -d %s' cmd_lzma = 'xz --format=lzma --decompress %s' cmd_gz = 'gzip -d %s' cmd_zst = 'zstd -d %s' filename_flag = 'flag' filename_bz2 = 'flag.bz2' filename_xz = 'flag.xz' filename_lzma = 'flag.lzma' filename_gz = 'flag.gz' filename_zst = 'flag.zst' i = 1 while True: print('%d times' % i) cmd = cmd_file % filename_flag ret = subprocess.check_output(cmd.split(' ')).rstrip().decode() print(ret) if 'bzip2 compressed' in ret: os.rename(filename_flag, filename_bz2) os.system(cmd_bz2 % filename_bz2) elif 'XZ compressed' in ret: os.rename(filename_flag, filename_xz) os.system(cmd_xz % filename_xz) elif 'LZMA compressed' in ret: os.rename(filename_flag, filename_lzma) os.system(cmd_lzma % filename_lzma) elif 'gzip compressed' in ret: os.rename(filename_flag, filename_gz) os.system(cmd_gz % filename_gz) elif 'Zstandard compressed' in ret: os.rename(filename_flag, filename_zst) os.system(cmd_zst % filename_zst) else: break time.sleep(2) i += 1
実行結果は以下の通り。
: 1017 times flag: XZ compressed data xz: flag: ファイルのパーミッションを設定できません: 定義されたデータ型に対して値が大きすぎます 1018 times flag: bzip2 compressed data, block size = 900k 1019 times flag: gzip compressed data, was "flag", last modified: Thu Jun 23 10:23:44 2022, from Unix 1020 times flag: Zstandard compressed data (v0.8+), Dictionary ID: None flag.zst : 259 bytes 1021 times flag: Zstandard compressed data (v0.8+), Dictionary ID: None flag.zst : 246 bytes 1022 times flag: bzip2 compressed data, block size = 900k 1023 times flag: bzip2 compressed data, block size = 900k 1024 times flag: XZ compressed data xz: flag: ファイルのパーミッションを設定できません: 定義されたデータ型に対して値が大きすぎます 1025 times flag: ASCII text
$ cat flag ACTF{r0cK_4Nd_rolL_1n_C0mpr33s1ng_aNd_uNCOmrEs5iNg}
ACTF{r0cK_4Nd_rolL_1n_C0mpr33s1ng_aNd_uNCOmrEs5iNg}
Mahjoong (Misc)
麻雀ゲームができるようなので、普通に勝負してみる。1勝したが、特にフラグは表示されない。継続して、もう一度勝負してみる。フラグ表示の条件はわからないが、ゲーム中にフラグが表示された。
ACTF{y@kumAn_1s_incredl3le}
impossible RSA (Crypto)
コードにあるパラメータの条件から、式を変形する。
e * q = p * A + 1 ↓ e * p * q = p * p * A + p ↓ A*p**2 + p - e*n = 0
Aはそれほど大きい数値でないと推測できるので、Aのブルートフォースで2次方程式の解が整数になるものを探し、p, qを求める。あとは通常通り復号しフラグを求める。
#!/usr/bin/env python3 from Crypto.Util.number import * from Crypto.PublicKey import RSA from sympy import * with open('public.pem', 'r') as f: pub_data = f.read() pubkey = RSA.importKey(pub_data) n = pubkey.n e = pubkey.e with open('flag', 'rb') as f: c = bytes_to_long(f.read()) A = 1 found = False while True: p = symbols('p') eq = Eq(A * p ** 2 + p - e * n, 0) sol = solve(eq, p) for p in sol: if p.is_Integer: found = True print('[+] A =', A) break if found: break A += 1 p = int(p) q = n // p assert p * q == n print('[+] p =', p) print('[+] q =', q) phi = (p - 1) * (q - 1) d = inverse(e, phi) m = pow(c, d, n) flag = long_to_bytes(m).decode() print('[*] flag =', flag)
実行結果は以下の通り。
[+] A = 46280 [+] p = 150465840847587996081934790667651610347742504431401795762471467800785876172317705268993152743689967775266712089661128372295606682852482012493939368044600366794969553828079064622047080051569090177885299781981209120854290564064662058027679075401901717932024549311396484660557278975525859127898004619405319768113 [+] q = 106253858346069738600667441477316882476975191191010804704017265511396163224664897689076447029585908855140507431062102645373463498213419404889139172575859514095414665779078979976323891310048026205540865067215318951327289428947198682355325809994354509756230772573224732747769822710641878029801786071777441733193 [*] flag = ACTF{F1nD1nG_5pEcia1_n_i5_nOt_eA5y}
ACTF{F1nD1nG_5pEcia1_n_i5_nOt_eA5y}
secure connection (Crypto)
master.txtの内容と合わせ、サーバの処理概要を考える。
・handler = connection_handle_socket(s, "master", args.dump) 送受信のオブジェクト生成(ダンプファイルは"master.txt") ・connection_engine(handler, "master", args.encrypt) ・state = connection_state("master", args.encrypt) ・state.role = "master" ・state.local_counter = 0 ・state.remote_counter = 0 ・state.encrypt = args.encrypt ・state.initCRC = b"" ・master_hello_procedure(handler, state) ・handler.send(state.prepare_hello_packet()) ・hello_packet = int(state.encrypt << 7 | 1).to_bytes(1, "little") ・hello_packet += "\x03" ・state.initCRC = ランダム3バイト ・hello_packet += state.initCRC ・hello_packet += state.calc_crc(hello_packet) →helloパケット送信 ・state.inc_local_counter() ・state.local_counter += 1 ・hello_pkt = handler.recv(2 + 255 + 3) →helloパケット受信 ・state.inc_remote_counter() ・state.remote_counter += 1 ・encrypt_or_not = (hello_pkt[0] >> 7) & 0b1 →暗号化しない場合、True返却 ・handler.send(state.prepare_sc_request_packet()) ・sc_request_packet += int(128 | 2).to_bytes(1, "little") ・sc_request_packet += "\x10" ・IV: ランダム8バイト ・Secret: ランダム8バイト ・state.IVm = IV ・state.Secretm = Secret ・sc_request_packet += IV ・sc_request_packet += Secret ・sc_request_packet += state.calc_crc(sc_request_packet) ・state.inc_local_counter() ・state.local_counter += 1 ・numeric_key: 入力 ・numeric_key = numeric_key % 0x1000000 ・state.numeric_key = numeric_key ・state.numeric_key_bytes = (numeric_key).to_bytes(16, "little") ・sc_respond_pkt: パケット受信 ・state.inc_remote_counter() ・state.remote_counter += 1 ・sc_respond_pkt[0] & 0x3fと3が一致していない場合、Falseを返す。 ・recvIVs = sc_respond_pkt[2:10] ・recvSecrets = sc_respond_pkt[10:18] ・crc = sc_respond_pkt[18: 18 + 3] ・should_crc = state.calc_crc(sc_respond_pkt[:18]) ・crcとshould_crcが一致していない場合、Falseを返す。 ・handler.send(state.prepare_master_confirm_packet()) ・master_confirm_packet += int(128 | 4).to_bytes(1, "little") ・master_confirm_packet += "\x10" ・master_random: ランダム16バイト ・master_confirm = secure_confirm( state.numeric_key_bytes, master_random, b"\x00" * 16, b"\xff" * 16) ・secure_encrypt(state.numeric_key_bytes, bytes_xor_16(secure_encrypt(state.numeric_key_bytes, master_random), b"\xff" * 16)) ・state.MRandom = master_random ・state.MConfirm = master_confirm ・master_confirm_packet += master_confirm ・master_confirm_packet += state.calc_crc(master_confirm_packet) ・state.inc_local_counter() ・state.local_counter += 1 ・sconfirm_pkt: パケット受信 ・state.inc_remote_counter() ・state.remote_counter += 1 ・sconfirm_pkt[0] & 0x3fと5が一致していない場合、Falseを返す。 ・recvSConfirm = sconfirm_pkt[2:18] ・crc = sconfirm_pkt[18:18 + 3] ・should_crc = state.calc_crc(sconfirm_pkt[:18]) ・crcとshould_crcが一致していない場合、Falseを返す。 ・state.SConfirm = recvSConfirm ・handler.send(state.prepare_master_random_packet()) ・master_random_packet += int(128 | 6).to_bytes(1, "little") ・master_confirm_packet += "\x10" ・master_random_packet += state.MRandom ・master_random_packet += state.calc_crc(master_random_packet) ・state.inc_local_counter() ・state.local_counter += 1 ・srandom_pkt: パケット受信 ・state.inc_remote_counter() ・state.remote_counter += 1 ・srandom_pkt[0] & 0x3fと7が一致していない場合、Falseを返す。 ・recvSRandom = srandom_pkt[2:18] ・crc = srandom_pkt[18:18 + 3] ・should_crc = state.calc_crc(srandom_pkt[:18]) ・crcとshould_crcが一致していない場合、Falseを返す。 ・state.check_slave_confirm(recvSRandom)がFalseの場合、Falseを返す。 ・should_Sconfirm = secure_confirm( state.numeric_key_bytes, recvSRandom, b"\x00" * 16, b"\xff" * 16) ・state.SConfirmとshould_Sconfirmが一致していない場合、Falseを返す。 ・state.setup_session() ・state.storekey = secure_encrypt( state.numeric_key_bytes, state.MRandom[:8] + state.SRandom[8:]) self.sessionkey = secure_encrypt( state.storekey, state.Secretm + state.Secrets) ・以下繰り返し ・メニュー選択 ・0を選択した場合、recieve_message(state, handler) ・dataheader: 2バイト受信 ・more_data = (dataheader[0] >> 6) & 0b1 ・opcode = dataheader[0] & 0x3f(PktOpcode.DATA.value) ・datalength = dataheader[1] ・payload = handler.recv(datalength) ・crc = handler.recv(3) ・should_crc = state.calc_crc(dataheader + payload) ・payload = state.decrypt_data_packet(payload) ・state.inc_remote_counter() ・crcとshould_crcが異なる場合はエラー ※more_dataが0になるまで、payloadを結合する。 ・1を選択した場合、send_message(state, handler) ・data: 入力 ・encoded_data: dataのbase64エンコード ・encoded_data_len: encoded_dataの長さ ・encoded_dataを255バイトごとに以下を実行 ・moreData: 次のデータの有無(True / False) ・data_segment: encoded_dataの対象の255バイト ・data_packet = state.prepare_data_packet(data_segment, moreData) ・data_packet = b"" ・data_packet += int(state.encrypt << 7 | moredata << 6 | 8).to_bytes(1, "little") ・data_packet += len(data_segment).to_bytes(1, "little") ・state.encryptがTrueの場合、 data_packet += secure_encrypt_packet(state.sessionkey, data_segment, (state.local_counter).to_bytes(13, "little") ・AES-CCM暗号化(nonce=state.local_counter) ・state.encryptがFalseの場合、 data_packet += data_segment ・data_packet += state.calc_crc(data_packet) →データパケット送信 ・state.inc_local_counter() ・state.local_counter += 1
各種パラメータを割り出していく。その際numeric_keyについては0x1000000未満なので、state.MRandomの暗号とstate.MConfirmが同じになるものを探す。あとはそのパラメータを使って、送信データのみ復号する。
#!/usr/bin/env python3 import base64 from Crypto.Cipher import AES import libscrc def bytes_xor_16(bytes1, bytes2): v1 = int.from_bytes(bytes1, 'big') v2 = int.from_bytes(bytes2, 'big') v3 = v1 ^ v2 return (v3).to_bytes(16, 'big') def secure_encrypt(key, plain): aes = AES.new(key=key, mode=AES.MODE_ECB) return aes.encrypt(plain) def secure_decrypt_packet(key, plain, nonce): aes = AES.new(key=key, mode=AES.MODE_CCM, nonce=nonce) return aes.decrypt(plain) def secure_confirm(key, r, p1, p2): return secure_encrypt(key, bytes_xor_16(secure_encrypt(key, bytes_xor_16(r, p1)), p2)) def calc_crc(initCRC, pdu): initvalue = int.from_bytes(initCRC, "little") crc = libscrc.hacker24(data=pdu, poly=0x00065B, init=initvalue, xorout=0x00000000, refin=True, refout=True) return crc.to_bytes(3, "little") with open('master.txt', 'r') as f: lines = f.read().splitlines() ######## 1st communication data ######## dirs = [] data = [] for line in lines[:31]: dir = line.rstrip().split('\t')[0] dat = line.rstrip().split('\t')[1].split(' ') d = b''.join([bytes([int(d, 16)]) for d in dat]) if dir != '': dirs.append(dir.encode()) data.append(d) else: data[-1] = data[-1] + d ######## 1st hello packet ######## assert dirs[0] == b'>' assert data[0][0] & 1 == 1 encrypt = (data[0][0] >> 7) & 1 assert encrypt == False assert data[0][1] == 3 initCRC = data[0][2:5] assert data[0][5:8] == calc_crc(initCRC, data[0][0:5]) assert dirs[1] == b'<' assert data[1] == data[0] ######## 1st data packet (not encrypted) ######## for i in range(3): assert dirs[i*4+2] == b'>' assert data[i*4+2][0] & 8 == 8 encrypt = (data[i*4+2][0] >> 7) & 1 assert encrypt == False more_data = (data[i*4+2][0] >> 6) & 1 assert more_data == False length = data[i*4+2][1] payload = data[i*4+2][2:2+length] assert data[i*4+2][2+length:2+length+3] == calc_crc(initCRC, data[i*4+2][0:2+length]) inp_data = base64.b64decode(payload).decode() print(inp_data) if i == 2: break assert dirs[i*4+3] == b'<' more_data = (data[i*4+3][0] >> 6) & 0b1 assert more_data == False opcode = data[i*4+3][0] & 0x3f assert opcode == 8 length = data[i*4+3][1] assert dirs[i*4+4] == b'<' payload = data[i*4+4] assert dirs[i*4+5] == b'<' crc = data[i*4+5] assert data[i*4+5] == calc_crc(initCRC, data[i*4+3] + data[i*4+4]) inp_data = base64.b64decode(payload).decode() print(inp_data) ######## 2nd hello packet ######## local_counter = 0 remote_counter = 0 assert dirs[11] == b'>' assert data[11][0] & 1 == 1 encrypt = (data[11][0] >> 7) & 1 assert encrypt == True assert data[11][1] == 3 initCRC = data[11][2:5] assert data[11][5:8] == calc_crc(initCRC, data[11][0:5]) assert dirs[12] == b'<' assert data[12] == data[11] local_counter += 1 remote_counter += 1 assert dirs[13] == b'>' assert data[13][0] == 128 | 2 assert data[13][1] == 16 IVm = data[13][2:10] Secretm = data[13][10:18] assert data[13][18:21] == calc_crc(initCRC, data[13][0:18]) local_counter += 1 remote_counter += 1 assert dirs[14] == b'<' assert data[14][0] & 0x3f == 3 IVs = data[14][2:10] Secrets = data[14][10:18] assert data[14][18:21] == calc_crc(initCRC, data[14][0:18]) assert lines[31].rstrip().split('\t')[0].encode() == b'>' data15_1 = lines[31].rstrip().split('\t')[1].split(' ') data15_2 = lines[32].rstrip().split('\t')[1].split(' ') d15_1 = b''.join([bytes([int(d, 16)]) for d in data15_1]) d15_2 = b''.join([bytes([int(d, 16)]) for d in data15_2[2:]]) assert d15_1[0] == 128 | 4 assert d15_1[1] == 16 for x in range(65536): X = x.to_bytes(2, "little") crc = calc_crc(initCRC, d15_1 + X) if crc == d15_2: break d15 = d15_1 + X + d15_2 MConfirm = d15[2:18] local_counter += 1 remote_counter += 1 assert lines[33].rstrip().split('\t')[0].encode() == b'<' data16_1 = lines[33].rstrip().split('\t')[1].split(' ') data16_2 = lines[34].rstrip().split('\t')[1].split(' ') assert int(data16_1[0], 16) & 0x3f == 5 assert lines[35].rstrip().split('\t')[0].encode() == b'>' data17_1 = lines[35].rstrip().split('\t')[1].split(' ') data17_2 = lines[36].rstrip().split('\t')[1].split(' ') d17_1 = b''.join([bytes([int(d, 16)]) for d in data17_1]) d17_2 = b''.join([bytes([int(d, 16)]) for d in data17_2]) d17 = d17_1 + d17_2 assert d17[0] == 128 | 6 assert d17[1] == 16 MRandom = d17[2:18] assert d17[18:] == calc_crc(initCRC, d17[0:18]) local_counter += 1 remote_counter += 1 #for numeric_key in range(0x1000000): for numeric_key in range(9190693, 0x1000000): numeric_key_bytes = (numeric_key).to_bytes(16, "little") master_confirm = secure_confirm(numeric_key_bytes, MRandom, b"\x00" * 16, b"\xff" * 16) if master_confirm == MConfirm: break print('\n[+] numeric_key:', numeric_key) print() numeric_key_bytes = (numeric_key).to_bytes(16, "little") assert lines[37].rstrip().split('\t')[0].encode() == b'<' data18_1 = lines[37].rstrip().split('\t')[1].split(' ') data18_2 = lines[38].rstrip().split('\t')[1].split(' ') d18_1 = b''.join([bytes([int(d, 16)]) for d in data18_1[:14]]) d18_2 = bytes([int(data18_1[15], 16)]) d18_3 = b''.join([bytes([int(d, 16)]) for d in data18_2]) assert d18_1[0] & 0x3f == 7 for x in range(256): X = x.to_bytes(1, "little") crc = calc_crc(initCRC, d18_1 + X + d18_2 + d18_3[:2]) if crc == d18_3[2:]: break d18 = d18_1 + X + d18_2 + d18_3 recvSRandom = d18[2:18] SConfirm = secure_confirm(numeric_key_bytes, recvSRandom, b"\x00" * 16, b"\xff" * 16) storekey = secure_encrypt(numeric_key_bytes, MRandom[:8] + recvSRandom[8:]) sessionkey = secure_encrypt(storekey, Secretm + Secrets) ######## 2nd communication data (only send message) ######## dirs = [] data = [] for line in lines[39:]: dir = line.rstrip().split('\t')[0] dat = line.rstrip().split('\t')[1].split(' ') d = b''.join([bytes([int(d, 16)]) for d in dat]) if dir != '': dirs.append(dir.encode()) data.append(d) else: data[-1] = data[-1] + d send_data = [] for i in range(len(dirs)): if dirs[i] == b'>': send_data.append(data[i]) ######## 2nd data packet (encrypted) (only send message) ######## i = 0 while i < len(send_data): payload = b'' while True: assert send_data[i][0] & 8 == 8 encrypt = (send_data[i][0] >> 7) & 1 assert encrypt == True more_data = (send_data[i][0] >> 6) & 1 length = send_data[i][1] body = send_data[i][2:2+length] body = secure_decrypt_packet(sessionkey, body, (local_counter).to_bytes(13, "little")) payload += body i += 1 local_counter += 1 if more_data == False: break inp_data = base64.b64decode(payload).decode() print(inp_data)
実行結果は以下の通り。
Hello there, long time no see, zraxx yeah, I am quite busy making ACTF crypto challenges well, I can offer you a not bad signin challenge show me let's first dive into secure connection [+] numeric_key: 9190693 I will tell you my flag after you finish your poem No I mean this one, I never saw a Moor-I never saw the Sea-Yet know I how the Heather looksAnd what a Billow be.I never spoke with GodNor visited in Heaven-Yet certain am I of the spotAs if the Checks were given- You got your flag: ACTF{ShORt_NUmeR1c_KEY_1s_Vuln3R4bLe_TO_e@V3sDropPEr}
ACTF{ShORt_NUmeR1c_KEY_1s_Vuln3R4bLe_TO_e@V3sDropPEr}
signoff (Misc)
アンケートに答えたら、フラグが表示された。
ACTF{YOu_4Re_4WEsOme_3njoy_tHE_v4cAT1on}