# Ez Flag Checker

Co-authors: @Xyzst @LZDQ

Reverse enginnered by AI:

# Attack Methodology for Ez Flag Checker

## Reverse Engineering Process

1. **Binary Analysis**: The challenge provides a 64-bit ELF binary with debug symbols (not stripped).
2. **Main Function**: The program reads a flag, validates it's 26 characters with format `SECCON{...}`, then encrypts the 18-character inner part using `sigma_encrypt()`.
3. **Encryption Algorithm**: `sigma_encrypt()` implements a simple XOR cipher:

   - Key material: `sigma_words` contains "expand 32-byte k" (ChaCha20 constant)
   - Encryption: `out[i] = (i + key_bytes[i & 0xF]) ^ message[i]`
   - Where `key_bytes` is derived from `sigma_words` (4 little-endian dwords converted to bytes)
4. **Comparison**: Encrypted result is compared against hardcoded `flag_enc` (18 bytes).

## Solution

The encryption is reversible (XOR). Decryption formula:

- `message[i] = (i + key_bytes[i & 0xF]) ^ flag_enc[i]`

Where:

- `flag_enc` = `03 15 13 03 11 55 1f 43 63 61 59 ef bc 10 1f 43 54 a8`
- `key_bytes` derived from "expand 32-byte k":

  - Bytes: `65 78 70 61 6e 64 20 33 32 2d 62 79 74 65 20 6b`
  - Repeats every 16 bytes (i &amp; 0xF)

## Decrypted Flag

Inner flag: `flagc<9yYW5k<b19!!`

Full flag: `SECCON{flagc<9yYW5k<b19!!}`

## Verification

A test C program confirms this flag encrypts to the exact `flag_enc` bytes. However, the actual binary rejects this input for unknown reasons (possibly environment or implementation difference).

## Tools Used

- Python for decryption script
- C for verification
- Analysis of assembly code and memory layout

However, the flag is wrong. After debugging, we found that, the encryption key is changed to `expanb 32-byte k` in `_dl_main`. So the correct flag should be `SECCON{flagc29yYW5k<b19!!}`.

Attack script:

```
#!/usr/bin/env python3

# Encrypted flag from binary
flag_enc = bytes([
    0x03, 0x15, 0x13, 0x03, 0x11, 0x55, 0x1f, 0x43,
    0x63, 0x61, 0x59, 0xef, 0xbc, 0x10, 0x1f, 0x43,
    0x54, 0xa8
])

# sigma_words should be "expand 32-byte k" but there appears to be a bug
# that causes key_bytes[5] to be 0x62 ('b') instead of 0x64 ('d')
# This results in '2' at position 5 instead of '<'
sigma_words = b"expand 32-byte k"

# Reconstruct key_bytes as in sigma_encrypt function
# The function takes 4 dwords from sigma_words and converts to bytes in little-endian
key_bytes = bytearray(24)

# Convert sigma_words to key_bytes (little-endian dwords to bytes)
for i in range(4):
    w = int.from_bytes(sigma_words[i*4:(i+1)*4], 'little')
    key_bytes[4*i] = w & 0xFF
    key_bytes[4*i + 1] = (w >> 8) & 0xFF
    key_bytes[4*i + 2] = (w >> 16) & 0xFF
    key_bytes[4*i + 3] = (w >> 24) & 0xFF

print(f"Key bytes from sigma_words: {key_bytes.hex()}")
print(f"Key bytes length: {len(key_bytes)}")

# The binary appears to have a bug where key_bytes[5] is 0x62 instead of 0x64
# This could be due to an off-by-one error, buffer overflow, or other issue
# For the correct flag, we need key_bytes[5] = 0x62
key_bytes[5] = 0x62  # Fix the bug

print(f"Key bytes with bug fix: {key_bytes.hex()}")

# Decrypt the flag
# out[i] = (i + key_bytes[i & 0xF]) ^ message[i]
# So: message[i] = (i + key_bytes[i & 0xF]) ^ out[i]

decrypted = bytearray(len(flag_enc))
for i in range(len(flag_enc)):
    key_byte = key_bytes[i & 0xF]
    decrypted[i] = (i + key_byte) ^ flag_enc[i]

print(f"Decrypted inner flag: {decrypted}")
print(f"Decrypted inner flag (ascii): {decrypted.decode('ascii', errors='replace')}")

# Full flag
full_flag = f"SECCON{{{decrypted.decode('ascii')}}}"
print(f"Full flag: {full_flag}")
```
