この大会は2018/10/25 20:00(JST)~2018/10/26 20:00(JST)に開催されました。
今回もチームで参戦。結果は 1376点で426チーム中23位でした。
自分で解けた問題をWriteupとして書いておきます。
Sanity Check (Welcome 1)
freenodeで#bi0s-ctfチャネルに入る。
20:44 *topic : Official channel for BSides CTF | Flag for sanity check: flag{w3lc0m3} | CTF starts at 11:30 UTC and runs for 24 hours
flag{w3lc0m3}
RSA-Baby (Crypto 100)
modulusのうちそれぞれペアで公約数を出してみる。1以外の公約数があったので、素因数分解できた。素因数分解できたnに対応するeは素数ではない。
例えば、1つ目は以下のようになる。
114194 = 2 * 57097
RSA暗号はm^e%n=cなので、m^(e1*e2)%n=c、(m^e1)^e2%n=cとなる。
一旦57097で復号する。その後2乗根で、フラグが得られるかどうか試してみる。
from Crypto.Util.number import * import itertools import gmpy def egcd(a, b): x,y, u,v = 0,1, 1,0 while a != 0: q, r = b//a, b%a m, n = x-u*q, y-v*q b,a, x,y, u,v = a,r, u,v, m,n gcd = b return gcd, x, y modulus_list = [143786356117385195355522728814418684024129402954309769186869633376407480449846714776247533950484109173163811708549269029920405450237443197994941951104068001708682945191370596050916441792714228818475059839352105948003874426539429621408867171203559281132589926504992702401428910240117807627890055235377744541913L, 73988726804584255779346831019194873108586184186524793132656027600961771331094234332693404730437468912329694216269372797532334390363774803642809945268154324370355113538927414351037561899998734391507272602074924837440885467211134022878597523920836541794820777951492188067045604789153534513271406458984968338509L, 95666403279611361071535593067846981517930129087906362381453835849857496766736720885263927273295086034390557353492037703154353541274448884795437287235560639118986397838850340017834752502157881329960725771502503917735194236743345777337851076649842634506339513864285786698870866229339372558162315435127197444193L, 119235191922699211973494433973985286182951917872084464216722572875998345005104112625024274855529546680909781406076412741844254205002739352725207590519921992295941563460138887173402493503653397592300336588721082590464192875253265214253650991510709511154297580284525736720396804660126786258245028204861220690641L] e = [114194L, 130478L, 122694L, 79874L] ciphertext = ['0c55bc89e3773d8e378121eced4f9300103a8696bc3f9a1542c5b1539442ca5de03a40ad564ab5c2e764b2f946058ec220abf20afc271896ff4ca1f4a2dd227405f221de51e097d6b9f270c4561cd25596e96efd7de1a0e65d37cbf6a73c62a7e323f48450b9dc75e3e738ec1c7e1ae9fc808da8c476e72aea9155125b815653', '67caf9720696b1d0d589f053bb00ebe42b7b26fed38acb4012d29ddc55cd53da8398f042f22987453bdfa2ee8fb35ff121f81e96137995a8ca4daa1fbd88af3fd29138853d5fe98f9b983f67d6fd2b7ff6650228479ca6cac1d49572d28f01a659892b0799ca8202031a1ab37656331470d3ea5f221cc948636c1027bb6dd10f', '65e1cffe93ebccd49a9d14c01b2583a5d5e3140bf38a768833aa494d2d879a2934dbc10a843ec834e9ade286824e68879cb09ac9bd67afd7318b74955e9aa66df5740e6dcc26ccc787f0b415bdc80c6468421c4d4ce615fa3d25350940c5004e9b480c86faebc31e809725a9a868c94e9f1eaac567b4672fe395a7b205775883', '23108bb7f35d12b69bbe5e649ff47fb802b68f22045c484805040a3f4f8669acde8b04daba71190154aef4be9a0eafdebe31b5f96e8b01b5085f502fc0e12a326cc4d867f5317ac12bf16607765d99708934c35c4b9404747f69988ea7d3f4d8022cdfd81ada3aedb22d110db4aa81038aa151c9a4dbb5651757dc092b70b84d'] for ns in list(itertools.combinations(modulus_list, 2)): gcd = egcd(ns[0], ns[1]) if gcd[0] > 1: n = ns[0] p = gcd[0] q = n / p idx = modulus_list.index(n) print 'index =', idx c = int(ciphertext[idx], 16) phi = (p - 1) * (q - 1) d = inverse(e[idx]/2, phi) m_tmp = pow(c, d, n) m = gmpy.root(m_tmp, 2)[0] flag = long_to_bytes(m) print flag
flag{Congratzzz_y0u_kn0w_ext3nded_GCD_WOw!!}
pyQueue (Crypto 150)
keyが少しずつ後ろにずれて、暗号化していて、以下のようなイメージになっている。
1ブロック目:key[1:] + ? 2ブロック目:key[2:] + ? + ? 3ブロック目:key[3:] + ? + ? + ? MAC: C1 ^ C2 ^ C3 ^ (key[4:] + ? + ? + ? + ?)
最終的なMACがわかっているので、逆算して鍵を割り出していけば、復号できる。鍵の最初の1バイトに対して、ブルートフォース。
from Crypto.Util.number import * from Crypto.Cipher import AES def is_printable(s): for c in s: code = ord(c) if code > 126: return False return True def unpad(s): return s[:-ord(s[-1])] with open('ci.pher.text', 'r') as f: data = f.read().split(':') MAC = int(data[0]) ct = data[1] ct1 = ct[:32] ct2 = ct[32:64] ct3 = ct[64:] ct = [ct1, ct2, ct3] key = int(ct1, 16) ^ int(ct2, 16) ^ int(ct3, 16) ^ MAC key = long_to_bytes(key) flag = '' for i in range(len(ct)): for code in range(256): try_key = chr(code) + key[:-1] cipher = AES.new(try_key, AES.MODE_ECB) pt = cipher.decrypt(ct[2-i].decode('hex')) if is_printable(pt): flag = pt + flag key = try_key break flag = unpad(flag) print flag
flag{H4il_bUggi3s!_qu3u3_k3y_2_h0t_4_w4rmUp}
RSA-reloaded (Crypto 200)
rとsは比較的近い素数と考えられるため、Fermat法で素因数分解する。さらにx+f1の次の素数がrになることからxの範囲を探し、2次方程式を解くことによって、p,qを求め復号する。
q%p = x q = p*k+x まずはk=1で考える。 ⇒p * (p+x) = N1 ⇒ p**2 + x*p = N1 ⇒該当するpが見つかる。
最終的なコードは以下の通り。
from Crypto.PublicKey import RSA from Crypto.Util.number import * import gmpy from z3 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 with open('publickey2.pem', 'r') as f: pub_data2 = f.read() pubkey2 = RSA.importKey(pub_data2) N2 = pubkey2.n e = pubkey2.e r, s = fermat(N2) with open('ciphertext2.txt', 'r') as f: enc_f2 = int(f.read()) phi2 = (r - 1) * (s - 1) d2 = inverse(e, phi2) f1 = pow(enc_f2, d2, N2) flag1 = long_to_bytes(f1) x = r - f1 - 1 with open('publickey1.pem', 'r') as f: pub_data1 = f.read() pubkey1 = RSA.importKey(pub_data1) N1 = pubkey1.n e = pubkey1.e p = Int('p') while True: np = gmpy.next_prime(x + f1) if np != r: break s = Solver() s.add(p**2 + x*p == N1, p > 0) if s.check() == sat: p = s.model()[p].as_long() q = p + x break x -= 1 with open('ciphertext1.txt', 'r') as f: enc_f1 = int(f.read()) phi1 = (p - 1) * (q - 1) d1 = inverse(e, phi1) f2 = pow(enc_f1, d1, N1) flag2 = long_to_bytes(f2) flag = flag1 + flag2 print flag
flag{F3rm@t_&_s0me_t1nk3r1ng_can_d0_w0nd3rs!!!!}
FuzzY (Forensics 200)
httpでフィルタリングし、success.txtを取得すると、PGP MESSAGEが含まれていた。
-----BEGIN PGP MESSAGE----- Version: OpenPGP v2.0.8 Comment: https://sela.io/pgp/ wcBMA8fXP+32fyviAQf/T+NzsOgQ+ejW16GeK6h9WS9IDelAN9GLY5x5o9ilBlEL G4IPati4/zqd+kyV5mmA7k2eKnNByRnxElpp0PoGULX0ykjBTcXuLtNXzGWcDsFF xAkH8PduoPCcnNGWrCU6D8ZuWNtp7oeZ1krUZP+Kg9sfjjKfx0aUFhWs9SQH6mif AlbJQwxKi2xXv0UsHvg4Mz4TpVstoO5XcN9d4V+gygc+wx0K61JwAFw96xptNi9y hdMz/c7yW56JwBfwyiHvYmgLdWYJW9OEoQIj7Rwh1v8mD846vbvEDmagQ0Ra/K6q lnxa37gBFE+4kYpSXP7yqr8QMhmGDpMROJoJqxYyY9JxAe6317HZ+UUEOmNR+0tB EmPl/VVaoPc5q6RQ/cxwY4VhR4DtPsG9Gw237Sx+xSTAG5JbmtBf4KfQdVbeaXn1 PYPYBeCVL6nb6uPz6ZHBJ2SODWg9+Ssas+Gd5P7Q0zSA/35qYdamnAqUM/ujM2nN k2U= =+x+V -----END PGP MESSAGE-----
これをsuccess.gpgとして保存する。秘密鍵もどこかにあるはずなので、探してみる。DNSの通信の中にPNGが含まれていたが、分散していたので、スクリプトで抽出する。
from scapy.all import * packets = rdpcap('final_fuzz.pcap') png = '' for p in packets: if p.haslayer(IP) and p.haslayer(UDP): if p[IP].src == '192.168.42.129' and p[UDP].sport == 53: pad = p[Padding].load.lstrip('\x00') png += pad with open('out.png', 'wb') as f: f.write(png)
この結果、QRコードの画像が抽出できる。
このQRコードを読み取ると、以下の秘密鍵が取得できる。
-----BEGIN PGP PRIVATE KEY BLOCK----- Version: BCPG C# v1.6.1.0 lQOsBFvO/9wBCACgT4fK4dJm+M14jotXPUeKueo8xfFDunNUx/ZaSQbp5Y0i64OZ dPkQk4E2zCgXaYKNRhiIx2RUy27GBf7xjtDb0gh/HNhC41f5ZzYrNQBEcabcr0hn VfwiEzAqmTg+5TNsG26ZD2kuO1/J5zbKxI1D3g/9//fe5Nw8GucDiOntKgvFEXeV ETZ0llbP/mh8SAn5+naJiJJri9y3GF+QhX7wYP+W6mBkano8X/Yk2B2qWIRT6wRU DMQy1ptavyv5EJhYbsQGeAMu7WPJN+mLutAE2E1Xj03Sevsx2ynN8b/jF/HYp/mZ SzZ+TbHlRUoMC4+hYh5XfXy7Cx9HSI0uIDShABEBAAH/AwMCEv0ZoXbeXxxg3ioH /Y0lUhYOormsNzbrBjl1ipyWTDmRAf9BhmAPrX9K5GPAFAurGOj8QOQEWGrOyXfk gYtHXzGk1K6ItCitgxdBqHgbti23Ht8SmVWw3/pijPXXerXXMqj6NQ95ma6bYPsU PRtE1qtiEs+T8ln6ZBU9BCNyuZDceBY6btZS0cp88wB1xEPorhXVtjiV1cjDRSFG licqXh4fr4Qe0TUEeZK1uTqlhdj6YvKoFP94OKGxeM0eR1R/H/zyOtJVMMsEZLGr GNSVBZBN0B6l9wAMa+DGpuHIX25I197vP3x0v0gvP/57bF9og9mj2JzntM9NJsR1 2zAgplgX4IUp4SGPvcNbLE5c9yIEj77SAOBumrF3IcUYNN9IXvIHQh8qzOWmI5q+ NFCKin0tQNCAx4ef+4ThkyPezRovlFxG6T4HMF1YjYrlVMgiN034opaCKoFXd3EF 4UufN3vV0IYB7AfxWLeNAJyPCreDSyYyLFGx+ONpM5JKk/1cwH8H0XQuLXY+TuGj iF6QWkRkVcAYv0F7r2wPaVOVa8465s34fY94Rv1+KCpsjNFc3FrAJhz84jETxxqr s44U/zmGh0/tixjs9vB1C/i7csYWXYJYiPsPmcp5sOE4M1PtYsIfuOlaJ12e/IV9 YnNK+RLghYQ0MghUMHZeg8aqKY7SATDB1SuK+YKmhXte/E/VhTBUy+3RautMIUwS w9R1z2Hh4POZ4kp8yj9PnEujoQ5XtZuruhNyiWEwWYf1GPuDSoeEYcIRj18h8dL+ OSvsS1DqgPxH9hy8iidLDq1ZkrQ0w08Du9zjVY02f4OoRunzXbis6P3Y0mRf4iif bYdqVg3snMwY5u9lEaIYqmtcGibgybah394CgTt0xrQTa2FydGlrOTk3QGdtYWls LmNvbYkBHAQQAQIABgUCW87/3AAKCRDH1z/t9n8r4qnlB/9N1BBWSf6lfmejPh3R DZ+QrBsCELm8qBeawlsY9To6UUdrIoC9vzIwKAgil2K2MC9z/laZQcep0WepnOar 5KSUyhPI50/aE97yfA0v4lKkylb0OPt8E0S4gIxTlRhpht2K4lsRaD+2wyRvMRuU /Grgxd5TVVm9KfXQBCAxgFgX2OdZ2/Yb2GJQ4M6DquISIBar+i39a9bdZ9kP70ox jfgG8SLXPxzBiHIULUy4X+80VafKWw1/AzN2t4CTRtIMHu7jeUqpws+MB6TxTLBA G/JSdb+W3ceHseJ9YXqVIhfrlKt8T3QAqErjQjPN0YB9KDaELwDM1rxFryy8zuAB zdZ/ =rb5z -----END PGP PRIVATE KEY BLOCK-----
これをprivate.ascとして保存する。
復号してみる。
$ gpg --import private.asc gpg: 鍵F67F2BE2: 秘密鍵をインポートしました gpg: /home/ctf/.gnupg/trustdb.gpg: 信用データベースができました gpg: 鍵F67F2BE2: 公開鍵"kartik997@gmail.com"をインポートしました gpg: 処理数の合計: 1 gpg: インポート: 1 (RSA: 1) gpg: 秘密鍵の読み込み: 1 gpg: 秘密鍵のインポート: 1 $ gpg success.gpg 次のユーザの秘密鍵のロックを解除するには パスフレーズがいります
パスフレーズが必要なので、探してみる。先ほどのQRコードのPNGファイルのzTXtチャンクに怪しいデータがある。
4578696600004d4d002a000000080003010e00020000000b000000320128000300000001 000200000213000300000001000100000000000068656c6c6f776f726c640000
末尾のデータをhexデコードする。
$ echo 68656c6c6f776f726c64 | xxd -r -p helloworld
これをパスフレーズにして復号する。
$ gpg success.gpg 次のユーザの秘密鍵のロックを解除するには パスフレーズがいります:"kartik997@gmail.com" 2048ビットRSA鍵, ID F67F2BE2作成日付は2018-10-23 gpg: *警告*: 暗号アルゴリズムAES256は受取人の優先指定に入っていません gpg: 2048-ビットRSA鍵, ID F67F2BE2, 日付2018-10-23に暗号化されました "kartik997@gmail.com" $ cat success flag{eNcryP7!ng_t0_PgP_1s_r34LLy_Pre3tY_g00D_pr1V4cY}
flag{eNcryP7!ng_t0_PgP_1s_r34LLy_Pre3tY_g00D_pr1V4cY}