reverse_cipher picoCTF 2019 Solution

Published: April 2, 2026

Description

We have a secret message and a binary that encrypts it. Reverse the cipher to decrypt enc.

Download both files: the rev binary and the enc ciphertext file.

bash
wget <url>/rev
bash
wget <url>/enc
bash
chmod +x rev

Solution

Want to try it yourself first?

The guided walkthrough reveals hints one step at a time.

Walk me through it
  1. Step 1
    Examine the binary
    Observation
    I noticed the challenge provided a compiled binary called rev alongside an enc ciphertext file, which suggested that understanding the encryption logic inside rev was the necessary first step before any decryption could be attempted.
    Run strings on the binary to find any hardcoded values. Then open it in Ghidra or Radare2 to decompile the encryption logic. The encryption function applies a simple transformation to each character.
    bash
    strings rev
    bash
    file rev
    bash
    ghidra rev &

    Expected output

    rev: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, not stripped
    What didn't work first

    Tried: Run xxd or hexdump on the enc file hoping to spot a pattern without opening the binary.

    xxd shows raw bytes but gives no hint about which indices are shifted and by how much. Without reading the decompiled logic, you cannot know the +5/-2 parity rule or the 8-byte untouched prefix. Ghidra is required to recover those exact constants.

    Tried: Use ltrace or strace on the rev binary to intercept its file I/O and infer the cipher from system call arguments.

    ltrace and strace show file opens and write sizes but not the arithmetic applied to each byte in user-space. The encryption loop never calls a library function - it is a plain indexed loop inside main - so no interceptable call reveals the +5/-2 transform. Static analysis in Ghidra is the correct path.

    Learn more

    Ghidra is a free reverse engineering tool from the NSA. It decompiles binaries to C-like pseudocode. Load the binary, let it auto-analyze, then navigate to the main function to see the encryption logic.

  2. Step 2
    Understand the encryption algorithm
    Observation
    I noticed that Ghidra's decompiled main function showed a loop that treated bytes differently depending on their index parity, which indicated a non-uniform transform that required careful mapping of the exact boundary (index 8) and the per-parity constants (+5 and -2) before writing the inverse.
    In Ghidra, main reads 24 (0x18) bytes from flag.txt into a buffer. The first 8 bytes are copied to the output unchanged. From index 8 onward the transform depends on position parity: bytes at an EVEN index are increased by 5 (byte + 5), bytes at an ODD index are decreased by 2 (byte - 2). The result is written to enc.
    Learn more

    This is not a uniform Caesar shift. The first 8 characters (the picoCTF{ prefix region) pass through untouched, and only from index 8 does the parity-based transform begin. Getting the index boundary and the per-parity operations right is the whole challenge.

  3. Step 3
    Write a Python decryption script
    Observation
    I noticed the decompiled logic revealed two simple arithmetic operations (+5 at even indices, -2 at odd indices) applied from index 8 onward, which meant each operation had a direct arithmetic inverse that could be scripted in a few lines of Python to recover the plaintext flag.
    Apply the inverse transform: keep indices 0-7 as is, for indices 8 through 22 (0x17 exclusive) subtract 5 at even indices and add 2 at odd indices, then append the closing brace at index 23 unchanged.
    python
    python3 - <<'EOF'
    data = open('enc', 'rb').read()
    out = bytearray(data[:8])          # first 8 bytes untouched
    for i in range(8, 0x17):           # indices 8-22; index 23 (closing }) is unchanged
        if i % 2 == 0:
            out.append((data[i] - 5) & 0xff)   # inverse of +5 at even index
        else:
            out.append((data[i] + 2) & 0xff)   # inverse of -2 at odd index
    out.append(data[0x17])             # closing } passed through by the binary
    print(out.decode())
    EOF
    What didn't work first

    Tried: Apply the same shift to all bytes uniformly: subtract 5 from every character after index 8 regardless of parity.

    This inverts only the even-index +5 half of the transform and leaves odd-index bytes two characters off. The output will contain garbage characters at every odd position from index 9 onward. The parity check (i % 2 == 0) must route even and odd indices to different operations - subtract 5 and add 2 respectively.

    Tried: Start the inversion loop at index 0 instead of index 8, applying the parity transform to the entire ciphertext.

    The binary copies the first 8 bytes (the picoCTF{ prefix) straight to enc without any modification. Inverting them shifts the p, i, c, o, C, T, F, { characters away from their correct ASCII values and produces a flag that does not start with picoCTF{. The loop must begin at index 8 to match where the binary's transform actually starts.

    Learn more

    Validate by running rev on a known test file and checking that your inverse takes its output back to your input. The recovered plaintext will be the flag in the form picoCTF{r3v3rs3...} where the trailing hex suffix differs per instance.

Interactive tools
  • Strings ExtractorPull printable text from any binary, library, or image. ASCII and UTF-16 detection, configurable minimum length, flag-like highlight, no command line needed.
Note

Because the transform is position-dependent (not a single fixed shift), a generic ROT Cipher tool will not recover it in one pass. The short Python inverse above is the reliable route.

Flag

Reveal flag

picoCTF{r3v3rs3...}

The trailing hex suffix after r3v3rs3 is generated per instance (confirmed instances include r3v3rs39ba4806b, r3v3rs369806a41, r3v3rs3d0051a07). The decryption logic is static: main reads 24 bytes from flag.txt; indices 0-7 (picoCTF{) pass through unchanged, indices 8-22 are shifted (+5 at even, -2 at odd), and index 23 (closing }) is written as-is. Invert that to recover your instance's flag.

Key takeaway

Custom ciphers in compiled binaries are reverse engineered by reading the disassembled logic and mathematically inverting each operation. Any arithmetic transformation on bytes (add, subtract, XOR, rotate) has a precise inverse, and decompilers like Ghidra expose the exact constants and index boundaries needed to write that inverse. The same workflow applies to license-check routines, firmware obfuscation, and malware that decrypts its own payload at runtime.

Related reading

Want more picoCTF 2019 writeups?

Tools used in this challenge

What to try next