この大会は2021/3/20 9:00(JST)~2021/3/21 9:00(JST)に開催されました。
今回もチームで参戦。結果は200点で297チーム中49位でした。
自分で解けた問題をWriteupとして書いておきます。
Welcome (WEB)
問題に記載のURLにアクセスしたら、フラグが表示された。
LINECTF{welcome_to_linectf}
babycrypto1 (CRY)
$ nc 35.200.115.41 16001 test Command: bkmYVFb7Ab2XprBQmVT7s4Abm5jHL6dwii7HmWzSUur7LOaa0ZWWhf0kfFXLbuExQNQW+Kq7AnFY614rnMlxRfT4+e7fKB4xQJ1ITbojA14AcY+VhlNZ/ymJ/ElOdyQSzHc8ncGi5XPrMPIN123O/t1hFrQ0jGNJtaSL4r2T024LAIlX414BNSBxWfukXPnNiTYrQ27qMJNJjwEDMoa7rn2ClOlu1cgkoWIyRq1xbESgl/zia/xB7ezP/6ygGWK8 **Cipher oracle** IV...: YWJjZGVmMDEyMzQ1Njc4OQ== Message...: YQ== Ciphertext:YWJjZGVmMDEyMzQ1Njc4Ob9uqp+fqBo8SBWm00miLVs= Enter your command: bkmYVFb7Ab2XprBQmVT7s4Abm5jHL6dwii7HmWzSUur7LOaa0ZWWhf0kfFXLbuExQNQW+Kq7AnFY614rnMlxRfT4+e7fKB4xQJ1ITbojA14AcY+VhlNZ/ymJ/ElOdyQSzHc8ncGi5XPrMPIN123O/t1hFrQ0jGNJtaSL4r2T024LAIlX414BNSBxWfukXPnNiTYrQ27qMJNJjwEDMoa7rn2ClOlu1cgkoWIyRq1xbESgl/zia/xB7ezP/6ygGWK8 T1k+JaWJbIdLK14ECGNtt9+Jw9UjcdzirjBpEZbBzMRIrQdEjVzrKjPsD7rMu6mxmNrmxZ4qp27XL1Br7Qk1qnMrmOJV9caqxhe940pYzbSHZCMeeXuT07eWnY8/mx/V3bMHFDO4Ku5gSLrOQ9VbVuEmzpFfVP3rtest
サーバの処理概要は以下の通り。
・aes_key: ランダム16バイト文字列 ・token: 160バイトランダム文字列をbase64にしたものの先頭160バイト ・token + 'test' のAES-CBC暗号化(iv(ランダム) + enc)を表示(base64) ・iv: base64で入力 ・msg: base64で入力 ・入力したivでmsgをAES-CBC暗号化して表示(base64) ・以下、繰り返し ・tt: コマンド入力(先頭16バイトはiv) ・tt2: ttを復号 → 表示 ・tt2とtoken + 'show'が一致したら、フラグを表示 T: token 0123456789abcdef TTTTTTTTTTTTTTTT TTTTTTTTTTTTTTTT TTTTTTTTTTTTTTTT TTTTTTTTTTTTTTTT TTTTTTTTTTTTTTTT TTTTTTTTTTTTTTTT TTTTTTTTTTTTTTTT TTTTTTTTTTTTTTTT TTTTTTTTTTTTTTTT TTTTTTTTTTTTTTTT ^ C 9 --AES--> C10 showPPPPPPPPPPPP ^ C10 --AES--> C11
test commandの表示は192バイトのbase64。16バイトがiv、次の160バイトがtoken、残り16バイトが"test"の暗号。ivでC10, msgで"show"を指定すれば、11ブロック目の暗号を入手できる。
これを元にスクリプトにして実行する。
from base64 import b64decode from base64 import b64encode import socket def recvuntil(s, tail): data = '' while True: if tail in data: return data data += s.recv(1) s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect(('35.200.115.41', 16001)) data = recvuntil(s, '\n').rstrip() print data test_ct = b64decode(data.split(': ')[1]) iv = b64encode(test_ct[16*10:16*11]) msg = b64encode('show') data = recvuntil(s, ': ') print data + iv s.sendall(iv + '\n') data = recvuntil(s, ': ') print data + msg s.sendall(msg + '\n') data = recvuntil(s, '\n').rstrip() print data ct11 = b64decode(data.split(':')[1])[16:] ct = b64encode(test_ct[:16*11] + ct11) test_ct = b64encode(test_ct) data = recvuntil(s, ': ') print data + ct s.sendall(ct + '\n') data = recvuntil(s, '\n').rstrip() print data data = recvuntil(s, '}') print data
実行結果は以下の通り。
test Command: wxugw/hyLsxPjOenrAL9vQ70GvyXPKOUVb3ByqUscFyCPLdDamKLP8hH1cHKlJF6mdrRibn5gjcM+3l/d28AH6x9c68jhN20MQ1pgnWSMdZjujt7Y54gona4R4E4ObON9+QYWmplrZZFuSzPcDrJBNVdQeiHmqw9SvrJUOUVSKKnCEJQqsD8Luynu5fKB7dHvl/7lK3awmbNhC/uo//ZlU/q0xMF/fLnPPbzG1+jWZhax2Jb5mzKXHjckCSZhSWr **Cipher oracle** IV...: T+rTEwX98uc89vMbX6NZmA== Message...: c2hvdw== Ciphertext:T+rTEwX98uc89vMbX6NZmH/Y3mVSU+npGRYyjfNkxmU= Enter your command: wxugw/hyLsxPjOenrAL9vQ70GvyXPKOUVb3ByqUscFyCPLdDamKLP8hH1cHKlJF6mdrRibn5gjcM+3l/d28AH6x9c68jhN20MQ1pgnWSMdZjujt7Y54gona4R4E4ObON9+QYWmplrZZFuSzPcDrJBNVdQeiHmqw9SvrJUOUVSKKnCEJQqsD8Luynu5fKB7dHvl/7lK3awmbNhC/uo//ZlU/q0xMF/fLnPPbzG1+jWZh/2N5lUlPp6RkWMo3zZMZl 0VOipsv3sjGUZ1HyvFhe9FEKJe5YkRlYkiCpyjAPb38EvmMX2E+zqm6RcghrRBkejy2K46l3095tQvjJ/klmDumWps4WJkoSmnMtrOnfdmri1+8IjfLP7WKlqH4r9gHEqdPJRr0JyRtwFdMc9Qgk1xneuic9Mj7kshow The flag is: LINECTF{warming_up_crypto_YEAH}
LINECTF{warming_up_crypto_YEAH}
babycrypto2 (CRY)
$ nc 35.200.39.68 16002 test Command: q7iDALThVWeTOdhhltJrf4qOsRdvEARRMKo2CtzgMD5HpVZf9Xcm4f9HQgq4rCVM9L2kmX2tQ1JSwhaynRHHQYlfMmK1ALK1OgxBoeSUoJxVPUIWVna9dhDU9+eXALKKWrVBz+q7h4P1etX5aAfspMx6B4kv8LdmOSm++gAqTvdgBNYZfOrP/iQn+4OSDcRgVny1CRVqW2HbqTo709e2WPeP7/n0LGHM9orabta/+cS+L4pBm/bDyiy6uO1ZzbdxrdjypjA9sQYZL2NwrMcNBkeit72kEOBiSBJQFJpN8RImcN0m6vzlXdeejkbCR+ocB5AXIT3tPYdEacV79gdlFQ== Enter your command: q7iDALThVWeTOdhhltJrf4qOsRdvEARRMKo2CtzgMD5HpVZf9Xcm4f9HQgq4rCVM9L2kmX2tQ1JSwhaynRHHQYlfMmK1ALK1OgxBoeSUoJxVPUIWVna9dhDU9+eXALKKWrVBz+q7h4P1etX5aAfspMx6B4kv8LdmOSm++gAqTvdgBNYZfOrP/iQn+4OSDcRgVny1CRVqW2HbqTo709e2WPeP7/n0LGHM9orabta/+cS+L4pBm/bDyiy6uO1ZzbdxrdjypjA9sQYZL2NwrMcNBkeit72kEOBiSBJQFJpN8RImcN0m6vzlXdeejkbCR+ocB5AXIT3tPYdEacV79gdlFQ== Command: testZzTosTPcUnBu44roqYGeyIjyJ22OoYp66c67bitZZthoRjCwTC9sW5jOFkVqW7WlMwJqI5Y+eJtxCkY4lkVkX3vzdDSkPtwB1FOrfHWPeGq4jOeprbrqhtcQzRdUsukyF1YdRzzZ8ezm2g82ydjFYcDrkfzk3ZQOzx0CpVulL80HGbwsB6amUMsmmVsC4jULWpg66++vj30B6p/aKGbh Enter your command:
サーバの処理概要は以下の通り。
・AES_KEY: ランダム16バイト文字列 ・TOKEN: 159バイトランダム文字列をbase64にしたもの→212バイト ・PREFIX = 'Command: ' ・PREFIX + 'test' + TOKEN のAES-CBC暗号化(iv(ランダム) + enc)を表示(base64) ・以下、繰り返し ・tt: コマンド入力(先頭16バイトはiv) ・tt2: ttを復号 → 表示 ・tt2とPREFIX + 'show' + TOKEN が一致したら、フラグを表示 T: token 0123456789abcdef Command: testTTT TTTTTTTTTTTTTTTT TTTTTTTTTTTTTTTT TTTTTTTTTTTTTTTT TTTTTTTTTTTTTTTT TTTTTTTTTTTTTTTT TTTTTTTTTTTTTTTT TTTTTTTTTTTTTTTT TTTTTTTTTTTTTTTT TTTTTTTTTTTTTTTT TTTTTTTTTTTTTTTT TTTTTTTTTTTTTTTT TTTTTTTTTTTTTTTT TTTTTTTTTTTTTTTT TPPPPPPPPPPPPPPP
ivを調整するだけで1ブロック目の平文は変更できる。
iv1 ^ pt1 = iv2 ^ pt2
上のようになるようpt1で"test"の部分をpt2で"show"になるようiv2を算出する。
これを元にスクリプトにして実行する。
from base64 import b64decode from base64 import b64encode import socket from Crypto.Util.strxor import strxor def recvuntil(s, tail): data = '' while True: if tail in data: return data data += s.recv(1) s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect(('35.200.39.68', 16002)) data = recvuntil(s, '\n').rstrip() print data test_ct = b64decode(data.split(': ')[1]) ct_body = test_ct[16:] iv1 = test_ct[:16] iv2 = iv1[:9] + strxor(strxor('test', 'show'), iv1[9:13]) + iv1[13:] ct = b64encode(iv2 + ct_body) data = recvuntil(s, ': ') print data + ct s.sendall(ct + '\n') data = recvuntil(s, '\n').rstrip() print data data = recvuntil(s, '}') print data
実行結果は以下の通り。
test Command: K0FUm+tgUeQz1SLEnBfhzrQ3nwbyDB8HmmUP7pUaVNS/u9pTLhQEu1M6E3Ag/V4dS2Eu/DXOWvjNCz1jL+UXICCY//ZxzPzPrOU7Esoxu+wd3KJYCpe8Jms58aGELPHcjjPM5DX7m7LpsoJ3O80yBqACoP/CHvalPd9hJFh8pCf6PKuamG/Kii97Vgtjp63qthSUeQE0IyKqLGOnimqd9fEsCKFCuuvH55xPxM0apu80PRmP3I0w3+uHj4t5Uld66IZi4w5HF5d1dCcfEWQXV6OOFacOZRV4bmcrH+teGEi1lEi4iW2tA2QWmr0GnvBxpl0+NIPJ+CCvMdn2hyEdPA== Enter your command: K0FUm+tgUeQz0i/YnxfhzrQ3nwbyDB8HmmUP7pUaVNS/u9pTLhQEu1M6E3Ag/V4dS2Eu/DXOWvjNCz1jL+UXICCY//ZxzPzPrOU7Esoxu+wd3KJYCpe8Jms58aGELPHcjjPM5DX7m7LpsoJ3O80yBqACoP/CHvalPd9hJFh8pCf6PKuamG/Kii97Vgtjp63qthSUeQE0IyKqLGOnimqd9fEsCKFCuuvH55xPxM0apu80PRmP3I0w3+uHj4t5Uld66IZi4w5HF5d1dCcfEWQXV6OOFacOZRV4bmcrH+teGEi1lEi4iW2tA2QWmr0GnvBxpl0+NIPJ+CCvMdn2hyEdPA== Command: showZzTosTPcUnBu44roqYGeyIjyJ22OoYp66c67bitZZthoRjCwTC9sW5jOFkVqW7WlMwJqI5Y+eJtxCkY4lkVkX3vzdDSkPtwB1FOrfHWPeGq4jOeprbrqhtcQzRdUsukyF1YdRzzZ8ezm2g82ydjFYcDrkfzk3ZQOzx0CpVulL80HGbwsB6amUMsmmVsC4jULWpg66++vj30B6p/aKGbh The flag is: LINECTF{echidna_kawaii_and_crypto_is_difficult}
LINECTF{echidna_kawaii_and_crypto_is_difficult}
babycrypto3 (CRY)
公開鍵を見ると、以下のパラメータになっていることがわかる。
n = 31864103015143373750025799158312253992115354944560440908105912458749205531455987590931871433911971516176954193675507337 e = 65537
nをyafuで素因数分解する。
>yafu-x64.exe "factor(31864103015143373750025799158312253992115354944560440908105912458749205531455987590931871433911971516176954193675507337)" -v -threads 4 03/20/21 14:35:11 v1.34.5 @ SOPHIA, System/Build Info: Using GMP-ECM 6.3, Powered by GMP 5.1.1 detected Intel(R) Core(TM) i7-2670QM CPU @ 2.20GHz detected L1 = 32768 bytes, L2 = 6291456 bytes, CL = 64 bytes measured cpu frequency ~= 2210.531110 using 20 random witnesses for Rabin-Miller PRP checks =============================================================== ======= Welcome to YAFU (Yet Another Factoring Utility) ======= ======= bbuhrow@gmail.com ======= ======= Type help at any time, or quit to quit ======= =============================================================== cached 78498 primes. pmax = 999983 >> fac: factoring 31864103015143373750025799158312253992115354944560440908105912458749205531455987590931871433911971516176954193675507337 fac: using pretesting plan: normal fac: no tune info: using qs/gnfs crossover of 95 digits div: primes less than 10000 fmt: 1000000 iterations rho: x^2 + 3, starting 1000 iterations on C119 rho: x^2 + 2, starting 1000 iterations on C119 rho: x^2 + 1, starting 1000 iterations on C119 pm1: starting B1 = 150K, B2 = gmp-ecm default on C119 fac: setting target pretesting digits to 36.62 fac: sum of completed work is t0.00 fac: work done at B1=2000: 0 curves, max work = 30 curves fac: 30 more curves at B1=2000 needed to get to t36.62 ecm: 30/30 curves on C119, B1=2K, B2=gmp-ecm default fac: setting target pretesting digits to 36.62 fac: t15: 1.00 fac: t20: 0.04 fac: sum of completed work is t15.18 fac: work done at B1=11000: 0 curves, max work = 74 curves fac: 74 more curves at B1=11000 needed to get to t36.62 ecm: 74/74 curves on C119, B1=11K, B2=gmp-ecm default fac: setting target pretesting digits to 36.62 fac: t15: 7.17 fac: t20: 1.04 fac: t25: 0.05 fac: sum of completed work is t20.24 fac: work done at B1=50000: 0 curves, max work = 214 curves fac: 214 more curves at B1=50000 needed to get to t36.62 ecm: 214/214 curves on C119, B1=50K, B2=gmp-ecm default, ETA: 0 sec pm1: starting B1 = 3750K, B2 = gmp-ecm default on C119 fac: setting target pretesting digits to 36.62 fac: t15: 37.74 fac: t20: 11.23 fac: t25: 1.05 fac: t30: 0.07 fac: sum of completed work is t25.33 fac: work done at B1=250000: 0 curves, max work = 430 curves fac: 430 more curves at B1=250000 needed to get to t36.62 ecm: 430/430 curves on C119, B1=250K, B2=gmp-ecm default, ETA: 2 sec pm1: starting B1 = 15M, B2 = gmp-ecm default on C119 fac: setting target pretesting digits to 36.62 fac: t15: 123.74 fac: t20: 64.98 fac: t25: 9.65 fac: t30: 1.07 fac: t35: 0.09 fac: sum of completed work is t30.45 fac: work done at B1=1000000: 0 curves, max work = 904 curves fac: 904 more curves at B1=1000000 needed to get to t36.62 ecm: 904/904 curves on C119, B1=1M, B2=gmp-ecm default, ETA: 5 sec fac: setting target pretesting digits to 36.62 fac: t15: 425.07 fac: t20: 245.78 fac: t25: 54.85 fac: t30: 8.73 fac: t35: 1.09 fac: t40: 0.11 fac: sum of completed work is t35.56 fac: work done at B1=3000000: 0 curves, max work = 2350 curves fac: 499 more curves at B1=3000000 needed to get to t36.62 ecm: 461/499 curves on C119, B1=3M, B2=gmp-ecm default, ETA: 9.4 min ecm: found prp42 factor = 291664785919250248097148750343149685985101 fac: setting target pretesting digits to 24.00 fac: t15: 656.07 fac: t20: 399.78 fac: t25: 96.85 fac: t30: 17.28 fac: t35: 2.53 fac: t40: 0.31 fac: t45: 0.03 fac: sum of completed work is t36.54 pretesting / qs ratio was 136097.68 Total factoring time = 11836.5628 seconds ***factors found*** P42 = 291664785919250248097148750343149685985101 P78 = 109249057662947381148470526527596255527988598887891132224092529799478353198637 ans = 1
素因数分解できたので、あとはそのまま復号する。途中からbase64文字列になっているので、その部分をデコードする。
from Crypto.PublicKey import RSA from Crypto.Util.number import * with open('ciphertext.txt', 'rb') as f: c = bytes_to_long(f.read()) with open('pub.pem', 'r') as f: pub_data = f.read() pubkey = RSA.importKey(pub_data) n = pubkey.n e = pubkey.e p = 291664785919250248097148750343149685985101 q = 109249057662947381148470526527596255527988598887891132224092529799478353198637 assert p * q == n phi = (p - 1) * (q - 1) d = inverse(e, phi) m = pow(c, d, n) msg = long_to_bytes(m) print '[+] msg =', msg flag = msg.rstrip()[-32:].decode('base64').rstrip() flag = 'LINECTF{%s}' % flag print '[*] flag =', flag
実行結果は以下の通り。
[+] msg = `g・ヘヒa・Η・ Q0xPU0lORyBUSEUgRElTVEFOQ0UuCg== [*] flag = LINECTF{CLOSING THE DISTANCE.}
LINECTF{CLOSING THE DISTANCE.}