interencdec picoCTF 2024 Solution

Published: April 3, 2024

Description

Can you get the real meaning from this file. Download the file here.

Local decode

Download the enc_flag file from the challenge artifacts.

Work locally; no remote service is needed once you have the blob.

bash
wget https://artifacts.picoctf.net/c_titan/3/enc_flag && \
cat enc_flag

Solution

Want to try it yourself first?

The guided walkthrough reveals hints one step at a time.

Walk me through it
The CTF Encodings guide walks through how to recognize Base64, ROT/Caesar, hex, and the other layered transforms this challenge stacks together.
  1. Step 1
    Read the initial Base64
    Observation
    I noticed the enc_flag file contained a long string of only alphanumeric characters, plus '+', '/', and '==' padding, which are the hallmarks of Base64 encoding and suggested running base64 -d as the first decoding step.
    cat enc_flag prints a long Base64 string ending in ==. Decode it once to reveal a Python byte literal (b'...').
    bash
    base64 -d enc_flag
    What didn't work first

    Tried: Running 'base64 enc_flag' (without -d) to decode the file

    Without -d, the base64 command encodes the file a second time rather than decoding it, producing a longer Base64 blob. The -d flag is required to switch from encode mode to decode mode; omitting it is a very common first-attempt mistake.

    Tried: Opening enc_flag in a hex editor to look for embedded data before trying Base64

    The file contains only printable ASCII characters arranged as a valid Base64 string - there is no binary header or embedded file signature to find with a hex editor. The correct first move is to recognize the Base64 fingerprint (A-Z a-z 0-9 + / with == padding) and decode it directly.

    Learn more

    Base64 is an encoding scheme, not encryption. It converts arbitrary binary data into a safe printable-ASCII string using 64 characters (A-Z, a-z, 0-9, +, /). Every 3 bytes of input become 4 Base64 characters, which is why Base64-encoded data is always about 33% larger than the original.

    The == at the end is padding. Base64 works in 3-byte groups; if the input isn't a multiple of 3 bytes, one or two = characters are appended as placeholders so the length is always a multiple of 4.

    Base64 is everywhere: email attachments (MIME), embedding images in CSS (data:image/png;base64,...), JWTs (the header and payload are Base64URL-encoded), and passing binary data through systems that only handle text. Seeing a string that ends in == or is unusually long and uses only alphanumeric characters is a strong hint to try Base64 decoding it.

  2. Step 2
    Strip quotes and decode again
    Observation
    I noticed the first decode produced a Python bytes literal (b'...') wrapping another long alphanumeric string, which indicated that the payload was Base64-encoded a second time and that the b'...' wrapper needed to be stripped before a second decoding pass.
    Remove the leading b' and trailing ' (cut -d "'" -f2 works on the typical bytecode print format), then Base64-decode the inner string to obtain a Caesar-shifted message.
    bash
    base64 -d enc_flag | cut -d "'" -f2 | base64 -d
    What didn't work first

    Tried: Piping the first base64 decode directly into base64 -d a second time without stripping the b'...' wrapper

    The second base64 -d call receives 'b\'<inner>\'' as input. The leading 'b\'' characters are not valid Base64 and cause an 'invalid input' error. The Python bytes-literal wrapper must be stripped first before the inner Base64 can be decoded.

    Tried: Using tr to strip the wrapper instead of cut, such as tr -d "b'"

    tr -d removes every occurrence of the listed characters throughout the entire string, so any 'b' or apostrophe inside the Base64 payload itself also gets deleted, corrupting the data before decoding. The cut -d "'" -f2 approach is safe because Base64's alphabet never contains a single-quote character.

    Learn more

    The b'...' wrapper is Python's syntax for a bytes literal. When Python prints a bytes object it adds this prefix so you can tell it apart from a regular string. It's not part of the data, just how Python represents it in text form.

    cut -d "'" -f2 splits on the single-quote character and takes the second field, which works cleanly when the inner Base64 contains no quotes (it never will, since the Base64 alphabet is A-Za-z0-9+/=) and there is no whitespace before the opening b'. If you ever see surrounding whitespace or a different quoting style (b"..."), reach for sed -E "s/^b['\\"]//; s/['\\"]$//" instead.

    The key insight: encoding is not encryption. No secret key is involved; anyone who recognizes the encoding can reverse it. Real encryption (AES, RSA) requires a key you don't have. Encoding is purely a format transformation.

  3. Step 3
    Apply ROT13 / Caesar
    Observation
    I noticed the second decode produced 'cvpbPGS{...}', which is the same length and structure as 'picoCTF{...}' but with each letter shifted by a fixed amount, suggesting a classical Caesar cipher that could be reversed by brute-forcing all 25 possible shifts.
    The decoded text starts with something like 'cvpbPGS{...}' - a Caesar-shifted picoCTF{...}. Use CyberChef's ROT13 (the easiest path) or pipe through bsdgames caesar, then scan the 25 candidate lines for the one starting with 'pico'.
    bash
    base64 -d enc_flag | cut -d "'" -f2 | base64 -d | caesar

    Expected output

    picoCTF{caesar_d3cr9pt3d_b20...}

    caesar from bsdgames applies English letter-frequency analysis and outputs a single best-guess decryption; if that guess is wrong you can pass an explicit shift number (e.g. caesar 19) to try a specific rotation. CyberChef's ROT13 recipe with "Brute force all rotations" checked is the friendliest option.

    What didn't work first

    Tried: Running 'rot13' or 'tr A-Za-z N-ZA-Mn-za-m' and expecting it to produce the flag immediately

    ROT13 is a fixed shift of 13 and only works if the Caesar shift happens to be 13. The actual shift in this challenge may differ, so ROT13 may produce nonsense. The correct approach is to brute-force all 25 possible shifts (via 'caesar' from bsdgames or CyberChef's 'Brute force' option) and look for the candidate that starts with picoCTF{.

    Tried: Trying to decrypt the shifted text with openssl or gpg, assuming the Caesar layer is actual encryption

    Caesar cipher is not encryption in any cryptographic sense - it has no key material, no IV, and no cipher algorithm for openssl or gpg to process. Those tools expect a known cipher mode and a key; feeding them a Caesar-shifted string produces an error or garbled output. The correct tool is a simple substitution brute-forcer or manual alphabet-shift.

    Learn more

    A Caesar cipher shifts each letter by a fixed number of positions in the alphabet. ROT13 is a Caesar cipher with a shift of 13; it's its own inverse (applying it twice gives you back the original), which made it popular for hiding spoilers in early internet forums.

    With only 25 possible shifts, the right one is whichever produces the recognizable picoCTF{ prefix. That single landmark, plus the known flag format, is the only signal you need; full English frequency analysis is overkill on a 40-character flag and is the standard tool for the Vigenère-style longer ciphertexts where you don't already know the plaintext skeleton.

    Historically, Julius Caesar reportedly used a shift of 3. The cipher was reasonably secure in an era when most people were illiterate, but provides zero real security today. It's the ancestor of the Vigenère cipher (a repeating-key Caesar), which itself was cracked in the 1800s using the Kasiski test.

Interactive tools
  • Base64 & Base32 DecoderDecode Base64 and Base32 strings with auto-detection. Multi-layer mode unwraps nested encodings automatically.
  • ROT / Caesar CipherDecode Caesar-shifted and ROT-encoded text. Drag the shift slider or scan all 26 rotations at once.
  • Cipher Identifier & Auto-DecoderPaste any ciphertext and the tool auto-runs every common decoder (base64, hex, Morse, ROT, Atbash, Bacon, binary, decimal, URL) and ranks the results by English-likeness.
No-terminal path (recommended)

If you don't want to fight quoting in the shell, the fastest solve is entirely in the browser. Open the Base64 Decoder, paste the contents of enc_flag, and decode. Strip the b' wrapper from the result, paste that back in, and decode again. Drop the final string into the ROT / Caesar Cipher tool and click Try all 26 shifts; the line starting with picoCTF{ is the flag.

Flag

Reveal flag

picoCTF{caesar_d3cr9pt3d_b20...}

Two Base64 layers plus a Caesar shift are all that stand between you and the flag.

Key takeaway

Encoding schemes like Base64 and Caesar ciphers provide no confidentiality because they require no secret key and use publicly known, reversible transformations. Layering encodings on top of each other (Base64 inside Base64 inside a Caesar shift) adds steps but not security, since each layer can be peeled off independently once recognized. Recognizing encoding fingerprints, such as the Base64 character set and padding, or the fixed-alphabet shift pattern, is a core CTF skill that transfers directly to real-world scenarios like decoding JWT payloads, obfuscated malware strings, and encoded configuration files.

Related reading

Want more picoCTF 2024 writeups?

Tools used in this challenge

Do these first

What to try next