← Back to list

HTB AI vs Human CTF Challenge — Crypto: Repeated Maleficence

After analyzing the source code, we discovered that the flag is XORed with a randomly generated key of length 5. Since the key is random…

MrX · 2025-03-17 21:49 · 18 claps · 2.5 min read
#crypto #cryptography #xor
Open on Medium ↗
Wiki topics: CRY · Crypto & Web3 🔒 · Cybersecurity

HTB AI vs Human CTF Challenge — Crypto: Repeated Maleficence

After analyzing the source code, we discovered that the flag is XORed with a randomly generated key of length 5. Since the key is random, recovering the exact key directly would be quite challenging.

However, we know that all flags follow the format HTB{some_text}, meaning the first four bytes of the plaintext are always HTB{. This allows us to recover the first four bytes of the key by XORing them with the corresponding encrypted bytes.

For the remaining fifth byte of the key, we can brute-force all possible values. Here is the script to do it:

def recover_key(enc, known_plaintext):
    key = bytearray(len(known_plaintext))
    for i in range(len(known_plaintext)):
        key[i] = enc[i] ^ known_plaintext[i]  # XOR encrypted byte with known plaintext byte
    return key

def brute_force_fifth_byte(enc, known_plaintext):
    # Try all printable ASCII characters for the 5th byte of plaintext
    for i in range(32, 127):  # Printable ASCII characters range
        # Create a candidate for the 5th byte of the plaintext
        candidate_plaintext = known_plaintext + bytes([i])

        # Recover key using the first 4 characters and this guess for the 5th byte
        recovered_key_part = recover_key(enc[:5], candidate_plaintext)

        # Here, you could check if the recovered key fits some condition (e.g., validity of decrypted message)
        print(f"Trying plaintext with 5th byte {chr(i)}: Recovered Key: {recovered_key_part.hex()}")

known_plaintext = b'HTB{'  # The first 4 characters of the original message
enc = bytes.fromhex("720c4103880a2a5c49a3652f304c9b652f324d9865336d488754077349c40b36364b880f25")  # Replace with actual encrypted data

brute_force_fifth_byte(enc, known_plaintext)

Possible keys for flag

Possible keys for flag

Using each potential key, we will decrypt the flag and obtain multiple possible outputs. Here is the script:

def xor_decrypt(ciphertext, key):
    """XOR the ciphertext with the given key."""
    decrypted = bytearray()
    for i in range(len(ciphertext)):
        decrypted.append(ciphertext[i] ^ key[i % len(key)])
    return bytes(decrypted)

def try_decrypt_with_keys(encrypted_hex, keys_hex):
    """Try to decrypt the encrypted text with all given keys."""
    encrypted_data = bytes.fromhex(encrypted_hex)

    for key_hex in keys_hex:
        key = bytes.fromhex(key_hex)
        decrypted_data = xor_decrypt(encrypted_data, key)

        try:
            # Attempt to decode the decrypted text as UTF-8 (this might fail if the text is not UTF-8)
            decrypted_text = decrypted_data.decode('utf-8')
            print(f"Decrypted with key {key_hex}: {decrypted_text}")
        except UnicodeDecodeError:
            # If decoding fails, print raw bytes
            print(f"Decrypted with key {key_hex}: (binary output) {decrypted_data.hex()}")

if __name__ == '__main__':
    # Encrypted data (replace with the actual encrypted text)
    encrypted_hex = "720c4103880a2a5c49a3652f304c9b652f324d9865336d488754077349c40b36364b880f25"

    # List of keys in hexadecimal
    keys_hex = [
        "3a580378a8", "3a580378a9", "3a580378aa", "3a580378ab", "3a580378ac",
        "3a580378ad", "3a580378ae", "3a580378af", "3a580378a0", "3a580378a1",
        "3a580378a2", "3a580378a3", "3a580378a4", "3a580378a5", "3a580378a6",
        "3a580378a7", "3a580378b8", "3a580378b9", "3a580378ba", "3a580378bb",
        "3a580378bc", "3a580378bd", "3a580378be", "3a580378bf", "3a580378b0",
        "3a580378b1", "3a580378b2", "3a580378b3", "3a580378b4", "3a580378b5",
        "3a580378b6", "3a580378b7", "3a580378c8", "3a580378c9", "3a580378ca",
        "3a580378cb", "3a580378cc", "3a580378cd", "3a580378ce", "3a580378cf",
        "3a580378c0", "3a580378c1", "3a580378c2", "3a580378c3", "3a580378c4",
        "3a580378c5", "3a580378c6", "3a580378c7", "3a580378d8", "3a580378d9",
        "3a580378da", "3a580378db", "3a580378dc", "3a580378dd", "3a580378de",
        "3a580378df", "3a580378d0", "3a580378d1", "3a580378d2", "3a580378d3",
        "3a580378d4", "3a580378d5", "3a580378d6", "3a580378d7", "3a580378e8",
        "3a580378e9", "3a580378ea", "3a580378eb", "3a580378ec", "3a580378ed",
        "3a580378ee", "3a580378ef", "3a580378e0", "3a580378e1", "3a580378e2",
        "3a580378e3", "3a580378e4", "3a580378e5", "3a580378e6", "3a580378e7",
        "3a580378f8", "3a580378f9", "3a580378fa", "3a580378fb", "3a580378fc",
        "3a580378fd", "3a580378fe", "3a580378ff", "3a580378f0", "3a580378f1",
        "3a580378f2", "3a580378f3", "3a580378f4", "3a580378f5", "3a580378f6"
    ]

    # Try to decrypt using each key
    try_decrypt_with_keys(encrypted_hex, keys_hex)

Since all valid flags follow the standard HTB{…} format, we can manually analyze the results to identify the correct flag.

flag is marked here

flag is marked here

After analyzing we found the correct flag.


메타데이터
post_id
d0c0e096800f
slug
htb-ai-vs-human-ctf-challenge-crypto-repeated-maleficence-d0c0e096800f
url
https://medium.com/@MrX2025/htb-ai-vs-human-ctf-challenge-crypto-repeated-maleficence-d0c0e096800f
canonical_url
https://medium.com/@MrX2025/htb-ai-vs-human-ctf-challenge-crypto-repeated-maleficence-d0c0e096800f
author_url
https://medium.com/@MrX2025
status
ok
fetched_at
2026-06-15 20:49:13