この大会は2020/9/18 18:00(JST)~2020/9/20 18:00(JST)に開催されました。
今回もチームで参戦。結果は5470点で1080チーム中21位でした。
自分で解けた問題をWriteupとして書いておきます。
Twitter (misc 10)
RFVDVEZ7aHR0cHM6Ly93d3cueW91dHViZS5jb20vd2F0Y2g/dj1YZlI5aVk1eTk0c30=
$ echo RFVDVEZ7aHR0cHM6Ly93d3cueW91dHViZS5jb20vd2F0Y2g/dj1YZlI5aVk1eTk0c30= | base64 -d DUCTF{https://www.youtube.com/watch?v=XfR9iY5y94s}
DUCTF{https://www.youtube.com/watch?v=XfR9iY5y94s}
Discord (misc 10)
Discordに入り、#rulesチャネルのルールの記載の下にある、旗のマークをクリックすると、他のチャネルがたくさん表示された。
その中の#generalチャネルのトピックにフラグが書いてあった。
DUCTF{c0n6r475_y0u_h4v3_n0w_j01n3d_0ur_4m4z1n6_d15c0rd}
Welcome! (misc 100)
Welcome文があちこちに表示される。moreで止めながら、画面を見ていく。
$ ssh ductf@chal.duc.tf -p 30301 | more ductf@chal.duc.tf's password:
DUCTF{w3lc0m3_t0_DUCTF_h4v3_fun!}
16 Home Runs (misc 100)
base64デコードするだけ。
$ echo RFVDVEZ7MTZfaDBtM19ydW41X20zNG41X3J1bm4xbjZfcDQ1N182NF9iNDUzNX0= | base64 -d DUCTF{16_h0m3_run5_m34n5_runn1n6_p457_64_b4535}
DUCTF{16_h0m3_run5_m34n5_runn1n6_p457_64_b4535}
In a pickle (misc 500)
Iから始まるASCIIコードをデコードする。
>>> chr(112) 'p' >>> chr(49) '1' >>> chr(99) 'c' >>> chr(107) 'k' >>> chr(108) 'l' >>> chr(51) '3' >>> chr(95) '_' >>> chr(121) 'y' >>> chr(48) '0' >>> chr(117) 'u' >>> chr(82) 'R' >>> chr(95) '_' >>> chr(109) 'm' >>> chr(51) '3' >>> chr(53) '5' >>> chr(53) '5' >>> chr(52) '4' >>> chr(103) 'g' >>> chr(51) '3'
結合すると、フラグになる。
DUCTF{p1ckl3_y0uR_m3554g3}
formatting (reversing 100)
$ ltrace ./formatting sprintf("d1d_You_Just_ltrace_296faa2990ac"..., "%s%02x%02x%02x%02x%02x%02x%02x%0"..., "d1d_You_Just_ltrace_", 0x29, 0x6f, 0xaa, 0x29, 0x90, 0xac, 0xbc, 0x36) = 37 puts("haha its not that easy}"haha its not that easy} ) = 24 +++ exited (status 0) +++
DUCTF{d1d_You_Just_ltrace_296faa2990acbc36}
Leggos (web 100)
右クリックメニューは出せない。Chromeのデベロッパーツールで、disableMouseRightClick.jsを読んでいることがわかり、このコードを見るとコメントにフラグが書いてあった。
//the source reveals my favourite secret sauce // DUCTF{n0_k37chup_ju57_54uc3_r4w_54uc3_9873984579843}
DUCTF{n0_k37chup_ju57_54uc3_r4w_54uc3_9873984579843}
rot-i (crypto 100)
アルファベットのみシフト数を1ずつ増やして復号する。
import string with open('challenge.txt', 'r') as f: ct = f.read().rstrip() pt = '' i = 0 for c in ct: if c in string.uppercase: index = (string.uppercase.index(c) - i) % 26 pt += string.uppercase[index] i += 1 elif c in string.lowercase: index = (string.lowercase.index(c) - i) % 26 pt += string.lowercase[index] i += 1 else: pt += c i += 1 print pt
復号結果は以下の通り。
You've solved the beginner crypto challenge! The flag is DUCTF{crypto_is_fun_kjqlptzy}. Now get out some pen and paper for the rest of them, they won't all be this easy :).
DUCTF{crypto_is_fun_kjqlptzy}
babyrsa (crypto 500)
s = pow(557*p - 127*q, n - p - q, n) = pow(557*p - 127*q, (p - 1) * (q - 1) - 1, n) p * q = n ↓ s * (557*p - 127*q) = 1 mod n p * q = n ↓ 557 * p - 127 * q = inverse(s, n) p * q = n ↓ 557 * p - 127 * (n/p) = inverse(s, n) 557 * p**2 - inverse(s, n) * p - 127 * n = 0
この2次方程式を解けば、pを求められ、qもわかる。あとはそのまま復号すればよい。
from Crypto.Util.number import * from sympy import * n = 19574201286059123715221634877085223155972629451020572575626246458715199192950082143183900970133840359007922584516900405154928253156404028820410452946729670930374022025730036806358075325420793866358986719444785030579682635785758091517397518826225327945861556948820837789390500920096562699893770094581497500786817915616026940285194220703907757879335069896978124429681515117633335502362832425521219599726902327020044791308869970455616185847823063474157292399830070541968662959133724209945293515201291844650765335146840662879479678554559446535460674863857818111377905454946004143554616401168150446865964806314366426743287 s = 3737620488571314497417090205346622993399153545806108327860889306394326129600175543006901543011761797780057015381834670602598536525041405700999041351402341132165944655025231947620944792759658373970849932332556577226700342906965939940429619291540238435218958655907376220308160747457826709661045146370045811481759205791264522144828795638865497066922857401596416747229446467493237762035398880278951440472613839314827303657990772981353235597563642315346949041540358444800649606802434227470946957679458305736479634459353072326033223392515898946323827442647800803732869832414039987483103532294736136051838693397106408367097 c = 7000985606009752754441861235720582603834733127613290649448336518379922443691108836896703766316713029530466877153379023499681743990770084864966350162010821232666205770785101148479008355351759336287346355856788865821108805833681682634789677829987433936120195058542722765744907964994170091794684838166789470509159170062184723590372521926736663314174035152108646055156814533872908850156061945944033275433799625360972646646526892622394837096683592886825828549172814967424419459087181683325453243145295797505798955661717556202215878246001989162198550055315405304235478244266317677075034414773911739900576226293775140327580 e = 0x10001 var('p') eq = Eq(557 * p**2 - inverse(s, n) * p - 127 * n) ans = solve(eq) for p in ans: if p > 0: break q = n // p assert p * q == n phi = (p - 1) * (q - 1) d = inverse(e, phi) m = pow(c, d, n) flag = long_to_bytes(m) print flag
DUCTF{e4sy_RSA_ch4ll_t0_g3t_st4rt3d}
Extra Cool Block Chaining (crypto 500)
サーバの処理概要は以下の通り。
・flagをKEY,IVで暗号化した結果を16進数表示 ・平文を入力(16進数) →入力データ先頭16バイトをKEY,IVで暗号化し、その結果を16進数表示 ・暗号文を入力(16進数) →入力データ先頭16バイトをKEY,IVで復号し、その結果を16進数表示 ■encrypt ・msgをパディング ・blocks: 16バイトごとのブロックを配列化 ・ブロックごとに以下の処理 ・enc: AES-ECB暗号化 ・2ブロック目以降は前のブロックの暗号化とさらにXORしたものを暗号化 ・各ブロックをIVとXOR ■decrypt ・blocks: 16バイトごとのブロックを配列化 ・ブロックごとに以下の処理 ・IVとXOR ・2ブロック目以降は前のブロックの暗号化とXOR ・dec: AES-ECB復号
このことから以下のようなイメージで暗号、復号が行われることがわかる。
<暗号> 平文1ブロック目 --(AES-ECB暗号化)--> 暗号1 ^ IV -> 暗号1ブロック目 平文2ブロック目 --(AES-ECB暗号化)--> 暗号2 ^ 暗号1 ^ IV --> 暗号2ブロック目 平文3ブロック目 --(AES-ECB暗号化)--> 暗号3 ^ 暗号2 ^ 暗号1 ^ IV --> 暗号3ブロック目 : <復号> 暗号1ブロック目 ^ IV --(AES-ECB復号)--> 平文1ブロック目 暗号2ブロック目 ^ IV ^ 暗号1ブロック目 --(AES-ECB復号)--> 平文1ブロック目
平文:'\x10' * 16の場合、2ブロックにパディングされ、以下のように暗号化される。
1ブロック目:'\x10' * 16 --(AES-ECB暗号化)--> 暗号1 ^ IV 2ブロック目:'\x10' * 16 --(AES-ECB暗号化)--> 暗号1 ^ 暗号1 ^ IV = IV
これでIVがわかる。あとはflagの暗号化の各ブロックについて、以下のようにして復号する。
・1ブロック目はそのまま復号 ・2ブロック目以降は前のブロックとIVとXORをして、復号
#!/usr/bin/env python3 import socket from Crypto.Util.strxor import strxor def recvuntil(s, tail): data = b'' while True: if tail in data: return data.decode() data += s.recv(1) def unpad(s): return s[:-ord(s[-1])] flag = '' for i in range(6): s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect(('chal.duc.tf', 30201)) data = recvuntil(s, b'\n').rstrip() print(data) data = recvuntil(s, b'\n').rstrip() print(data) flag_enc = data.split(' ')[-1] try_pt = b'10' * 16 data = recvuntil(s, b': ') print(data + try_pt.decode()) s.sendall(try_pt + b'\n') data = recvuntil(s, b'\n').rstrip() print(data) IV = bytes.fromhex(data)[16:] if i == 0: ct = flag_enc[32*i:32*i+32] else: pre_block = bytes.fromhex(flag_enc[32*(i-1):32*i]) cur_block = bytes.fromhex(flag_enc[32*i:32*(i+1)]) ct = strxor(strxor(pre_block, cur_block), IV).hex() data = recvuntil(s, b': ') print(data + ct) s.sendall(ct.encode() + b'\n') data = recvuntil(s, b'\n').rstrip() print(data) flag += bytes.fromhex(data).decode() flag = unpad(flag) print(flag)
実行結果は以下の通り。
Welcome! You get 1 block of encryption and 1 block of decryption. Here is the ciphertext for some message you might like to read: 7009345f5ff2ec47da0392986d296a7ee99e1d4ec81a3648e5b5ce194e781c8278b4e869231fed6be94655e21b0b38bf942a746cfeab0bbe0ec8461c4ce3296a2b1aecfa0cf29f75656911f2b985dcef3c066963ed131a573c947b88bf88b628 Enter plaintext to encrypt (hex): 10101010101010101010101010101010 e8d9f607f98a38d207f42dfc6ab3d47f5e957b2dc0083bea0ead59532a0167c4 Enter ciphertext to decrypt (hex): 7009345f5ff2ec47da0392986d296a7e 44554354467b346444316e475f72346e Welcome! You get 1 block of encryption and 1 block of decryption. Here is the ciphertext for some message you might like to read: 780ab81f0b15bbbd1289ae623a88083b43da7fac8e114f7986fe332e8e2b045da8d240fa383bbdae4a1026ed211a9321ff3f2c8e6743774f7458ac59cf01dce0234c28bcb9359621733e7cebf95986fe79ba5cc4af573c2f1a9c098259a1f105 Enter plaintext to encrypt (hex): 10101010101010101010101010101010 9b11b6918c6f20a953b91181ed7cc136fd429b28d189924c7b64e6d0fd3c60bf Enter ciphertext to decrypt (hex): c6925c9b548d6688ef137b9c499f6cd9 64304d5f3472523077735f344e445f78 Welcome! You get 1 block of encryption and 1 block of decryption. Here is the ciphertext for some message you might like to read: 832e4b9908c1626b5106a0770497edf4e284fbccea327e9ef7d7ce6c119ec2476b21f0f0eee49c30c6c60b1fee4e2c092a0e02f639eaa5edb7e45b015c04789e5a2d825ffa308ba0b430127716015e70a6cd46b96a249a4a646f32dc5688e2f1 Enter plaintext to encrypt (hex): 10101010101010101010101010101010 2e0eedf9a49f58a1b00ad832fb3d9c5a22d08bca5bdf8fee319a1c9fc85fdc0d Enter ciphertext to decrypt (hex): ab7580f65f096d40008bd9ec378f3243 3052535f683372335f346e445f746833 Welcome! You get 1 block of encryption and 1 block of decryption. Here is the ciphertext for some message you might like to read: 69e3b032f80be1fa0df4b61240e4dc64c1faee9f2d8c08932a393e1654d814c27e92eb045382b0dfd137d7f45f12bb6e0e39556976b023d3d168e27b90589e2f2c37a77250743a9e629dbebd5ce5f68efde7748eb9d40b5fcaf255c9be417fac Enter plaintext to encrypt (hex): 10101010101010101010101010101010 b75ee59680bd3f92b236172c1f0319fbaf39a1491cafa325e72b35f5a6ceca1f Enter ciphertext to decrypt (hex): df921f24399d3029e774007a6984ef5e 52335f553575344c6c795f48336c7073 Welcome! You get 1 block of encryption and 1 block of decryption. Here is the ciphertext for some message you might like to read: 87bf6f3c04536e1e4d31967093dd9872c2070cd747500a2bbb6de0c274fad133fea0ad8ad9c5b8e1531fecaa06608196cfca32fa5798c3b4cc17867246cba586f8a823005e3f6a2fdc2b4a597a85302cb30627d69eef1aa1689a2e129a63258f Enter plaintext to encrypt (hex): 10101010101010101010101010101010 59c7d9f2b308f4ccab8ef4c3fcc6f7ea84e912bd5446abfc0565adc72ae6cd5c Enter ciphertext to decrypt (hex): b38b03475de10267155961ec16a858f6 5f4275375f6e30545f374831735f7431 Welcome! You get 1 block of encryption and 1 block of decryption. Here is the ciphertext for some message you might like to read: 6adbaa3cc6f37cf80872ed6ed02c544e9cd43b4c95df58de0612a18ed1cc131a25fc22511ec1ddaea4e7f88e35cbe8b5ef06755f18fd0aff6cca2736c6df22adf863ae23f955f749c4897b669620a4f182993ab79334d805be8fd66c9aeccf80 Enter plaintext to encrypt (hex): 10101010101010101010101010101010 ac302310925ddc23a00047983b20c4c28a8888fa78be64883c197264fa972f58 Enter ciphertext to decrypt (hex): f0721c6e12df4bc4461fdf6ef65b4429 6d335f69375f7333336d7321217d0202 DUCTF{4dD1nG_r4nd0M_4rR0ws_4ND_x0RS_h3r3_4nD_th3R3_U5u4Lly_H3lps_Bu7_n0T_7H1s_t1m3_i7_s33ms!!}
DUCTF{4dD1nG_r4nd0M_4rR0ws_4ND_x0RS_h3r3_4nD_th3R3_U5u4Lly_H3lps_Bu7_n0T_7H1s_t1m3_i7_s33ms!!}
ceebc (crypto 500)
CBC-MACの問題。sampleのCBC-MACが表示される。solution_messageのCBC-MACを指定できればフラグが表示される。
MACにIVの値が含まれるので、以下のようになるよう調整すれば、MACは同じ値になる。
sample ^ iv_org = solution_message ^ iv
#!/usr/bin/env python3 import socket from Crypto.Util.strxor import strxor 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(('chal.duc.tf', 30202)) data = recvuntil(s, b'\n').rstrip() print(data) sig_org = data.split(' ')[-1][:-1] iv_org = bytes.fromhex(sig_org)[16:] sample = b'cashcashcashcash' solution_message = b'flagflagflagflag' iv = strxor(strxor(sample, iv_org), solution_message) sig = sig_org[:32] + iv.hex() data = recvuntil(s, b': ') print(data + solution_message.decode()) s.sendall(solution_message + b'\n') data = recvuntil(s, b': ') print(data + sig) s.sendall(sig.encode() + b'\n') data = recvuntil(s, b'\n').rstrip() print(data) data = recvuntil(s, b'\n').rstrip() print(data)
実行結果は以下の通り。
Hey there, have a message cashcashcashcash and its signature b14049c2168c7737329b8b1342abdb18d991e0c9073556fa8406cd351f6663f3! Now give me your message: flagflagflagflag Now the signature (in hex): b14049c2168c7737329b8b1342abdb18dc9cf2c6023844f5810bdf3a1a6b71fc Signature valid! DUCTF{WAT_I_THOUGHT_IV_IZ_G00D}
DUCTF{WAT_I_THOUGHT_IV_IZ_G00D}
Cosmic Rays (crypto 500)
少し試した結果を含め、整理すると以下のようになる。
blocks: 暗号ブロック配列(11ブロック) 平文0ブロック目 ^ フラグ前半 --(AES-ECB暗号化)--> 暗号0 ^ フラグ後半 --> 暗号0ブロック目 平文1ブロック目 ^ 暗号0 --(AES-ECB暗号化)--> 暗号1 ^ 平文0ブロック目 --> 暗号1ブロック目 平文2ブロック目 ^ 暗号1 --(AES-ECB暗号化)--> 暗号2 ^ 平文1ブロック目 --> 暗号2ブロック目 : 平文 0123456789abcdef If Bruce Schneie r multiplies two primes, the pro duct is prime. O n a completely u nrelated note, t he key used to e ncrypt this mess age is 0?9d0fe19 20ca?85e3851b162 b8cc9?5PPPPPPPPP 暗号文 ed5dd65ef5ac36e886830cf006359b30 0?1??7??????c?????a?????8??????? d6?????7????b????2?????????f?d?0 ??????????????????????6????????? ????f?????????????????????d??b?? ?a?????e??c?????2??????????0??3? 0c??f????????????1??7??????????? ??1e??0?0?????9??c??e??2??4????7 ?????0?????4????????f???7?????e? b??9????4?f??1?c??6?0a?3a0e6?d7? 975d?1cde66e41791b?780988c9b8329 鍵 0?9d0fe1920ca?85e3851b162b8cc9?5
最後の暗号ブロックから求めていく。
平文10ブロック目 ^ 暗号9 --(AES-ECB暗号化)--> 暗号10 ^ 平文9ブロック目 --> 暗号10ブロック目
不明箇所数は以下の通りで、取る値は0~f。
暗号11ブロック目: 2 平文 9ブロック目: 1 平文10ブロック目: 1 平文11ブロック目: 1 鍵: 3 合計 5(鍵が平文に含まれている)
ブルートフォースで暗号9を求め、形式があっているかチェックし、鍵と平文、暗号10ブロック目、暗号11ブロック目を特定する。あとは順に暗号1ブロック目まで算出する。
平文0ブロック目 ^ フラグ前半 --(AES-ECB暗号化)--> 暗号0 ^ フラグ後半 --> 暗号0ブロック目
上記の暗号0を求めたら、暗号0ブロック目とのXORでフラグ後半がわかる。また暗号0を復号して、平文0ブロック目とのXORでフラグ前半がわかる。
from Crypto.Cipher import AES from Crypto.Util.strxor import strxor import itertools import string def check_c9(s): if s[0] != 'b': return False if s[3] != '9': return False if s[8] != '4': return False if s[10] != 'f': return False if s[13] != '1': return False if s[15] != 'c': return False if s[18] != '6': return False if s[20:22] != '0a': return False if s[23:28] != '3a0e6': return False if s[29:31] != 'd7': return False return True msg = 'If Bruce Schneier multiplies two primes, the product is prime. On a completely unrelated note, the key used to encrypt this message is ' in_cs = [] for c in itertools.product(string.hexdigits[:16], repeat=5): c10 = '975d%s1cde66e41791b%s780988c9b8329' % (c[0], c[1]) key = '0%s9d0fe1920ca%s85e3851b162b8cc9%s5' % (c[2], c[3], c[4]) p8 = 'age is 0%s9d0fe19' % c[2] p9 = '20ca%s85e3851b162' % c[3] p10 = ('b8cc9%s5' % c[4]) + '\x09' * 9 in_c10 = strxor(c10.decode('hex'), p9) in_p10 = AES.new(key.decode('hex'), AES.MODE_ECB).decrypt(in_c10) in_c9 = strxor(p10, in_p10) c9 = strxor(in_c9, p8).encode('hex') if check_c9(c9): KEY = key in_cs.append(in_c9) print '[+] KEY =', KEY print '[+] ct10 =', c10 print '[+] ct9 =', c9 break msg += KEY[:25] ps = [msg[i:i+16] for i in range(0, len(msg), 16)] for i in range(9): in_p = AES.new(KEY.decode('hex'), AES.MODE_ECB).decrypt(in_cs[-1]) in_c = strxor(ps[9-i], in_p) in_cs.append(in_c) if i < 8: c = strxor(in_c, ps[7-i]).encode('hex') print '[+] ct%d =' % (8 - i), c c0 = 'ed5dd65ef5ac36e886830cf006359b30' flag2 = strxor(c0.decode('hex'), in_cs[-1]) print '[+] flag2 =', flag2 in_p = AES.new(KEY.decode('hex'), AES.MODE_ECB).decrypt(in_cs[-1]) flag1 = strxor(ps[0], in_p) print '[+] flag1 =', flag1 flag = 'DUCTF{%s}' % (flag1 + flag2) print flag
実行結果は以下の通り。
[+] KEY = 0b9d0fe1920ca685e3851b162b8cc9e5 [+] ct10 = 975d21cde66e41791b8780988c9b8329 [+] ct9 = b7c9076140feb15c1e690a83a0e60d75 [+] ct8 = 55fca0ebd654e6962862f31270f414ea [+] ct7 = ed1e8e0f0f5e33993c8ee28200461ad7 [+] ct6 = 0c0dff963317506a110a742feea43cf2 [+] ct5 = 3a1fe35ed6c0b03d2e1a8badb6c04e33 [+] ct4 = 3096f135f702fdda23b764c1bfde3b10 [+] ct3 = a45d08c7dee9da217412bb6edcdab75f [+] ct2 = d6e5c397d1d4bd6f42539db06aff5de0 [+] ct1 = 0112c744b0aac58207aea28e804ec6ab [+] flag2 = !_but_fl4gs_ar3! [+] flag1 = IVs_4r3nt_s3cret DUCTF{IVs_4r3nt_s3cret!_but_fl4gs_ar3!}
DUCTF{IVs_4r3nt_s3cret!_but_fl4gs_ar3!}
Survey (survey 10)
アンケートに答えたら、フラグが表示された。
DUCTF{th4nk_y0u_f0r_p4rt1c1pating_in_DUCTF!1!1}