Andor
Writeup: Andor
- Category: Crypto
- Difficulty: Easy
- Author: Alol
- Points: 50
1. Challenge Description
The challenge provides a TCP service (nc crypto.heroctf.fr 9000) and the corresponding Python source code. The service implements a loop that continuously generates random keys and performs bitwise operations against a static flag.
Source Code Analysis:
#!/usr/bin/env python3import secrets
# ... (Helper functions for AND/OR) ...
# The flag is split into two halvesl = len(flag) // 2
while True: # A new random key is generated every iteration k = secrets.token_bytes(len(flag))
# Half 1: Flag AND Random Key a = AND(flag[:l], k[:l])
# Half 2: Flag OR Random Key o = IOR(flag[l:], k[l:])
print("a =", bytearray(a).hex()) print("o =", bytearray(o).hex()) input("> ")2. Vulnerability Analysis
The core vulnerability lies in the reuse of the static flag against multiple random keys in an infinite loop. This allows for a statistical bit recovery attack.
Part 1: The AND Operation (a)
The operation is $Result = Flag \land Key$.
- If a bit in the
Flagis0, the result is always0(regardless of the key). - If a bit in the
Flagis1, the result is0or1(randomly determined by the key).
Strategy: By collecting multiple samples and applying a bitwise OR to all of them, we accumulate the 1s. Eventually, every position that has a 1 in the flag will appear as a 1 in our accumulator.
Part 2: The OR Operation (o)
The operation is $Result = Flag \lor Key$.
- If a bit in the
Flagis1, the result is always1(regardless of the key). - If a bit in the
Flagis0, the result is0or1(randomly determined by the key).
Strategy: By collecting multiple samples and applying a bitwise AND to all of them, we filter out the random 1s generated by the key. Eventually, only the positions that are truly 1 in the flag will remain 1 (and the 0s will be revealed).
3. Solution Script
We can automate the connection and statistical analysis using Python and pwntools.
from pwn import *
# Connection detailsHOST = "crypto.heroctf.fr"PORT = 9000SAMPLES = 60 # 60 samples are statistically sufficient
def solve(): print(f"[*] Connecting to {HOST}:{PORT}...") r = remote(HOST, PORT)
part1_accumulator = None # For AND operation (we will accumulate 1s using OR) part2_accumulator = None # For OR operation (we will filter noise using AND)
log.info(f"Collecting {SAMPLES} samples for statistical analysis...")
prog = log.progress("Analyzing")
for i in range(SAMPLES): try: # Receive data r.recvuntil(b"a = ") a_hex = r.recvline().strip().decode() r.recvuntil(b"o = ") o_hex = r.recvline().strip().decode()
# Convert hex to integer val_a = int(a_hex, 16) val_o = int(o_hex, 16)
if part1_accumulator is None: # Initialize accumulators with the first sample part1_accumulator = val_a part2_accumulator = val_o else: # Logic for Part 1 (AND Gate): # We want to find where the flag is 1. # If we see a 1 in any sample, the flag is 1. part1_accumulator |= val_a
# Logic for Part 2 (OR Gate): # We want to find where the flag is 0. # If we see a 0 in any sample, the flag is 0. part2_accumulator &= val_o
# Request next sample r.sendline(b"") prog.status(f"Sample {i+1}/{SAMPLES}")
except EOFError: break
prog.success("Analysis complete")
# Reconstruct the Flag # Calculate byte length (2 hex chars = 1 byte) byte_length = len(a_hex) // 2
flag_part1 = part1_accumulator.to_bytes(byte_length, 'big') flag_part2 = part2_accumulator.to_bytes(byte_length, 'big')
full_flag = (flag_part1 + flag_part2).decode(errors='ignore')
print(f"\n[+] RECOVERED FLAG: {full_flag}") r.close()
if __name__ == "__main__": solve()4. Execution Output
Running the script collects the samples, eliminates the cryptographic noise, and outputs the clean flag.
[*] Connecting to crypto.heroctf.fr:9000...[+] Opening connection to crypto.heroctf.fr on port 9000: Done[*] Collecting 60 samples for statistical analysis...[+] Analyzing: Analysis complete
[+] RECOVERED FLAG: Hero{y0u_4nd_5l33p_0r_y0u_4nd_c0ff33_3qu4l5_fl4g_4nd_p01n75}5. Conclusion
This challenge demonstrates that bitwise operations against a random key (a simplified One-Time Pad) are only secure if used exactly once. By allowing the attacker to query the oracle multiple times with the same plaintext and different keys, the randomness can be statistically filtered out to reveal the original message.
Flag: Hero{y0u_4nd_5l33p_0r_y0u_4nd_c0ff33_3qu4l5_fl4g_4nd_p01n75}