この大会は2023/4/29 7:00(JST)~2023/5/1 10:00(JST)に開催されました。
今回もチームで参戦。結果は2646点で745チーム中109位でした。
自分で解けた問題をWriteupとして書いておきます。
Sanity Check (misc)
問題にフラグが書いてあった。
UMDCTF{w3lc0m3_t0_th3_p0k3v3rs3}
Ports (misc)
port-1.txt.zipを解凍すると、以下の内容が記載されているテキストファイルが展開される。
Go to port 21105 instead :( Random message: ofbllnyusmwbyghepmvricemlgbgiziiavfgaabcht
次のポート番号の名前が書いてあるので、例外が発生するまで解凍していく。
#!/usr/bin/env python3 import zipfile import re zip_name_format = 'port-%s.txt.zip' txt_name_format = 'port-%s.txt' pattern = 'Go to port (\d+) instead' port = '1' while True: print('[+] port:' , port) fname = zip_name_format % port pwd = str(port).encode() try: with zipfile.ZipFile(fname, 'r') as zf: zf.extractall(path='.', pwd=pwd) except: break fname = txt_name_format % port with open(fname, 'r') as f: data = f.read() m = re.search(pattern, data) if m is not None: port = m.group(1) else: print(data) break
実行結果は以下の通りで、port-42237.txt.zipは解凍できなかった。
: [+] port: 39192 [+] port: 8352 [+] port: 8637 [+] port: 2600 [+] port: 7590 [+] port: 14491 [+] port: 14987 [+] port: 23905 [+] port: 7326 [+] port: 23671 [+] port: 42237
port-42237.txt.zipを調べてみる。7zipでは解凍でき、展開されたテキストファイルにフラグが書いてあった。
UMDCTF{dDSA-d_23+t0ta11y_n0t_NSFW_tCp_pAcKET-0_0-15039254&((*#@!}
A TXT For You and Me (misc)
$ dig -t TXT a-txt-for-you-and-me.chall.lol ; <<>> DiG 9.18.12-0ubuntu0.22.04.1-Ubuntu <<>> -t TXT a-txt-for-you-and-me.chall.lol ;; global options: +cmd ;; Got answer: ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 20207 ;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1 ;; OPT PSEUDOSECTION: ; EDNS: version: 0, flags:; udp: 65494 ;; QUESTION SECTION: ;a-txt-for-you-and-me.chall.lol. IN TXT ;; ANSWER SECTION: a-txt-for-you-and-me.chall.lol. 5 IN TXT "UMDCTF{just_old_school_texting}" ;; Query time: 8 msec ;; SERVER: 127.0.0.53#53(127.0.0.53) (UDP) ;; WHEN: Sat Apr 29 14:00:53 JST 2023 ;; MSG SIZE rcvd: 103
UMDCTF{just_old_school_texting}
Gone Missing 1 (OSINT)
周囲を見ると、特徴的な建物がある。
この建物を画像検索すると、ノルウェー王宮であることがわかる。これで周囲の景色が合うところを探し、以下の場所でSubmitしたところ、フラグが表示された。
UMDCTF{I_b3t_rainbolt_c0uld_g3t_th1s_!n_thr33_s3c0nd5}
Gone Missing 2 (OSINT)
周囲を見ると、特徴的な星型の枠組みがある。
この星型の枠組みを画像検索すると、Castle Rock butteが見つかった。
https://ja.m.wikipedia.org/wiki/%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB:Castle_Rock_butte_in_Castle_Rock_Colorado.JPG
これで周囲の景色が合うところを探し、以下の場所でSubmitしたところ、フラグが表示された。
UMDCTF{why_d0es_th1s_r0ck_h4ve_a_st4r??}
Welcome to Python! (rev)
$ strings chal | grep python blib-dynload/_bz2.cpython-310-x86_64-linux-gnu.so blib-dynload/_codecs_cn.cpython-310-x86_64-linux-gnu.so blib-dynload/_codecs_hk.cpython-310-x86_64-linux-gnu.so :
Python3.10製の実行ファイルのようなので、pyinstxtractor.pyでモジュールを抽出する。
$ python3 pyinstxtractor.py chal [+] Processing chal [+] Pyinstaller version: 2.1+ [+] Python version: 3.10 [+] Length of package: 6544209 bytes [+] Found 35 files in CArchive [+] Beginning extraction...please standby [+] Possible entry point: pyiboot01_bootstrap.pyc [+] Possible entry point: pyi_rth_inspect.pyc [+] Possible entry point: chal.pyc [+] Found 99 files in PYZ archive [+] Successfully extracted pyinstaller archive: chal You can now use a python decompiler on the pyc files within the extracted directory
chal.pycファイルをデコンパイルする。
$ cd chal_extracted/ $ pycdc chal.pyc # Source Generated with Decompyle++ # File: chal.pyc (Python 3.10) from math import sqrt, sin, cos from ctypes import c_uint32, c_float from sys import exit as exit_ source = [ 672662614, 741343303, 495239261, 744259788, 722021046, 0xA70AA247L, 1053692, 0xA8050035L, 0xA982A820L, 624689, 0xA90D20BCL, 41134, 295340, 0xA0028102L, 622681, 576469, 671170814, 0x8041086EL, 765, 680595550, 0x80200166L, 698368102, 2437137, 0x8042C1EEL, 570966112, 4612341, 0x800008D4L, 0xA94D02CEL, 16484, 2103301, 136226, 9438506, 663820758, 0x8013523BL, 8405532, 0xA4000875L, 0x80030A78L, 136768] seed = 64 def wandom(x): return x * x * cos(x) * sin(x) / 1000 def evil_bit_hack(y): return int(c_uint32.from_buffer(c_float(y)).value) print('==========================================') print('Professional flag checker service (v 97.2)') print('==========================================') flag = input('Show me the flag: ') lf = len(flag) ls = len(source) l = lf if lf < ls else ls for i in range(seed, seed + l): w = wandom(i) c = ~(~ord(flag[i - seed]) ^ evil_bit_hack(wandom(wandom(w))) & evil_bit_hack(w)) + 1 if source[i - seed] != c: print("Uh oh! We don't think your flag is correct... :(") exit_(1) if lf == ls: print('Your flag is correct!') return None None('Some of your flag is correct...')
ブルートフォースで1文字ずつフラグを割り出す。
#!/usr/bin/env python2 from math import sqrt, sin, cos from ctypes import c_uint32, c_float from sys import exit as exit_ source = [ 672662614, 741343303, 495239261, 744259788, 722021046, 0xA70AA247L, 1053692, 0xA8050035L, 0xA982A820L, 624689, 0xA90D20BCL, 41134, 295340, 0xA0028102L, 622681, 576469, 671170814, 0x8041086EL, 765, 680595550, 0x80200166L, 698368102, 2437137, 0x8042C1EEL, 570966112, 4612341, 0x800008D4L, 0xA94D02CEL, 16484, 2103301, 136226, 9438506, 663820758, 0x8013523BL, 8405532, 0xA4000875L, 0x80030A78L, 136768] seed = 64 def wandom(x): return x * x * cos(x) * sin(x) / 1000 def evil_bit_hack(y): return int(c_uint32.from_buffer(c_float(y)).value) l = len(source) flag = '' for i in range(seed, seed + l): w = wandom(i) for code in range(32, 127): c = ~(~code ^ evil_bit_hack(wandom(wandom(w))) & evil_bit_hack(w)) + 1 if source[i - seed] == c: flag += chr(code) break i += 1 print(flag)
UMDCTF{0_0+-+eXP-eLLiARm_us_!!!-12345}
Terps Ticketing System (web)
以下のように適当入力して、Get Ticketsをクリックする。
Name: a Email: test@test.com
すると、https://tts.chall.lol/ticket?num=338に遷移して。以下のメッセージが表示されている。
Your Ticket # is: 338
https://tts.chall.lol/ticket?num=0にアクセスしてみると、フラグが表示された。
UMDCTF{d0nt_b3_@n_id0r_@lw@ys_s3cur3_ur_tick3ts}
Malware Chall Disclaimer (forensics)
「Doctors Hate Him」の添付ファイルはマルウェアと同等の注意が必要ということを説明するためだけの問題。
得点にはならないが、一応回答することにした。フラグは問題に書いてあった。
UMDCTF{i_understand_that_malware_chall_is_sus}
Mirror Unknown (forensics)
次の画像が問題になっている。
以下のURLのページを参考に、ポケモンのアンノーンの姿をアルファベットに置き換える。
https://www.dopr.net/pokemongoupdates/28-unown-event
ただし、画像が左右逆になっていることに注意する。
SINJOH RUINS
UMDCTF{SINJOHRUINS}
No. 352 (forensics)
パスワード1はポケモンNo.352の名前の小文字表記とのこと。調べると次の名前だとわかる。
kecleon
$ steghide extract -sf hide-n-seek.jpg -p kecleon wrote extracted data to "kecleon.jpg".
パスワード2は"timetofindwhatkecleonishiding"とのことなので、抽出されたファイルについてもこのパスワードで秘密情報を抽出する。
$ steghide extract -sf kecleon.jpg -p timetofindwhatkecleonishiding wrote extracted data to "flag.txt". $ cat flag.txt UMDCTF{KECLE0NNNNN}
UMDCTF{KECLE0NNNNN}
Fire Type Pokemon Only (forensics)
問題タイトルからもFTPの通信があやしい。「ftp || ftp-data」でフィルタリングする。secretpic1.pngやsecretが転送されていることがわかる。
まずsecretpic1.pngを抽出するためにNo.15292, 15293のパケットをエクスポートし、結合する。
$ cat 15292.bin 15293.bin > secretpic1.png
secretpic1.pngにはポケモンのSpearの画像があるだけ。
次にsecretの断片を拾い、結合する。
#!/usr/bin/env python3 from scapy.all import * packets = rdpcap('fire-type-pokemon-only.pcapng') flag = b'' for i in range(28928, 29113): if packets[i].haslayer(Raw): flag += packets[i][Raw].load with open('secret', 'wb') as f: f.write(flag)
$ file secret secret: Zip archive data, at least v2.0 to extract, compression method=deflate
zipだが、パスワードがかかっているので、クラックする。
$ fcrackzip -u -D -p dict/rockyou.txt secret PASSWORD FOUND!!!!: pw == pika $ unzip secret Archive: secret [secret] wisdom.mp4 password: inflating: wisdom.mp4
解凍し、展開されたmp4の動画を再生すると、下の方にフラグが書いてあった。
UMDCTF{its_n0t_p1kachu!!}
pokecomms (crypto)
"CHU!", "PIKA"で構成されていて、1行8個含まれている。1行の先頭は必ず"CHU!"になっていることから、"CHU!"は"0"、"PIKA"は"1"にしてデコードする。
#!/usr/bin/env python3 with open('pokecomms.txt', 'r') as f: lines = f.read().splitlines() flag = '' for line in lines: code = line.replace(' CHU!', '0').replace(' PIKA', '1') flag += chr(int(code, 2)) print(flag)
UMDCTF{P1K4CHU_Once_upon_a_time,_there_was_a_young_boy_named_Ash_who_dreamed_of_becoming_the_world's_greatest_Pokemon_trainer._He_set_out_on_a_journey_with_his_trusty_Pokemon_partner,_Pikachu,_a_cute_and_powerful_electric-type_Pokemon._As_Ash_and_Pikachu_traveled_through_the_regions,_they_encountered_many_challenges_and_made_many_friends._But_they_also_faced_their_fair_share_of_enemies,_including_the_notorious_Team_Rocket,_who_were_always_trying_to_steal_Pikachu._Despite_the_odds_stacked_against_them,_Ash_and_Pikachu_never_gave_up._They_trained_hard_and_battled_even_harder,_always_looking_for_ways_to_improve_their_skills_and_strengthen_their_bond._And_along_the_way,_they_learned_valuable_lessons_about_friendship,_determination,_and_the_power_of_believing_in_oneself._Eventually,_Ash_and_Pikachu's_hard_work_paid_off._They_defeated_powerful_opponents,_earned_badges_from_Gym_Leaders,_and_even_competed_in_the_prestigious_Pokemon_League_tournaments._But_no_matter_how_many_victories_they_achieved,_Ash_and_Pikachu_never_forgot_where_they_came_from_or_the_importance_of_their_friendship._In_the_end,_Ash_and_Pikachu_became_a_legendary_team,_admired_by_Pokemon_trainers_around_the_world._And_although_their_journey_may_have_had_its_ups_and_downs,_they_always_knew_that_as_long_as_they_had_each_other,_they_could_overcome_any_obstacle_that_stood_in_their_way}
CBC-MAC 1 (crypto)
サーバの処理概要は以下の通り。
・key: ランダム16バイト文字列 ・queries = [] ・queriesの長さが10未満の間、以下を実行 ・choice: 入力 ・choiceが'1'の場合 ・msg: 入力 ・msg: msgをhexデコード ・msgの長さが16の倍数でない場合はエラーメッセージ表示 ・msgの長さが16の倍数の場合 ・queriesにmsgを追加 ・t = cbc_mac(msg, key) ・iv = b'\x00' * 16 ・cipher: AES-CBC(key, iv)のインスタンス ・t: msgをAES-CBC暗号化した最後のブロック ・tの16進数表記文字列を返却 ・tを表示 ・choiceが'2'の場合 ・msg: 入力 ・tag: 入力 ・msg: msgをhexデコード ・msgの長さが16の倍数でない場合、エラーメッセージ表示 ・tagの長さが32バイトでない場合、エラーメッセージ表示 ・queriesの中にmsgがある場合、エラーメッセージ表示 ・t_ret = cbc_mac(msg, key) ・t_retとtagが同じ場合、フラグを表示
平文1ブロックの場合と平文2ブロックの場合で、以下のようなイメージになる。
PT0 ^ IV --(AES暗号) --> CT0 = tag0 PT0 ^ IV --(AES暗号) --> CT0 PT1 ^ CT0 --(AES暗号) --> CT1 = tag1
1回目でtag0を取得し、2回目でtag1を取得する。PT1 ^ CT0 を1ブロックのみを平文とした場合も、tagの値はtag1になることを使えばよい。
#!/usr/bin/env python3 import socket from binascii import hexlify, unhexlify 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(('0.cloud.chals.io', 12769)) pt = b'a' * 32 data = recvuntil(s, b': ') print(data + '1') s.sendall(b'1\n') data = recvuntil(s, b': ') print(data + hexlify(pt[:16]).decode()) s.sendall(hexlify(pt[:16]) + b'\n') data = recvuntil(s, b'\n').rstrip() print(data) tag0 = bytes.fromhex(data.split(' ')[-1]) data = recvuntil(s, b': ') print(data + '1') s.sendall(b'1\n') data = recvuntil(s, b': ') print(data + hexlify(pt).decode()) s.sendall(hexlify(pt) + b'\n') data = recvuntil(s, b'\n').rstrip() print(data) h_tag1 = data.split(' ')[-1] h_pt = strxor(pt[16:], tag0).hex() data = recvuntil(s, b': ') print(data + '2') s.sendall(b'2\n') data = recvuntil(s, b': ') print(data + h_pt) s.sendall(h_pt.encode() + b'\n') data = recvuntil(s, b': ') print(data + h_tag1) s.sendall(h_tag1.encode() + b'\n') data = recvuntil(s, b'\n').rstrip() print(data)
実行結果は以下の通り。
Team Rocket told me CBC-MAC with arbitrary-length messages is safe from forgery. If you manage to forge a message you haven't queried using my oracle, I'll give you something in return. What would you like to do? (1) MAC Query (2) Forgery (3) Exit Choice: 1 msg (hex): 61616161616161616161616161616161 CBC-MAC(msg): ac1df0534caa0b03b707b955e22984d1 What would you like to do? (1) MAC Query (2) Forgery (3) Exit Choice: 1 msg (hex): 6161616161616161616161616161616161616161616161616161616161616161 CBC-MAC(msg): 679b4c2357e1d15a3512a3c4309f89dc What would you like to do? (1) MAC Query (2) Forgery (3) Exit Choice: 2 msg (hex): cd7c91322dcb6a62d666d8348348e5b0 tag (hex): 679b4c2357e1d15a3512a3c4309f89dc If you reach this point, I guess we need to find a better MAC (and not trust TR). UMDCTF{Th!s_M@C_Sch3M3_1s_0nly_S3cur3_f0r_f!xed_l3ngth_m3ss4g3s_78232813}
UMDCTF{Th!s_M@C_Sch3M3_1s_0nly_S3cur3_f0r_f!xed_l3ngth_m3ss4g3s_78232813}
Noisy Bits (crypto)
暗号化処理の概要は以下の通り。
・POLY = 0xC75 ・FLAG_BIN_LENGTH = 360 ・binary_str: flagの2進数表記で360バイトになるよう"0"を先頭にパディング ・blocks: binary_strを12バイトごとのブロック配列にしたもの ・block_nos: blocksの各値を10進数数値にしたもの ・encoded = [encode(cw) for cw in block_nos] ・encodedの各要素につき、1bitまたは3bit反転 ・encoded_bin: encodedの各要素を2進数にし、23バイトになるよう"0"を先頭にパディング ・encoded_binの各要素をスペース区切りで連結して出力
decodeするときは末尾12ビットを取得すればよい。encodeして1ビットまたは3ビット反転して、条件に合うものを探す。
#!/usr/bin/env python3 import itertools def encode(cw): cw = (cw & 0xfff) c = cw for i in range(1, 12+1): if cw & 1 != 0: cw = cw ^ POLY cw = cw >> 1 return (cw << 12) | c def get_bits_list_for_all_cases(s): ret = [] for r in [1, 3]: for x in itertools.combinations(range(23), r): c = list(s) for i in range(r): c[x[i]] = str(int(c[x[i]]) ^ 1) c = ''.join(c) ret.append(c) return ret POLY = 0xC75 with open('output.txt', 'r') as f: encoded_bin = f.read().rstrip().split(' ') bin_flag = '' for i in range(len(encoded_bin)): c = encoded_bin[i] bits = get_bits_list_for_all_cases(c) for b in bits: int_b = int(b, 2) if encode(int(b[-12:], 2)) == int_b: bin_flag += b[-12:] break flag = '' for i in range(0, len(bin_flag), 8): flag += chr(int(bin_flag[i:i+8], 2)) print(flag)
UMDCTF{n0i5y_ch4nn3l5_ar3_n0t_@_pr0bleM_4_u}
Hidden Message (crypto)
大文字を"-"、小文字を"."にしてモールス信号として書き出す。
-- ----- .-. ... ...--! -.-. ----- -.. ...--! -- ...-- ....- -. ...! .... ....- ...- .---- -. --.! ... -----! -- ---.. -.-. ....! ..-. ---.. -. ... .---- ...-- ...!
これをデコードする。
m0rs3!c0d3!m34ns!h4v1ng!s0!m8ch!f8ns13s!
!を区切りとして"_"に変更して、フラグにする。
UMDCTF{M0RS3_C0D3_M34NS_H4V1NG_S0_M8CH_F8NS13S}
Reduce Thyself (crypto)
サーバの処理概要は以下の通り。
・n,e,d = gen_params() ・flag_ct = pow(b2l(FLAG), e, n) ・n, e, flag_ctを表示 ・以下繰り返し ・ct: 数値入力 ・pt = decrypt(flag_ct, ct, d, n) ・pt = 'null' ・ctが素数でflag_ctと異なる場合 ・pt = pow(ct, d, n)の文字列化したものの16進数表記 ・ptを返却 ・ptを表示
flag_ctに素数をかけてnで割った余りが素数になるものを探す。
(flag_ct * ct1) % n = ct2
pow(ct1, d, n)とpow(ct2, d, n)を取得して、以下の計算でフラグを求めることができる。
flag = pow(flag_ct, d, n) = pow(ct2, d, n) * inverse(pow(ct1, d, n), n) % n
#!/usr/bin/env python3 import socket from Crypto.Util.number import * from sympy import * 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(('0.cloud.chals.io', 33047)) data = recvuntil(s, b'\n').rstrip() print(data) n = int(data.split(' ')[-1], 16) data = recvuntil(s, b'\n').rstrip() print(data) e = int(data.split(' ')[-1], 16) data = recvuntil(s, b'\n').rstrip() print(data) flag_ct = int(data.split(' ')[-1], 16) ct1 = 2 while True: ct2 = (flag_ct * ct1) % n if isPrime(ct2): break ct1 = nextprime(ct1) data = recvuntil(s, b': ') print(data + hex(ct1)) s.sendall(hex(ct1).encode() + b'\n') data = recvuntil(s, b'\n').rstrip() print(data) pt1 = int(data.split(' ')[-1], 16) data = recvuntil(s, b': ') print(data + hex(ct2)) s.sendall(hex(ct2).encode() + b'\n') data = recvuntil(s, b'\n').rstrip() print(data) pt2 = int(data.split(' ')[-1], 16) m = pt2 * inverse(pt1, n) % n flag = long_to_bytes(m).decode() print(flag)
実行結果は以下の通り。
n: 0x7fdfa59c3868e12f74f7628a2b4488b48c19f97ed923f0bdfa3b475488956d3502f99b38c8ab00b147c34749d5677f526d8f073c499e11878bb87f1fa514f59a26984557bf7537d2a5a55daa0dd378fc4ed33716de7b8cfaf44928d3f6cd3b6cbeb6a6ad38afa32e1564146cf6d7d72cc00879608b5d0658e5e2338087eecf6ca238bb0b13a8e0ceaed32ee49941bebb7bd32b6a385b547b92720c607e9ec5cfb9e8e0731bb0510215002066ec26dea5ccdb433badad29fdf7861ecd2d362297d5b455bfbcda35e8ea30a11e3341a1ee1e04d32db2116d60c3d7b06490d2e6f6f153cafe49a3b03932e29e0029428fd90ca7965df95264e0dcfbf717c7f4ce23 e: 0x10001 flag_ct: 0x18eacb5008c17c9c6d87bd74f13f28c1545d63a280d708bad7d068e21a2216c2e853585c2a303af8639764f96ee008b3a447b4af730a42d5004c86d5d5253c36af6f3cc286e0c46145a87de48d90c8ab31770189153ab7507df8a37bb98736fe8080bf1a630d7779f24053b7bb197231c120f7557b6e2c732bbb2a963196eb6f071ef0210d9076670328f97334c9515baf91276499c4c906bf6c057a7a581d2fc54439199a557ce8cf3a658314c0ade2634e2ff9655707bc505af560c49a3a9ff84e6aeb0accaa20079dce743041da119f7f96db230d16469433f6095d7c58228642d49a3525bf41fbcaf419445c13fdf03bd5094d816a3d41a44c158dd0b914 Gimme ct (hex): 0x1039 result: 047b49c80743ef864353a8f2fb71f0de6c7508a1d1b2aab4e406653c73f9b29b1efbe92a58abe22ed7f9f2eccab77646b85425569305bb9fe59baed27646908469b8013178b4165a508f1f13a172d4c2980d87e7a6f72c14f9251ee476024c640a3b68f27836d82f7e9211550c7d47a0277a1f2482d1260321c3a42b04de1d4b83cb025e50ddad351363c32f841aed1c2ec497af35aa201768325ec88d9c06f5c94231ff275cca38848655749c021565b0d0055833ec01cf9328a2e9aa53e38a8f713b0ad56c4fd5cdf0cfd0cbe0944200655fac6a52caeeb3e07b86ac556dc53a7a07a774f33f7fb98f9e2befa47984a3bb25bb4d06ece620c6f4a735205101 Gimme ct (hex): 0x1f39eaafc76ee6b43d4a177aee0825c2e0efe55fedd8d6bb4ea111d652cd23648948e8b64407774bdae8ca244c5bcec7d64454bc9cf0cc194f6da7ee19d1af000c5d8d720ae4655592cd5256e07e0be05a1ed194511753bd9b621c4dd3fa5c0bf96779a8c26c8ca95c4993422fb68cbb3504d7a6f5560971f7811d5ce7a3dddddf9e7233ebe25ffbca578d8b052519e8db73dc5670c333f594fbc2fa9bb25e84aed5049634a75d356cf2663b62ef43a89e80cd4528a2ca605cbf587189edbb50d9418416649f848f7b5d1272e1e1ee679f96ed93e63bb82f5a55f01febb5e5b3705e7c117b13d0abf14c9f639667bda53d08d4f17313b1238a166f82ba6b08d9 result: 7b0e948ba1f395f6cf968cdc96489705767fee9ca928466dc6ab309afc4c099a6e7aeec5927ab0cf54adf3fd4ad5dbcecdecf1e760ac7aac75522becae3a4f75c4a93a18fee311a9edf3e2193a7092d7ac402d6d14c3bc95a87290b699e3af563161cffeeb57a95da48f7b756a30a5d6eb95a38d9bfdcc0dcbe9391e9b1786be8a6aab0c5ca71bfc68d2bb8fcbf58934bd328a828d21bd9b1a1e5af35c604b549001ab2fa4c781da24d21ad0c44e80ccad639a5df00dd433808937ca8130314da1e6e9e8085515918931d55279de92f38db5f2758ad195c38e2d10323e223c5a858514158db534eb5a9ed75897d293b4057eb56f2f7fc827e024b851a7d1e142 UMDCTF{s3lf_r3duc!bility_1s_n0t_ju5t_f0r_DH_97912837923}
UMDCTF{s3lf_r3duc!bility_1s_n0t_ju5t_f0r_DH_97912837923}