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:

  1. Reads user input (the flag)
  2. Strips the KCTF{...} wrapper and extracts the inner content
  3. For each character at index i, builds a string in the format salt + 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
  1. Hashes that string with MD5 and compares it against the hardcoded hash table
  2. 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}

14c27ddac401a76eff2848bd6f809604.png