CSIT Mini CTF 2026 Walkthrough

The CSIT Mini CTF 2026 presented us with a 2 stage Windows binary. The first stage downloads an ascii payload from https://minictf2026.s3.us-east-1.amazonaws.com/enc_release-2.txt, performs some decoding where the bits were inverted and reversed, and then loaded into memory and executed. The simpler way to get hold of the decoded binary is to search for where output.exe is referenced, insert a breakpoint there and inspect the memory to get the decoded payload. The rationale is that the payload should already be in plaintext since it is close to where it is written out, hence doing so allow us to skip though the other non-relevant parts.

The second stage payload iterates through random strings, hashes them and checks if it matches 12 pre-specified MD5 hashes. If it matches, the string is encrypted using PKCS1_OAEP and then base64 encoded and sent out over the network. The server probably performs the functions in reverse. It base64 decodes each line, uses the RSA private key to decrypt and verifies that all 12 plaintext strings were sent. When the network traffic for the second stage payload is inspected in wireshark, the observant would notice only 11 base64 encoded strings, and deduce that since there were 12 MD5 hashes in the binary, that could be the missing key. The final MD5 hash, 3bf1114a986ba87ed28fc1b5884fc2f8 pre-image could be trivially cracked since it corresponded to shadow.

Solving the second stage would thus require either patching the binary to include shadow in the plaintext string, or to fully reverse the entire binary and write a python script to RSA encrypt each plaintext string, base64 encode it and send it over the network. The former option would be simpler and is probably the intended solution.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
# 0x0042C3B8    32 B    MD5 hash[0]: 'a3527a5dabb280c471b07f15becb9f12'.
# 0x0042C3DC    32 B    MD5 hash[1]: 'e0e7aa5bcd7cfdde08e71cf6f6c54cf6'.
# 0x0042C400    32 B    MD5 hash[2]: 'dbc5c57d2d12ca4e7a794ad60944e1ff'.
# 0x0042C424    32 B    MD5 hash[3]: '24b499ce8a4c42e58248a09fa33f73aa'.
# 0x0042C448    32 B    MD5 hash[4]: 'a471ad29ba98d59c1b2d9c4195194d66'.
# 0x0042C46C    32 B    MD5 hash[5]: 'f1a0848f41ff40bbfd7f362c4b96a054'.
# 0x0042C490    32 B    MD5 hash[6]: '96541659008679773ff430af165a4453'.
# 0x0042C4B4    32 B    MD5 hash[7]: '838bf61cff974fecea5e3eaa1deb1fca'.
# 0x0042C4D8    32 B    MD5 hash[8]: '93f40ec1f4293ef041e43bcca112b0f3'.
# 0x0042C4FC    32 B    MD5 hash[9]: '91ea4b818957448c2be90ed980f4d280'.
# 0x0042C520    32 B    MD5 hash[10]: '6c5f6b0b8fa5e15ed59faf633cb4ccf2'.
# 0x0042C544    32 B    MD5 hash[11]: '3bf1114a986ba87ed28fc1b5884fc2f8'.


import hashlib


crib = '4304br1r7n lr1qunn4c363d0 1fcb040un75 513qd3nlp541u4 nc10lc1ru7cm0u q0munn7146l3 p475r3cp1y1c f11l74bf3y1n 50u7nr703p 1h13bnrl7yn4 4n7c07cn034n1 63clp071430m1l5 l37mm71d44nn4141r3155bn517h5 1lpl0ry3blhcy4 006y7urr5p3r34 mur7chl6u44471 06l0l7014nc 37346v0nc11m7 3r44fr13b6rl ml3lu5lf0u1 5711l17ln4n0c k31pcl50cd410 vp4c34r7110nr 451j7nx0up017 lp515c11705 5lu0b61uru l71yx0rp1 56r171v03374nr 401r01477cnn1 05uccf14fn113 7474117754n5rnun0b x43l7c1363 71py3n53rck m1l1l50un54pu 14436d13nf7bl l1cp6104ll0h 053unnr3f5415 7c3474n30cn1v 15luy41ml7n1b 045hc0673c41ll r77cm1c54n47yul11 r1y1qpp0u7n 3lun74cl13b n1q4u71300cv 3fv7n5133nfrc3 cc416u750nr n0cl73p415 411nnful0m7 p3n0643n1r7r1 u50br5r373p0 64r01y71n5d n3rn5l1fcf3073 c4rbl33x3 1f4pncl701 30rclh4y5m 364l7r x7nu33 rp01lx 5uu3015q0b lnu7r37uc 30ru17rm1c53 43n70ndc7c34 nc3d710r3 17h0550p34 ff11bn7y135l4 64frl3351p 41n1nl734duv 55crcyn71173 63r1n147n75n 331pm15c7 r034417munplb 137c63x3 4rn0nc43770c 015l33qbn5u 3cp1xlhy3r nrp4l3u74r3r7 ffbc1l117134ny 04cu10c7mnu5 pq31l5u573y4d1 d1yn640rulq 14mmcnc4rulb710u 036lh5453017r c0lh3r10r6 r474r34n13vm7 cppl5417m513 y44700c73nncr 3nn41brl71m3 71lpf5y3uru p145l0lcbyly l3r0h7cp1 n3c3rvff3135 74n310016c7x 0xrp110lu5 4ycr7u0fb50 r31650dn4 p55cup30u1r 35n371ru l07c43ncn4b3 cn3x704r13 4n1myn416m7 shadow'

# can't find the input for the last MD5 hash
for key in crib.split(' '):
    hashaa = hashlib.md5(key.encode('utf-8')).hexdigest()
    if hashaa == '3bf1114a986ba87ed28fc1b5884fc2f8':
        print(key)


order = ['p475r3cp1y1c', '50u7nr703p', 'vp4c34r7110nr', '451j7nx0up017', 'm1l1l50un54pu', 'n0cl73p415', '43n70ndc7c34', 'r034417munplb', '04cu10c7mnu5', 'c0lh3r10r6', 'n3c3rvff3135']

#from Crypto.Cipher import PKCS1_OAEP
#from Crypto.PublicKey import RSA
#import base64

#der = #bytes.fromhex('30820122300D06092A864886F70D01010105000382010F003082010A0282010100E2F0578560B35A55FAA8BF8441889A2AE2D54A547CE62ECBB3D015AC8A2C55559B2882FC2455A04C7BB24499CF1D6AA70A9FE859C6B3D91926C7EAD4D2A08D4064120FC9713914BD229C0739B0334EDC51CB07DE878C7C559FDD17FAE6D456884E3C80A2DB745699767B3F781B4B7797858E19000D70685413C1DE3F2828BFC60424C0E3D39193B20B721EF3B3BD4C01ACB8E4AED1126D0836CF230199573B44E0281715C29DF79D1FDC1B6D818D9F9EF98A24743EA435E34B928EBDEAF3ECF5FE2B584188E87C035E08D1236B405695B9F875531BCC0E94F35A411E665F4F6083F48EF67EAB2EDE1B632AED00A0E08C022F4C0E6633A5DEB4CC0A981840C6810203010001')
#key = RSA.import_key(der)
#cipher = PKCS1_OAEP.new(key)
#for each in order:
#   ct = base64.b64encode(cipher.encrypt(each.encode("utf-8")))
    #print(ct.decode("utf-8"))
#   print(each)

Video Walkthrough here.