>Ez Flag Checker
Author: rand0m (kafka)
Category: Reverse
Flag format: SECCON{...}
>Challenge Summary
A small ELF64 binary (chall) reads input from stdin and checks whether it matches a stored encrypted value. The binary contains:
-
An embedded ciphertext
flag_encin .rodata -
A function
sigma_encryptthat initializes a 16-byte sigma array from 4 32-bit words and XORs the input with a keystream derived from this array.
This challenge is about reversing the encryption/keystream to recover the flag.
>Tools used
-
file,strings, andnmto view metadata and symbols -
gdbfor disassembly and runtime inspection -
xxdto dump bytes from binary -
python3to write a decryption script and verify the flag
>Quick Static Recon
-
file challshows ELF 64-bit x86_64 PIE. -
strings challrevealsSECCON{and some function names such assigma_encrypt. -
nm -C challshows a symbolsigma_encryptand aflag_encsymbol in.rodata.
Use gdb to disassemble main and sigma_encrypt:
sigma_encryptdoes two things:
1. Build an array of 16 bytes derived from a sigma_words table (four 32-bit words). These bytes are the ASCII string 'expand 32-byte k' (the common ChaCha constant used in ChaCha20), stored in little-endian order.
2. For each byte of input message (index i), it computes a key byte as key = (sigma_bytes[i % 16] + i) & 0xff and XORs the input byte with key to produce the output.
mainreads a user-supplied input, validates length (the expected flag string length is 26 characters), verifies prefixSECCON{and suffix}and then runssigma_encrypton the input's middle portion (afterSECCON{and before}) and compares the produced ciphertext against the embeddedflag_enc.
Therefore, to recover the flag we can:
-
Extract
flag_encfrom the binary -
Reconstruct
sigma_bytesfromsigma_words -
Reverse the XOR key to recover the plaintext middle of the flag
>Key Observations
-
sigma_wordsis the 32-bit constant sequence that spells “expand 32-byte k” in ASCII when read in little-endian byte order, which is used as the base bytes for a 16-byte key. -
The actual keystream used to XOR the message is the sigma byte for position
i % 16plus the positioni(mod 256). -
The XOR operation used is symmetric, so decryption is the same process as encryption: XOR the encrypted byte with the same keystream.
>Steps to Recover Flag
- Extract
flag_encfrom the binary (e.g., usingxxdor examine memory withgdb).
Example using gdb:
# Show 18 bytes of the embedded ciphertext
gdb -q -batch -ex 'x/18bx flag_enc' ./chall
This prints the bytes of flag_enc.
-
Construct
sigma_bytesby breaking the four 32-bitsigma_wordsinto bytes (little-endian) — you can extract these withgdbtoo, or read the bytes atsigma_wordsoffset. -
For each ciphertext byte at position
i, compute the key byte:key = (sigma_bytes[i % 16] + i) & 0xffand then compute plaintext asplain_byte = enc_byte ^ key. -
Combine the plaintext bytes and wrap with
SECCON{and}.
>Reproduction Script (decrypt.py)
You can use this small Python script to decrypt flag_enc and verify the flag.
#!/usr/bin/env python3
sigma_words = [0x61707865, 0x3320646e, 0x79622d32, 0x6b206574]
# Build sigma_bytes by taking each word in little-endian byte order
sigma_bytes = []
for w in sigma_words:
for shift in (0, 8, 16, 24):
sigma_bytes.append((w >> shift) & 0xff)
# Replace the following with extracted bytes from the binary's flag_enc
flag_enc = bytes([0x03, 0x15, 0x13, 0x03, 0x11, 0x55, 0x1f, 0x43, 0x63, 0x61, 0x59, 0xef, 0xbc, 0x10, 0x1f, 0x43, 0x54, 0xa8])
plain = bytearray()
for i, b in enumerate(flag_enc):
key = (sigma_bytes[i % 16] + i) & 0xff
plain.append(b ^ key)
print('Plain middle:', plain.decode('utf-8'))
print('Flag: SECCON{' + plain.decode('utf-8') + '}')
# To verify, you can re-encrypt and compare
enc_again = bytearray()
for i, c in enumerate(plain):
key = (sigma_bytes[i % 16] + i) & 0xff
enc_again.append(c ^ key)
print('Matches original enc:', bytes(enc_again) == flag_enc)
>Example Commands Used During Analysis
- Inspect binary and symbols:
file chall
strings chall | grep SECCON
nm -C chall
- Examine embedded rodata and constants with
gdb:
gdb -q -batch -ex 'x/18bx flag_enc' -ex 'x/16bx sigma_words' ./chall
- Inspect strings used by
mainto confirm messages and offsets:
gdb -q -batch -ex "x/s 0x2022" -ex "x/s 0x2031" -ex "x/s 0x203a" -ex "x/s 0x2042" ./chall
- Run and test input:
printf 'SECCON{flagc29yYW5k<b19!!}\n' | ./chall
# Expected output: correct flag!
>Final Answer
The decrypted middle of the flag is:
flagc29yYW5k<b19!!
So the full flag is:
SECCON{flagc29yYW5k<b19!!}
>Wrap-up / Notes
-
The
sigma_wordsstringexpand 32-byte kindicates the author borrowed the ChaCha20 constant (fun easter egg), but implemented a simple XOR with a derived keystream rather than a full ChaCha20 round. -
The keystream is trivially reversible (XOR with known keystream), so the challenge reduces to extracting constants and inverting the XOR.
If you'd like, I can also:
-
Add a fully runnable script that extracts
flag_encandsigma_wordsautomatically from the binary withpyelftools, or -
Clean up the Python script for more general use and add a short README with run instructions.