この大会は2024/7/20 4:00(JST)~2024/7/22 4:00(JST)に開催されました。
今回もチームで参戦。結果は1782点で1457チーム中141位でした。
自分で解けた問題をWriteupとして書いておきます。
sanity-check (MISC)
問題にフラグが書いてあった。
ictf{this_isnt_real}
discord (MISC)
Discordに入り、#imaginaryctf-2024チャネルのトピックやメッセージにフラグがあった。
ictf{fake_flag_for_testing}
unoriginal (REVERSING)
Ghidraでデコンパイルする。
undefined8 main(void) { int iVar1; long in_FS_OFFSET; int local_4c; byte local_48 [56]; long local_10; local_10 = *(long *)(in_FS_OFFSET + 0x28); printf("Enter your flag here: "); gets((char *)local_48); for (local_4c = 0; local_4c < 0x30; local_4c = local_4c + 1) { local_48[local_4c] = local_48[local_4c] ^ 5; } iVar1 = strcmp((char *)local_48,"lfqc~opvqZdkjqm`wZcidbZfm`fn`wZd6130a0`0``761gdx"); if (iVar1 == 0) { puts("Correct!"); } else { puts("Incorrect."); } if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) { /* WARNING: Subroutine does not return */ __stack_chk_fail(); } return 0; }
5とXORして、"lfqc~opvqZdkjqm`wZcidbZfm`fn`wZd6130a0`0``761gdx"になればよいので、XORで元に戻す。
>>> s = "lfqc~opvqZdkjqm`wZcidbZfm`fn`wZd6130a0`0``761gdx"
>>> ''.join([chr(ord(c) ^ 5) for c in s])
'ictf{just_another_flag_checker_a3465d5e5ee234ba}'
ictf{just_another_flag_checker_a3465d5e5ee234ba}
readme (WEB)
Dockerfileのある行にフラグが設定されていた。
ENV FLAG="ictf{path_normalization_to_the_rescue}"
ictf{path_normalization_to_the_rescue}
bom (FORENSICS)
$ strings chal.txt ictf{th4t_isn7_chin3se}
ictf{th4t_isn7_chin3se}
packed (FORENSICS)
$ file routed.pkz routed.pkz: Zip archive data, at least v2.0 to extract, compression method=deflate $ unzip routed.pkz Archive: routed.pkz inflating: routed.pkt inflating: cscoptlogo177x111.jpg inflating: secret.png
secret.pngにフラグが書いてあった。

