Description
The flag is only lightly concealed. Download hiddencipher.zip, identify the cipher and key, and peel the text back to plaintext.
Download and extract hiddencipher.zip.
Inspect the included files to identify the cipher and ciphertext.
unzip hiddencipher.zipls -lacat *Solution
Walk me through it- Step 1Identify the cipherBoth the cipher and the key are in the binary itself: ciphertext as a string constant, key as another string constant, transform as a tight loop in main(). Fastest path is strings first, r2 if strings is noisy, Ghidra last when you need the loop body decompiled.bash
strings binarybashr2 -A binarybashghidra binaryLearn more
Classical ciphers are simple substitution or transposition schemes that predate modern cryptography. The most common ones in CTF challenges are:
- Caesar cipher: shifts each letter by a fixed number (e.g., shift 13 = ROT13)
- Vigenère cipher: a polyalphabetic cipher using a repeating keyword as the key
- XOR cipher: bytes are XORed with a key byte or key sequence
- Substitution cipher: each letter is replaced by a fixed different letter
- Atbash: reverses the alphabet (A↔Z, B↔Y, etc.)
Recognising which cipher was used is the first step. Ciphertext that contains only letters and spaces suggests an alphabetic cipher (Caesar, Vigenère, substitution). Ciphertext containing arbitrary bytes or hex suggests XOR. The key insight for this challenge is that "hidden" means the cipher type and key are embedded in the provided files (in constants, strings, or the program logic) rather than being something you need to crack.
Distinguishing XOR from Vigenère in pseudocode is a one-glance test. XOR loops look like
out[i] = ct[i] ^ key[i % keylen]with a bitwise XOR (and frequently a& 0xFFmask if the operands are wider than a byte). Vigenère loops look likeout[i] = ((ct[i] - 'A') - (key[i % keylen] - 'A')) % 26 + 'A': subtraction of letter offsets, modulo 26, no XOR, and the operands are guaranteed to be alphabetic. If you see a^operator or a hex constant XOR, it is XOR; if you see%26or character arithmetic on'A', it is Vigenère or a Caesar variant. See the encodings guide for the broader pattern catalogue and Ghidra reverse engineering for tips on decompiling these loops. - Step 2Extract the keyLook at the data section in r2 or Ghidra: the key is stored next to the ciphertext, often with a label like 'key' or as the second long ASCII constant. The XOR/lookup loop in main() references both addresses.bash
r2 -A binary # then 'iz' to list strings, 'pdf @ main' to dump mainbashghidra binary # decompile main, follow the data refsLearn more
Static analysis extracts information from a binary without executing it.
stringsfinds printable ASCII sequences and is the fastest way to find hardcoded keys, passwords, or meaningful strings. Radare2 (r2) is a powerful open-source reverse engineering framework;-Aanalyses the binary automatically and identifies functions, cross-references, and strings. Ghidra (developed by the NSA) provides a GUI with decompilation - it translates assembly back into readable C-like pseudocode.Keys in CTF binaries are typically stored as: hardcoded string literals (found by
strings), integer constants in the assembly, arrays initialised at the start of a function, or values computed from program inputs. When you see an XOR loop in Ghidra output, look for the key value being loaded from a nearby variable or constant. For Vigenère, look for a string being used as a repeating index.In real malware analysis, extracting hardcoded encryption keys from binaries is a core skill. Malware families often XOR-encrypt their configuration (C2 server addresses, port numbers, campaign identifiers) with a static key embedded in the binary. Tools like FLOSS (FireEye Labs Obfuscated String Solver) automatically find and decode XOR-encoded strings in malware samples.
- Step 3Decrypt the ciphertextApply the identified cipher with the discovered key to decrypt the ciphertext.python
python3 << 'EOF' # Example XOR decrypt key = b"FOUND_KEY" ct = bytes.fromhex("CIPHERTEXT_HEX") pt = bytes(c ^ k for c, k in zip(ct, key * (len(ct)//len(key)+1))) print(pt.decode()) EOFLearn more
XOR encryption is symmetric: applying the same key twice returns the original plaintext (
P XOR K XOR K = P). This makes decryption identical to encryption - the same code that encrypts also decrypts. XOR with a repeated key is also called a Vigenère cipher in binary and has the same weaknesses: if any plaintext byte is known (like thepinpicoCTF), you can recover the corresponding key byte.The Python expression
bytes(c ^ k for c, k in zip(ct, key * N))decrypts by cycling the key over the ciphertext usingzip. The key is repeated withkey * Nto ensure it is at least as long as the ciphertext. This is a clean, Pythonic one-liner for any XOR cipher regardless of key length.For classical alphabetic ciphers, Python's
str.translate()andstr.maketrans()are the cleanest decryption tools: they define a character mapping table and apply it to the entire string in one call. Online tools like CyberChef("The Cyber Swiss Army Knife") support all common ciphers and encoding schemes and are excellent for rapid prototyping when writing custom code feels like overkill.
Alternate Solution
Once you have identified the key, use the XOR Cipher tool on this site to decrypt without writing any Python. Paste the hex ciphertext, enter the key, and the plaintext flag appears instantly. If the cipher turns out to be a Caesar/ROT variant, use the ROT / Caesar Cipher tool instead.
Flag
picoCTF{h1dd3n_c1ph3r_1_...}
Both the cipher type and the key are hidden in the program - static analysis reveals them.