repetitions picoCTF 2023 Solution

Published: April 26, 2023

Description

A mysterious file named enc_flag hides nested encodings. Your task is to unwrap each layer until the plain-text flag appears.

Fetch enc_flag from a shell where you can chain decoding utilities.

Detect how deep the nesting goes: try CyberChef Magic, or loop base64 -d until picoCTF{ appears.

bash
wget https://artifacts.picoctf.net/c/451/enc_flag

Solution

Want to try it yourself first?

The guided walkthrough reveals hints one step at a time.

Walk me through it
The fastest browser-based approach is the Base64 & Base32 Decoder in Multi-layer Base64 mode, which unwraps every layer in one pass. For background on Base64 and the rest of the encoding zoo you will meet in CTFs, see the CTF encodings guide.
  1. Step 1
    Detect how many layers you have
    Observation
    I noticed the file is named enc_flag and its contents look like Base64 at a glance, but a single decode produces another Base64-looking blob, which suggested the same encoding was applied multiple times and a sentinel-driven loop was needed to find the exact depth.
    Loop base64 decoding and stop when the output contains picoCTF{. That confirms the depth without guessing.
    python
    python3 - <<'PY'
    import base64, pathlib
    data = pathlib.Path('enc_flag').read_bytes().strip()
    for i in range(1, 20):
        try:
            data = base64.b64decode(data)
        except Exception as e:
            print(f'layer {i}: decode failed ({e})')
            break
        if b'picoCTF{' in data:
            print(f'layer {i}: found flag -> {data.decode(errors="replace")}')
            break
        print(f'layer {i}: still encoded ({data[:32]!r}...)')
    PY

    Expected output

    layer 1: still encoded (b'VkZSSmVFNVhTWGRPUkVsNFRWUn...'...)
    layer 2: still encoded (b'VFZSSk1FNVVXVE5PUkVsNFRWUn...'...)
    layer 6: found flag -> picoCTF{base64_n3st...e523f49}
    What didn't work first

    Tried: Run 'base64 -d enc_flag' once and assume the result is plaintext.

    A single decode emits another Base64-looking blob, not the flag. The loop output shows at least six layers; stopping after one leaves five layers of encoding still in place. The sentinel-driven loop exists precisely to discover the real depth rather than assuming it.

    Tried: Pipe the file through 'strings enc_flag' to find the flag without decoding.

    Base64 characters are all printable ASCII, so 'strings' returns the raw encoded blob unchanged - it has no way to detect or reverse the encoding. The flag is not present as a literal string at any intermediate layer; it only appears after all layers are decoded.

    Learn more

    Nested Base64 encoding means the data has been Base64-encoded several times in a row. Each layer wraps the previous output, so to recover the original you decode exactly as many times as it was encoded. Use the self-terminating loop above to learn the exact count for your instance rather than assuming a fixed number.

    The loop above is more robust than counting layers by eye: it stops as soon as picoCTF{ appears and tells you how many decodes that took. If the output ever stops looking like Base64 (mixed case + digits + = padding) before the flag shows, the next layer is probably hex, gzip, or a different encoding instead.

  2. Step 2
    Apply the fixed-depth decode chain (once you know N)
    Observation
    I noticed the detection loop from step 1 reported a specific layer count N, which suggested building a flat pipeline of exactly N base64 decode stages to efficiently extract the flag in a single command.
    Once the loop tells you the depth N, a flat shell pipeline reads enc_flag once and pipes through N-1 more decodes. The example below shows six decodes; adjust the number of stages to the N your loop reported.
    bash
    base64 --decode enc_flag | base64 --decode | base64 --decode | base64 --decode | base64 --decode | base64 --decode

    Expected output

    picoCTF{base64_n3st...e523f49}
    What didn't work first

    Tried: Hard-code six decode stages without first running the detection loop, then get no output.

    The number of layers is not guaranteed to be six across all challenge instances; it can vary. If your instance uses a different depth, the pipeline either errors out (too many decodes produce invalid Base64) or outputs a partially decoded blob that still is not the flag. The detection loop in step 1 exists to find the exact count for your specific download.

    Tried: Use 'base64 -d' (BSD/macOS default) and see 'invalid input' errors mid-pipeline.

    BSD base64 is stricter about whitespace and newlines than GNU base64 and may reject intermediate decoded output that contains unexpected bytes. Switching to GNU base64 (available on Linux or via 'coreutils' on macOS with Homebrew) or using the Python loop from step 1 avoids this sensitivity entirely.

    Learn more

    The first base64 --decode enc_flag reads the file directly (no UUOC) and emits decoded bytes. Each subsequent | base64 --decode peels another layer. After N total decodes (six in this example) you land on plain ASCII containing the flag; if it has not appeared, add or remove a stage.

    Want a sanity-check sentinel inside the pipeline? Append | grep -o "picoCTF{[^}]*}". If the flag has not surfaced, grep prints nothing and you know the depth was off by at least one.

  3. Step 3
    Alternative: CyberChef Magic
    Observation
    I noticed CyberChef's Magic operation can fingerprint and auto-cascade decoders, which suggested it as a one-click browser-based alternative that avoids writing any script for straightforward multi-layer Base64 inputs.
    CyberChef's Magic operation auto-detects multi-layer Base64 and decodes until the output stops looking encoded.
    Learn more

    CyberChef ships a Magic operation that fingerprints the input and tries cascades of decoders until the output stops looking encoded. For pure multi-layer Base64 it nails this challenge in one click.

    Magic's limits matter though: it does not handle hex-or-compressed layers cleanly, so if a layer mid-stack is gzipped, XOR'd with a key, or reversed, Magic gives up. In those cases drop into the recipe panel and chain operations manually.

Interactive tools
  • Base64 & Base32 DecoderDecode Base64 and Base32 strings with auto-detection. Multi-layer mode unwraps nested encodings automatically.
  • 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.

Flag

Reveal flag

picoCTF{base64_n3st...e523f49}

Each Base64 decode peels a layer; run the self-terminating loop until picoCTF{ appears (the depth is fixed per instance) to reveal the flag.

Key takeaway

Encoding schemes like Base64 transform binary data into a printable character set for safe transport, but they provide zero confidentiality because the transformation is entirely public and reversible. Recognizing the character set and padding pattern of an encoded blob is enough to identify and reverse it with standard tools. Real-world obfuscation sometimes stacks multiple encoding layers to slow down casual inspection, but automated tools and sentinel-driven loops dissolve any fixed depth almost instantly.

Related reading

Want more picoCTF 2023 writeups?

Tools used in this challenge

What to try next