← Back to Writeups
CUSTOMMediumReversing

Chaos

XESXOR9/10/20263 min read
#reversing#custom#medium#dictionary_bruteforce#dotnet_decompilation#emoji_aes_decoding#hidden_input_trigger_discovery#openssl_aes_decryption

Chaos

Platform: hackerlab | Category: Reversing | Type: Challenge | Difficulty: Medium | OS: NA | Author: XESXOR | Date: 2026-04-06 | Status: Solved Techniques: dictionary_bruteforce, dotnet_decompilation, emoji_aes_decoding, hidden_input_trigger_discovery, openssl_aes_decryption

Summary

Task: a .NET WinForms app hides an emoji-only ciphertext behind a secret keyboard trigger and clipboard copy path. Solution: decompile Form1, trigger the hidden CHEAT branch, convert the emoji-aes text to OpenSSL salted AES, and brute-force the passphrase from a local wordlist.

Chaos — Hackerlab

Description

Хаос

English summary: the archive contains Chaos.exe, Chaos.dll, and Chaos.runtimeconfig.json. The executable is a .NET WinForms application. The goal is to reverse the program, recover the hidden ciphertext, identify the encryption scheme, and decrypt it to obtain the CODEBY{...} flag.

Analysis

Initial Recon

The provided ZIP archive contained:

  • Chaos.exe
  • Chaos.dll
  • Chaos.runtimeconfig.json

Because this is a .NET application, the fastest route is decompilation instead of low-level PE analysis. Decompiling the project and reviewing Emoji.Form1 / Form1.cs immediately revealed the interesting UI logic. ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Hidden Keyboard Trigger

The key function is Zero_KeyPress. It appends typed characters into an internal buffer and checks for a hardcoded trigger:

csharp
if (inputBuffer.Contains("CHEAT"))

When that condition becomes true, the application enters the hidden win path:

  • it shows WIN
  • it copies label1.Text to the clipboard

So the actual secret is not generated dynamically. It is already stored inside the form as label text and is only exposed after typing CHEAT.

Recovered Ciphertext

‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

label1.Text contained this 88-emoji ciphertext:

text
🙃💵🌿🎤🚪🌏🐎🥋🚫😆✅😊👌🏹🕹👌🍌🎃🎤👌🍴✉🌪☺👁✉🔄👁⏩🌪🎤🍵🍎🖐🔬🌪🍌🚨😍🐎😁🔪❓🐅✅📂🕹👑🐅👣☃ℹ🌊🖐🎅📮🏹👁🍎🙃🖐🎈🚪🌊🔬🐍🌿🖐😊😊🤣🕹😂📮☃🚪🍍🍎😀🎤😀😆⏩😁📂🏹🗒🗒

The user hint identified the scheme as emoji-aes. Using the standard public emoji-to-base64 alphabet used by emoji-aes implementations, the string decodes to:

text
U2FsdGVkX1+5oANob8soKlH4elReZHsJahSHbL9V0/YT+ONqTfIiWhDMAeaUhBdWSvFh553NjMIdpagsg1Z0OA==

That prefix is the biggest clue: U2FsdGVkX1 is Base64 for Salted__, which is the classic OpenSSL / CryptoJS salted AES format. ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Passphrase Recovery

At this point the problem becomes a passphrase search against a standard salted AES blob. A local dictionary attack against:

text
/Users/sergeyskorobogatov/Projects/Agents/CTF/resources/wordlists/passwords/2025-top200.txt

found the correct hit at line 55:

  • passphrase: 666666

Decrypting the ciphertext with that passphrase yielded the final flag.

Solution

Step 1 — Decompile the WinForms Application

Open the .NET binary in ILSpy or a similar decompiler and inspect Emoji.Form1. The important logic is in Zero_KeyPress, where the program accumulates user keystrokes and looks for CHEAT. ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Step 2 — Trigger the Hidden Win Branch

Typing CHEAT activates the secret branch. That branch displays WIN and copies label1.Text to the clipboard. The clipboard content is the real secret material to analyze.

Step 3 — Decode the Emoji Ciphertext

The clipboard data is not random emoji art. It matches the public emoji-aes alphabet, where each emoji corresponds to one Base64 character. Converting the 88 emoji symbols with that mapping gives a normal Base64 ciphertext in OpenSSL/CryptoJS salted format. ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Step 4 — Brute-Force the Passphrase

Try candidate passwords from the local wordlist against the decoded AES blob. The successful password is 666666, which decrypts the payload directly to the flag.

python
#!/usr/bin/env python3
from base64 import b64decode
from hashlib import md5

from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad


EMOJI_CT = "🙃💵🌿🎤🚪🌏🐎🥋🚫😆✅😊👌🏹🕹👌🍌🎃🎤👌🍴✉🌪☺👁✉🔄👁⏩🌪🎤🍵🍎🖐🔬🌪🍌🚨😍🐎😁🔪❓🐅✅📂🕹👑🐅👣☃ℹ🌊🖐🎅📮🏹👁🍎🙃🖐🎈🚪🌊🔬🐍🌿🖐😊😊🤣🕹😂📮☃🚪🍍🍎😀🎤😀😆⏩😁📂🏹🗒🗒"
OPENSSL_B64 = "U2FsdGVkX1+5oANob8soKlH4elReZHsJahSHbL9V0/YT+ONqTfIiWhDMAeaUhBdWSvFh553NjMIdpagsg1Z0OA=="
WORDLIST = "/Users/sergeyskorobogatov/Projects/Agents/CTF/resources/wordlists/passwords/2025-top200.txt"
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍


def evp_bytes_to_key(password: bytes, salt: bytes, key_len=32, iv_len=16):
    out = b""
    prev = b""
    while len(out) < key_len + iv_len:
        prev = md5(prev + password + salt).digest()
        out += prev
    return out[:key_len], out[key_len:key_len + iv_len]


def decrypt_openssl_blob(blob_b64: str, password: str) -> bytes:
    blob = b64decode(blob_b64)
    assert blob.startswith(b"Salted__")
    salt = blob[8:16]
    ciphertext = blob[16:]
    key, iv = evp_bytes_to_key(password.encode(), salt)
    cipher = AES.new(key, AES.MODE_CBC, iv)
    return unpad(cipher.decrypt(ciphertext), 16)
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍


def main():
    print(f"Recovered emoji ciphertext length: {len(EMOJI_CT)}")
    print(f"Decoded emoji-aes blob: {OPENSSL_B64}")

    with open(WORDLIST, "r", encoding="utf-8") as fh:
        for idx, line in enumerate(fh, 1):
            password = line.rstrip("\n")
            if not password:
                continue
            try:
                pt = decrypt_openssl_blob(OPENSSL_B64, password)
            except Exception:
                continue
            text = pt.decode("utf-8", errors="strict")
            if text.startswith("CODEBY{") and text.endswith("}"):
                print(f"Hit at line {idx}: {password}")
                print(text)
                return
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

    raise SystemExit("Password not found")


if __name__ == "__main__":
    main()

Expected output:

text
Hit at line 55: 666666
CODEBY{REDACTED}

‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