Skip to content

SECURE_CONNECTION//PRESS[CTRL+J]FOR ROOT ACCESS

BACK TO INTEL
ReverseMedium

Ez Flag Checker

CTF writeup for Ez Flag Checker from SECCON

>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_enc in .rodata

  • A function sigma_encrypt that 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, and nm to view metadata and symbols

  • gdb for disassembly and runtime inspection

  • xxd to dump bytes from binary

  • python3 to write a decryption script and verify the flag


>Quick Static Recon

  • file chall shows ELF 64-bit x86_64 PIE.

  • strings chall reveals SECCON{ and some function names such as sigma_encrypt.

  • nm -C chall shows a symbol sigma_encrypt and a flag_enc symbol in .rodata.

Use gdb to disassemble main and sigma_encrypt:

  • sigma_encrypt does 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.

  • main reads a user-supplied input, validates length (the expected flag string length is 26 characters), verifies prefix SECCON{ and suffix } and then runs sigma_encrypt on the input's middle portion (after SECCON{ and before }) and compares the produced ciphertext against the embedded flag_enc.

Therefore, to recover the flag we can:

  • Extract flag_enc from the binary

  • Reconstruct sigma_bytes from sigma_words

  • Reverse the XOR key to recover the plaintext middle of the flag


>Key Observations

  • sigma_words is 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 % 16 plus the position i (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

  1. Extract flag_enc from the binary (e.g., using xxd or examine memory with gdb).

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.

  1. Construct sigma_bytes by breaking the four 32-bit sigma_words into bytes (little-endian) — you can extract these with gdb too, or read the bytes at sigma_words offset.

  2. For each ciphertext byte at position i, compute the key byte: key = (sigma_bytes[i % 16] + i) & 0xff and then compute plaintext as plain_byte = enc_byte ^ key.

  3. 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.

python

#!/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 main to 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_words string expand 32-byte k indicates 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_enc and sigma_words automatically from the binary with pyelftools, or

  • Clean up the Python script for more general use and add a short README with run instructions.