この大会は2020/3/7 9:00(JST)~2020/3/9 9:00(JST)に開催されました。
今回もチームで参戦。結果は1713点で1001チーム中122位でした。
自分で解けた問題をWriteupとして書いておきます。
Sanity Check (Misc)
問題文にフラグが書いてあった。
utflag{this_is_the_flag}
[basics] reverse engineering (Reverse Engineering)
$ strings calc | grep utflag utflag{str1ngs_1s_y0ur_fr13nd}
utflag{str1ngs_1s_y0ur_fr13nd}
.PNG2 (Reverse Engineering)
バイナリの構成は以下のようになっていると推測できる。
0x0000-0x0003 "PNG2"固定 0x0004-0x0009 "width="固定 0x000a-0x000b 幅の値 0x000c-0x0012 "height="固定 0x0013-0x0014 高さの値 0x0015以降 3バイトごとに各ピクセルのRGBの値
このことを元に画像を生成してみる。
from PIL import Image from struct import * with open('pic.png2', 'rb') as f: data = f.read() width = unpack('>H', data[10:12])[0] height = unpack('>H', data[19:21])[0] colors = data[21:] img = Image.new('RGB', (width, height), (255, 255, 255)) i = 0 for y in range(height): for x in range(width): r = ord(colors[i]) g = ord(colors[i+1]) b = ord(colors[i+2]) i += 3 img.putpixel((x, y), (r, g, b)) img.save('flag.png')
画像を復元すると、フラグが表記されていた。
utflag{j139adfo_93u12hfaj}
[basics] forensics (Forensics)
$ file secret.jpeg secret.jpeg: UTF-8 Unicode text, with CRLF line terminators $ cat secret.jpeg | grep utflag utflag{fil3_ext3nsi0ns_4r3nt_r34l}
utflag{fil3_ext3nsi0ns_4r3nt_r34l}
Observe Closely (Forensics)
pngの末尾にzipが付いているので、抽出してextract.zipとして保存する。
$ unzip extract.zip Archive: extract.zip inflating: hidden_binary $ file hidden_binary hidden_binary: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/l, BuildID[sha1]=101705e6f60a300ab34f377a87fdb67f92996732, for GNU/Linux 3.2.0, not stripped $ ./hidden_binary Ah, you found me! utflag{2fbe9adc2ad89c71da48cabe90a121c0}
utflag{2fbe9adc2ad89c71da48cabe90a121c0}
Spectre (Forensics)
Sonic Visualiserで開き、スペクトログラムを見ると、フラグが現れた。
utflag{sp3tr0gr4m0ph0n3}
1 Frame per Minute (Forensics)
wavの画像受信データをデコードする必要がある。https://github.com/colaclanth/sstvのツールを使う。
$ sstv -d signals.wav -o flag.png [sstv] Searching for calibration header... Found! [sstv] Detected SSTV mode Martin 1 [sstv] Decoding image... [#############################################] 100% [sstv] Drawing image data... [sstv] ...Done!
出力された画像にフラグが書かれていた。
utflag{6bdfeac1e2baa12d6ac5384cdfd166b0}
The Legend of Hackerman, Pt. 1 (Forensics)
pngヘッダ4バイトが壊れているので修正すると、画像にフラグが書かれていた。
utflag{3lit3_h4ck3r}
The Legend of Hackerman, Pt. 2 (Forensics)
docxをzip解凍する。Hacker\word\media\image23.pngにフラグが書かれている。
utflag{unz1p_3v3ryth1ng}
[basics] crypto (Cryptography)
2進数のコードが並んでいるので、デコードする。すると途中からbase64文字列になるので、さらにデコードする。今度は途中から今度はシーザー暗号になっているので復号する。
import string def caesar(s, key): d = '' for c in s: code = ord(c) if c in string.uppercase: code = code - key if code < ord('A'): code += 26 elif c in string.lowercase: code = code - key if code < ord('a'): code += 26 d += chr(code) return d with open('binary.txt', 'r') as f: codes = f.read().rstrip() codes = codes.split(' ') dec = '' for code in codes: dec += chr(int(code, 2)) pt0 = dec.split('\n')[0] print pt0 ct0 = dec.split('\n')[1] dec = ct0.decode('base64') pt1 = dec.split('\n')[0] print pt1 ct1 = dec.split('\n')[1] ct2 = dec.split('\n')[2] dec = caesar(ct1, 10) print dec dec = caesar(ct2, 10) print dec
実行結果は以下の通り。
Uh-oh, looks like we have another block of text, with some sort of special encoding. Can you figure out what this encoding is? (hint: if you look carefully, you'll notice that there only characters present are A-Z, a-z, 0-9, and sometimes / and +. See if you can find an encoding that looks like this one.) New challenge! Can you figure out what's going on here? It looks like the letters are shifted by some constant. (hint: you might want to start looking up Roman people). alright, you're almost there! Now for the final (and maybe the hardest...) part: a substitution cipher. In the following text, I've taken my message and replaced every alphabetic character with a correspondence to a different character - known as a substitution cipher. Can you find the final flag? hint: We know that the flag is going to be of the format utflag{...} - which means that if you see that pattern, you know what the correspondences for u, t, f, l a, and g are. You can probably work out the remaining characters by replacing them and inferring common words in the English language. Another great method is to use frequency analysis: we know that 'e' shows up most often in the alphabet, so that's probably the most common character in the text, followed by 't', and so on. Once you know a few characters, you can infer the rest of the words based on common words that show up in the English language. hwxdnitvoitjwxk! gwv yiqa sjxjkyau tya padjxxan hngbtwdnibyg hyiooaxda. yana jk i soid swn ioo gwvn yinu asswntk: vtsoid{x0l_ty4tk_ly4t_j_h4oo_hngbt0}. gwv ljoo sjxu tyit i owt ws hngbtwdnibyg jk fvkt pvjoujxd wss tyjk kwnt ws pikjh rxwloauda, ixu jt naioog jk xwt kw piu istan ioo. ywba gwv axfwgau tya hyiooaxda!
最後の一行は換字式暗号になっているので、quipqiupで復号する。
congratulations! you have finished the beginner cryptography challenge. here is a flag for all your hard efforts: utflag{n0w_th4ts_wh4t_i_c4ll_crypt0}. you will find that a lot of cryptography is just building off this sort of basic knowledge, and it really is not so bad after all. hope you enjoyed the challenge!
復号した文章の中にフラグが含まれていた。
utflag{n0w_th4ts_wh4t_i_c4ll_crypt0}
One True Problem (Cryptography)
https://github.com/SpiderLabs/cribdragを使って推測していく。
$ python xorstrings.py 213c234c2322282057730b32492e720b35732b2124553d354c22352224237f1826283d7b0651 3b3b463829225b3632630b542623767f39674431343b353435412223243b7f162028397a103e 1a0765740a007316651000666f0d04740c146f10106e0801796317010018000e06000401166f $ python cribdrag.py 1a0765740a007316651000666f0d04740c146f10106e0801796317010018000e06000401166f : Your message is currently: 0 THE BEST CTF CATEGORY IS CRYPTOGRAPHY! Your key is currently: 0 NO THE BEST ONE IS BINARY EXPLOITATION
xor keyがフラグになる。
from Crypto.Util.strxor import strxor ct1 = '213c234c2322282057730b32492e720b35732b2124553d354c22352224237f1826283d7b0651'.decode('hex') ct2 = '3b3b463829225b3632630b542623767f39674431343b353435412223243b7f162028397a103e'.decode('hex') pt1 = 'THE BEST CTF CATEGORY IS CRYPTOGRAPHY!' pt2 = 'NO THE BEST ONE IS BINARY EXPLOITATION' key = strxor(pt1, ct1) print key key = strxor(pt2, ct2) print key index = key.index('}') flag = key[:index + 1] print flag
実行結果は以下の通り。
utflag{tw0_tim3_p4ds}utflag{tw0_tim3_p utflag{tw0_tim3_p4ds}utflag{tw0_tim3_p utflag{tw0_tim3_p4ds}
utflag{tw0_tim3_p4ds}
Random ECB (Cryptography)
$ nc crypto.utctf.live 9003 Input a string to encrypt (input 'q' to quit): 1 Here is your encrypted string, have a nice day :) d825b841547c7d5fddd67841db22b6bf6af5818287b92aa3b4b9043f4f615c5f Input a string to encrypt (input 'q' to quit): 1 Here is your encrypted string, have a nice day :) a9d279584431ef8fc22bf882737e87131ae342b5af9b6f75fd1aea064aaee9eb64686f2fb06d61510e745ca67cee4fc9 Input a string to encrypt (input 'q' to quit): 1 Here is your encrypted string, have a nice day :) a9d279584431ef8fc22bf882737e87131ae342b5af9b6f75fd1aea064aaee9eb64686f2fb06d61510e745ca67cee4fc9 Input a string to encrypt (input 'q' to quit): 1 Here is your encrypted string, have a nice day :) d825b841547c7d5fddd67841db22b6bf6af5818287b92aa3b4b9043f4f615c5f Input a string to encrypt (input 'q' to quit):
暗号化処理は以下のようになっている。
plaintext = 'A' * (0 or 1) + [入力] + flag + padding -> AES-ECB
1で何回か試したところ、長さが32バイトのときと48バイトのときがある。このことから以下のような構造になることがわかる。
0123456789abcdef A1FFFFFFFFFFFFFF FFFFFFFFFFFFFFFF PPPPPPPPPPPPPPPP
flagの長さは30バイトであることがわかる。
0123456789abcdef 0 XXXXXXXXXXXXXXX? XXXXXXXXXXXXXXXX XXXXXXXXXXXXXXXF FFFFFFFFFFFFFFFF FFFFFFFFFFFFFPPP 0123456789abcdef 1 XXXXXXXXXXXXXXF? XXXXXXXXXXXXXXXX XXXXXXXXXXXXXXFF FFFFFFFFFFFFFFFF FFFFFFFFFFFFPPPP : 0123456789abcdef 15 FFFFFFFFFFFFFFF? XXXXXXXXXXXXXXXX FFFFFFFFFFFFFFFF FFFFFFFFFFFFFFPP 0123456789abcdef 16 FFFFFFFFFFFFFFF? XXXXXXXXXXXXXXXF FFFFFFFFFFFFFFFF FFFFFFFFFFFFFPPP
先頭から1文字ずつフラグをはみ出させ、0ブロック目と2ブロック目で暗号を比較する。ただし、'A'が付く場合とそうでない場合があるため、2パターン比較する。
import socket def recvuntil(s, tail): data = b'' while True: if tail in data: return data.decode() data += s.recv(1) s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect(('crypto.utctf.live', 9003)) flag = '' for i in range(30): found = False for code in range(32, 127): print '[+] flag =', flag if len(flag) < 16: pt = 'X' * (15 - i) + flag + chr(code) + 'X' * (31 - i) else: pt = flag[-15:] + chr(code) + 'X' * (31 - i) tmp = '' while True: data = recvuntil(s, '\n').rstrip() print data + pt s.sendall(pt + '\n') data = recvuntil(s, '\n').rstrip() print data data = recvuntil(s, '\n').rstrip() print data enc0 = data[32*0:32*1] enc2 = data[32*2:32*3] if enc0 == enc2: found = True flag += chr(code) break if tmp == '': tmp = data elif tmp != data: break if found: break print flag
実行結果は以下の通り。
: [+] flag = utflag{3cb_w17h_r4nd0m_pr3f1x Input a string to encrypt (input 'q' to quit):h_r4nd0m_pr3f1x}XX Here is your encrypted string, have a nice day :) c4901be47a39685e49077e97b1f99a01ab7aefb49370f50edf206bdeb9ce2452af93d35d1f8262d2f10a71badc3fe3fc7c55bf8b3be29ef6762e488ffe4ad872 Input a string to encrypt (input 'q' to quit):h_r4nd0m_pr3f1x}XX Here is your encrypted string, have a nice day :) c4901be47a39685e49077e97b1f99a01ab7aefb49370f50edf206bdeb9ce2452af93d35d1f8262d2f10a71badc3fe3fc7c55bf8b3be29ef6762e488ffe4ad872 Input a string to encrypt (input 'q' to quit):h_r4nd0m_pr3f1x}XX Here is your encrypted string, have a nice day :) c4901be47a39685e49077e97b1f99a01ab7aefb49370f50edf206bdeb9ce2452af93d35d1f8262d2f10a71badc3fe3fc7c55bf8b3be29ef6762e488ffe4ad872 Input a string to encrypt (input 'q' to quit):h_r4nd0m_pr3f1x}XX Here is your encrypted string, have a nice day :) 9ca4b3c0e1e2244ab3fcff7e91884442c05f4ec1bc2ac4e758261ef3109f96bd9ca4b3c0e1e2244ab3fcff7e9188444250549e4f79525df7e4b66f3bb33b88d6 utflag{3cb_w17h_r4nd0m_pr3f1x}
utflag{3cb_w17h_r4nd0m_pr3f1x}
Galois (Cryptography)
AES-GCMの問題。GCMは暗号化モードにCTRモードを使っているので、keyとnonceが変わらなければ、平文と暗号文のXORは常に同じになる。このことからXORでflagを復号する。
import socket def recvuntil(s, tail): data = b'' while True: if tail in data: return data.decode() data += s.recv(1) def str_xor(s1, s2): return ''.join(chr(ord(a) ^ ord(b)) for a, b in zip(s1, s2)) s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect(('crypto.utctf.live', 9004)) data = recvuntil(s, ': \n').rstrip() print data enc_flag = data.split('\n')[0].split(' ')[1].decode('hex') print '1' s.sendall('1\n') data = recvuntil(s, ':\n').rstrip() print data try_pt = '1' * len(enc_flag) print try_pt s.sendall(try_pt + '\n') data = recvuntil(s, '\n').rstrip() print data data = recvuntil(s, '\n').rstrip() print data try_ct = eval(data)[0].decode('hex') flag = str_xor(str_xor(try_pt, try_ct), enc_flag) print flag
実行結果は以下の通り。
flag fc4bd0f92507ce533500b4b59deecb74d95633f4c9a54d709e9af27756f4584f Welcome to the AES GCM encryption and decryption tool! 1. Encrypt message 2. Decrypt message 3. Quit Select option: 1 Input a string to encrypt (must be at least 32 characters): 11111111111111111111111111111111 Here is your encrypted string & tag, have a nice day :) ('b80e87a475518454675cdae29cad98748c0331aba7a04b769bc8a81950f25e03', '78545c2af8bb95d71eada9703bb5ceb5') utflag{6cm_f0rb1dd3n_4774ck_777}
utflag{6cm_f0rb1dd3n_4774ck_777}
Hill (Cryptography)
アルファベットのみ取り出し、ヒル暗号として復号する。暗号は4文字セットで、平文が"utfl"から始まることを前提にすれば、鍵を求められる。あとは逆行列を使えば、復号できる。
#!/usr/bin/env sage -python import string def letters_to_numlist(s): s = s.lower() ary = [] for c in s: index = string.lowercase.index(c) ary.append(index) return ary def numlist_to_letters(lst): s = '' for idx in lst: s += string.lowercase[idx] return s def ary1x4_to_2x2(ary): ary2 = [] for i in range(2): row = [] for j in range(2): row.append(ary[i + j * 2]) ary2.append(row) return ary2 def ary2x2_to_1x4(ary2): ary = [] for i in range(2): for j in range(2): ary.append(ary2[j][i]) return ary def decrypt(k_mat, c_mat): p_mat = k_mat.inverse() * c_mat return p_mat enc = 'wznqca{d4uqop0fk_q1nwofDbzg_eu}' pre_flag = 'utfl' enc = list(enc) ct = '' for c in enc: if c in string.letters: ct += c ct = letters_to_numlist(ct) pt = letters_to_numlist(pre_flag) #### calculate key #### p_ary = ary1x4_to_2x2(pt) c_ary = ary1x4_to_2x2(ct[:4]) p_mat = matrix(Zmod(26), p_ary) c_mat = matrix(Zmod(26), c_ary) k_mat = c_mat * p_mat.inverse() #### decrypt #### pt = [] for i in range(0, len(ct), 4): c_ary = ary1x4_to_2x2(ct[i:i+4]) c_mat = matrix(Zmod(26), c_ary) p_mat = decrypt(k_mat, c_mat) p_ary = ary2x2_to_1x4(p_mat) pt += p_ary pt = numlist_to_letters(pt) index = 0 flag = '' for c in enc: if c in string.lowercase: flag += pt[index] index += 1 elif c in string.uppercase: flag += pt[index].upper() index += 1 else: flag += c print flag
utflag{d4nger0us_c1pherText_qq}
Survey (Misc)
アンケートに答えたら、フラグが表示された。
utflag{thank_you_RyehGswqqC}