MultiCode picoCTF 2026 Solution

Published: March 20, 2026

Description

We intercepted a suspiciously encoded message, but it's clearly hiding a flag. No encryption, just multiple layers of obfuscation. Can you peel back the layers and reveal the truth? Download the message.txt .

Download message.txt and examine its contents.

Identify which encoding is the outermost layer.

bash
cat message.txt
  1. Step 1Download and inspect the message
    Download message.txt and look at the outermost encoding. The string uses multiple stacked encodings, with no encryption, just encoding transforms.
    bash
    cat message.txt
    Learn more

    Encoding transforms data into a different representation without using a secret key. It is reversible by anyone who knows the encoding scheme - no key is required. This is fundamentally different from encryption, which requires a key to reverse. Common encodings include base64, hex, URL encoding, and binary representation. Stacking multiple encodings adds layers that must be peeled in reverse order, but provides no additional secrecy.

    The key to identifying the outermost layer is recognising the character set used. Base64 uses A-Za-z0-9+/=. Base32 uses A-Z2-7=. Hex uses 0-9a-fA-F with even length. Binary uses only 0 and 1 in groups of 8. Morse code uses ., -, spaces, and / as word separators. URL encoding uses %XX sequences. Recognising these patterns at a glance is a core CTF skill.

  2. Step 2Identify encoding layers and peel them
    Each layer is one of: binary (groups of 8 bits), octal (3-digit groups 0-7), decimal ASCII (space-separated numbers 0-127), hex (even-length hex string), URL encoding (% sequences), base32 (A-Z2-7=), base64 (A-Za-z0-9+/=), ROT13, Atbash, or Morse code (dots and dashes). Keep decoding until picoCTF{...} appears.
    bash
    base64 -d message.txt 2>/dev/null
    bash
    xxd -r -p message.txt 2>/dev/null
    python
    python3 -c "import base64; print(base64.b32decode(open('message.txt').read().strip()+'====').decode())"
    Learn more

    Each encoding has a reliable detection signature:

    • Base64: length divisible by 4 (with = padding), chars A-Za-z0-9+/=
    • Base32: uppercase letters and digits 2-7, = padding, length divisible by 8
    • Hex: even number of chars from 0-9a-fA-F
    • Binary: only 0 and 1, length divisible by 8
    • Octal: space-separated groups of 0-7 digits
    • Decimal ASCII: space-separated integers in range 32-126
    • URL encoding: contains % followed by two hex digits
    • Morse code: only ., -, spaces, and /
    • ROT13: looks like English text but shifted; applying ROT13 again reveals the original

    CyberChef(gchq.github.io/CyberChef/) is an invaluable tool for multi-layer encoding challenges. Its "Magic" operation automatically detects and applies decoding operations, and you can chain operations visually to peel layers one by one. For automated scripting, the Python script in the next step handles all these cases programmatically.

  3. Step 3Auto-peel all layers with a script
    Use this script to automatically detect and peel every encoding layer in sequence until the flag is revealed.
    python
    python3 - <<'EOF'
    import base64, re, urllib.parse
    
    MORSE = {'.-':'A','-...':'B','-.-.':'C','-..':'D','.':'E','..-.':'F',
      '--.':'G','....':'H','..':'I','.---':'J','-.-':'K','.-..':'L',
      '--':'M','-.':'N','---':'O','.--.':'P','--.-':'Q','.-.':'R',
      '...':'S','-':'T','..-':'U','...-':'V','.--':'W','-..-':'X',
      '-.--':'Y','--..':'Z','-----':'0','.----':'1','..---':'2',
      '...--':'3','....-':'4','.....':'5','-....':'6','--...':'7',
      '---..':'8','----.':'9'}
    
    def peel(s):
        # Encoding-precedence order (most-restrictive char set first):
        # binary -> morse -> octal -> decimal -> hex -> URL -> base32 -> base64 -> rot13
        # This order is irrecoverable from reading the script alone, so it's
        # documented here. Reorder and you'll mis-classify (e.g. binary as hex).
        s = s.strip()
        c = re.sub(r'[\s:0x\\]', '', s)
        # binary
        cb = s.replace(' ','').replace(',','')
        if re.fullmatch(r'[01]+', cb) and len(cb)%8==0:
            try: t=''.join(chr(int(cb[i:i+8],2)) for i in range(0,len(cb),8)); return 'binary',t
            except: pass
        # morse
        if re.fullmatch(r'[./- \t\n]+', s) and ('.' in s or '-' in s):
            try:
                words=[' '.join(MORSE.get(l,'?') for l in w.strip().split()) for w in s.split('/')]
                return 'morse',' '.join(words)
            except: pass
        # octal
        parts=re.split(r'[\s,]+', s)
        if len(parts)>3 and all(re.fullmatch(r'[0-7]{1,3}',p) for p in parts):
            try: return 'octal',''.join(chr(int(p,8)) for p in parts)
            except: pass
        # decimal ASCII
        try:
            nums=[int(p) for p in parts]
            if len(nums)>3 and all(32<=n<=126 for n in nums):
                return 'decimal',''.join(chr(n) for n in nums)
        except: pass
        # hex
        if re.fullmatch(r'[0-9a-fA-F]+', c) and len(c)%2==0:
            try: return 'hex', bytes.fromhex(c).decode()
            except: pass
        # URL
        if '%' in s:
            t=urllib.parse.unquote(s)
            if t!=s: return 'url', t
        # base32: pad to multiple of 8 dynamically
        try:
            b32_padded = s.upper() + '=' * ((8 - len(s) % 8) % 8)
            t = base64.b32decode(b32_padded).decode()
            # isprintable() is too permissive (matches all-whitespace garbage);
            # require at least one alphanumeric to kill mis-padded false positives.
            if any(ch.isalnum() for ch in t): return 'base32', t
        except: pass
        # base64: pad to multiple of 4 dynamically
        try:
            b64_padded = s + '=' * ((4 - len(s) % 4) % 4)
            t = base64.b64decode(b64_padded).decode()
            if any(ch.isalnum() for ch in t): return 'base64', t
        except: pass
        # rot13
        t=s.translate(str.maketrans(
            'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz',
            'NOPQRSTUVWXYZABCDEFGHIJKLMnopqrstuvwxyzabcdefghijklm'))
        if 'pico' in t.lower(): return 'rot13', t
        return None, s
    
    data = open('message.txt').read().strip()
    for i in range(20):
        if m:=re.search(r'picoCTF[{][^}]+[}]', data): print('FLAG:', m.group()); break
        name, data = peel(data)
        if name: print(f'[{i+1}] {name}: {data[:80]}')
        else: print('No decoder matched'); break
    EOF
    Learn more

    The auto-peeling script implements a greedy decoder: at each step it tries all known encoding schemes in order and applies the first one that succeeds, then repeats on the result. The order matters - binary detection (only 0s and 1s) must come before hex (which is a superset of binary characters); Morse must come before decimal (both use only certain characters). The script terminates when the flag pattern picoCTF{...} is found or when no decoder matches.

    Building a robust multi-encoding detector requires handling edge cases: base64 padding (== or = appended), base32 requiring uppercase and padding to a multiple of 8, hex strings that are also valid base64, and Morse code that contains spaces (word separator) and slashes (letter separator). The script handles these by trying the most specific matchers first and falling back gracefully.

    This type of challenge teaches encoding literacy: the ability to recognise data representations at a glance. In real incident response and malware analysis, attackers commonly encode payloads in base64 (to bypass text-based filters), hex-encode shellcode (for embedding in source code), and use multiple encoding layers to evade signature-based detection. Being able to peel these layers quickly is an essential analyst skill. See the encodings guide for the full reference.

Alternate Solution

If you want to decode individual layers by hand, the site has tools for each encoding you will encounter: the Base64 Decoder, Morse Code Decoder, ROT / Caesar Cipher tool, Number Base Converter, and Binary → Hex Converter. Work layer by layer, pasting the output of each tool into the next, until the flag appears.

Flag

picoCTF{mult1c0d3_...}

Multiple stacked encoding layers (binary, octal, decimal ASCII, hex, URL, base32, base64, ROT13, Atbash, Morse). The script auto-detects and peels each layer until picoCTF{...} appears.

Want more picoCTF 2026 writeups?

Useful tools for General Skills

Related reading

What to try next