KnightCTF 2026 - Reversing 50 - E4sy P3asy
A stripped ELF binary that validates a flag by MD5-hashing each character against a salt and comparing it to a hardcoded hash table. Solved with a simple Python brute-force script.
Task
An easy RE…
Flag Format:
KCTF{flag}File: E4sy_P3asy.ks
file E4sy_P3asy.ks
# ELF 64-bit LSB pie executable, x86-64, stripped
Running strings on the binary reveals a hash table and some OpenSSL MD5 function names, which tells us exactly what the validation mechanism is:
EVP_MD_CTX_new
EVP_md5
EVP_DigestUpdate
EVP_DigestFinal_ex
781011edfb2127ee5ff82b06bb1d2959
4cf891e0ddadbcaae8e8c2dc8bb15ea0
...
Reversing
The binary follows this logic in order:
- Reads user input (the flag)
- Strips the
KCTF{...}wrapper and extracts the inner content - For each character at index
i, builds a string in the formatsalt + index + char:
snprintf(&var_2c8, 0x80, "%s%zu%c", var_358, i, (&var_148)[i])
sub_401660(&var_2c8, strlen(&var_2c8), &var_338)
if (strcmp(&var_338, (&data_403ca0)[i]) != 0)
fail
- Hashes that string with MD5 and compares it against the hardcoded hash table
- If all 23 hashes match, prints
Good job! You got it!
The salt is KnightCTF_2026_s@lt (visible in the binary). There is also a decoy path for GoogleCTF{...} input using a different salt (G00gleCTF_s@lt_2026) that prints a taunting message — ignore it.
Solution
The validation structure maps to:
expected[i] == MD5("KnightCTF_2026_s@lt" + str(i) + flag[i])
Since we have all the expected hashes and the salt, we can brute-force each position independently by trying every printable ASCII character:
import hashlib
targets = [
"781011edfb2127ee5ff82b06bb1d2959",
"4cf891e0ddadbcaae8e8c2dc8bb15ea0",
"d06d0cbe140d0a1de7410b0b888f22b4",
"d44c9a9b9f9d1c28d0904d6a2ee3e109",
"e20ab37bee9d2a1f9ca3d914b0e98f09",
"d0beea4ce1c12190db64d10a82b96ef8",
"ac87da74d381d253820bcf4e5f19fcea",
"ce3f3a34a04ba5e5142f5db272b6cb1f",
"13843aca227ef709694bbfe4e5a32203",
"ca19a4c4eb435cb44d74c1e589e51a10",
"19edec8e46bdf97e3018569c0a60baa3",
"972e078458ce3cb6e32f795ff4972718",
"071824f6039981e9c57725453e005beb",
"66cd6098426b0e69e30e7fa360310728",
"f78d152df5d277d0ab7d25fb7d1841f3",
"dba3a36431c4aaf593566f7421abaa22",
"8820bbdad85ebee06632c379231cfb6b",
"722bc7cde7d548b81c5996519e1b0f0f",
"c2862c390c830eb3c740ade576d64773",
"94da978fe383b341f9588f9bab246774",
"bea3bb724dbd1704cf45aea8e73c01e1",
"ade2289739760fa27fd4f7d4ffbc722d",
"3cd0538114fe416b32cdd814e2ee57b3",
]
salt = "KnightCTF_2026_s@lt"
flag = ""
for i in range(len(targets)):
for c in range(32, 127):
s = f"{salt}{i}{chr(c)}".encode()
h = hashlib.md5(s).hexdigest()
if h == targets[i]:
flag += chr(c)
break
print("KCTF{" + flag + "}")
Running it:
python crack3.py
# KCTF{_L0TS_oF_bRuTE_foRCE_:P}
Verifying against the binary:
./E4sy_P3asy.ks
# flag> KCTF{_L0TS_oF_bRuTE_foRCE_:P}
# Good job! You got it!
Flag
Flag: KCTF{L0TS_oF_bRuTE_foRCE:P}