ictf{ab4697882634d4aeb6f21141ea2724d0}
base64 (CRYPTO)
64進数への変換のような処理をしているので、10進数に変換し、文字列にすればフラグになる。
#!/usr/bin/env python3 from Crypto.Util.number import * q = 64 with open('out.txt', 'r') as f: secret_key = eval(f.read().split(' = ')[1]) flag_int = 0 for k in secret_key[::-1]: flag_int *= q flag_int += k flag = long_to_bytes(flag_int).decode() print(flag)
ictf{b4se_c0nv3rs1on_ftw_236680982d9e8449}
integrity (CRYPTO)
RSA暗号で、n、eとctがわかっている。その他に以下の値がわかっている。
signature = pow(flag, crc_hqx(long_to_bytes(d), 42), n)
crc_hqxの値は16ビットのため、65536未満の値になる。この値をe2とすると、以下の2つの値がわかっていることになる。
ct = pow(flag, e, n) signature = pow(flag, e2, n)
e2のブルートフォースで、Common Modulus Attackで復号し、フラグの形式になるものを探す。
#!/usr/bin/env python3 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 with open('out.txt', 'r') as f: params = f.read().splitlines() e = 65537 n = int(params[0].split(' ')[-1]) ct = int(params[1].split(' ')[-1]) signature = int(params[2].split(' ')[-1]) for e2 in range(65536): m = commom_modulus_attack(ct, signature, e, e2, n) flag = long_to_bytes(m) if flag.startswith(b'ictf{'): flag = flag.decode() print(flag) break
ictf{oops_i_leaked_some_info}
tango (CRYPTO)
サーバの処理概要は以下の通り。
・KEY: ランダムな32バイト文字列
・以下繰り返し
・option: 入力
・optionが"E"の場合
・command: 入力
・encrypt_command(command)
・commandの長さが3以外の場合、optionの入力からやり直し
・cipher = Salsa20.new(key=KEY)
・nonce = cipher.nonce
・data = json.dumps({'user': 'user', 'command': command, 'nonce': [ランダム8バイト文字列の16進数表記文字列]}).encode('ascii')
・checksum: dataのCRC32の値を文字列にしたもの
・ciphertext = cipher.encrypt(data)
・nonce + checksum + ciphertextを16進数表記で表示
・optionが"R"の場合
・packet: 入力
・run_command(packet)
・packet: packetをhexデコード
・nonce: packetの先頭8バイト
・checksum: packetの8バイト目から11バイト目までを数値化したもの
・ciphertext: packetの12バイト目以降
・cipher = Salsa20.new(key=KEY, nonce=nonce)
・plaintext = cipher.decrypt(ciphertext)
・plaintextのCRC32がchecksumと一致していない場合、optionの入力からやり直し
・data: plaintextをjsonとしてパースしたデータ
・user: dataの"user"データ
・command: dataの"command"データ
・commandが"nop"の場合、対応するメッセージを表示
・commandが"sts"の場合
・userが"user"または"root"にない場合、対応するメッセージを表示
・userが"user"または"root"の場合、対応するメッセージを表示
・commandが"flag"の場合
・userが"root"以外の場合、対応するメッセージを表示
・userが"root"の場合、フラグを表示
・optionが"Q"の場合、終了Salsa20はkeyとnonceが同じ場合、平文と暗号文のXORは同じになる。
適当な3文字のコマンドを指定し、以下のデータを暗号化する。
{"user": "user", "command": "sts", "nonce": "xxxxxxxxxxxxxxxx(16進数)"} 作成したいデータは以下のデータの暗号化データ。nonceは使わないので不要。
{"user": "root", "command": "flag"}あとはSalsa20のnonceを同じにすれば、XORが同じになることから、暗号文を生成して送信すればよい。
#!/usr/bin/env python3 import socket from Crypto.Util.number import * from zlib import crc32 from Crypto.Util.strxor import strxor def recvuntil(s, tail): data = b'' while True: if tail in data: return data.decode() data += s.recv(1) data1 = b'{"user": "user", "command": "sts", "nonce": "0000000000000000"}' data2 = b'{"user": "root", "command": "flag"}' s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect(('tango.chal.imaginaryctf.org', 1337)) data = recvuntil(s, b'\n').rstrip() print(data) data = recvuntil(s, b'\n').rstrip() print(data) data = recvuntil(s, b'> ') print(data + 'E') s.sendall(b'E\n') data = recvuntil(s, b': ') print(data + 'sts') s.sendall(b'sts\n') data = recvuntil(s, b'\n').rstrip() print(data) packet = bytes.fromhex(data.split(' ')[-1]) nonce = packet[:8] checksum = bytes_to_long(packet[8:12]) ciphertext = packet[12:] key = strxor(data1, ciphertext)[:len(data2)] ciphertext = strxor(data2, key) checksum = long_to_bytes(crc32(data2)) packet = (nonce + checksum + ciphertext).hex() data = recvuntil(s, b'> ') print(data + 'R') s.sendall(b'R\n') data = recvuntil(s, b': ') print(data + packet) s.sendall(packet.encode() + b'\n') data = recvuntil(s, b'\n').rstrip() print(data)
実行結果は以下の通り。
== proof-of-work: disabled ==
Welcome to the Tango server! What would you like to do?
[E]ncrypt a command
[R]un a command
[Q]uit
> E
Your command: sts
Your encrypted packet is: e2e6c43b2f4be3869dd001a812dde02fb3114297cc78f42c637e8722208c706ea5fdc74f8a420ebe8b9ef1c6d0c530fec0d0cb0774b01578482cbbfc0410621f6bc4db9a3c4a7e9f88a4de
[E]ncrypt a command
[R]un a command
[Q]uit
> R
Your encrypted packet (hex): e2e6c43b2f4be3863feb6cae12dde02fb3114297cc78f33069788722208c706ea5fdc74f8a420ebe8b8be9d495cb6d
ictf{F0xtr0t_L1m4_4lph4_G0lf}
ictf{F0xtr0t_L1m4_4lph4_G0lf}