Skip to main content

bloat.py picoCTF 2022 Solution

Reverse engineer an obfuscated Python script to uncover the secret passphrase it accepts.

Published: July 20, 2023Updated: August 25, 2026

Description

The provided Python script (bloat.flag.py) hides logic behind an array of printable characters before requesting a password to decrypt flag.txt.enc. Deobfuscate the script to recover the password, then run it to reveal the flag.

Download both bloat.flag.py and flag.txt.enc into the same working directory.

Read through the script to understand how the lookup table a[...] maps back to readable characters.

After uncovering the hard-coded password (happychance), run the script to decrypt the encrypted flag file.

bash
wget https://artifacts.picoctf.net/c/103/bloat.flag.py
bash
wget https://artifacts.picoctf.net/c/103/flag.txt.enc
python
python3 bloat.flag.py
python
python3 bloat.flag.py | tee output.txt
bash
sed -n '2p' output.txt

Solution

Want to try it yourself first?

The guided walkthrough reveals hints one step at a time.

Walk me through it
  1. Step 1Understand the lookup table
    Observation
    Every string in the script is assembled from indexed lookups into one character array rather than written as a literal. The whole obfuscation is array indexing, so define that array in a REPL and evaluate the index expressions yourself.
    Every string literal is built from indices into one character array a. Define a in a REPL and let Python evaluate the indexing for you.
    python
    python3 -c "
    # Paste the obfuscated array exactly as it appears in bloat.flag.py
    a = ['h','a','p','y','c','n','e','...']
    # Then walk one of the obfuscated index expressions, e.g.:
    print(''.join([a[i] for i in [0, 1, 2, 2, 3, 4, 0, 1, 5, 4, 6]]))
    "

    Expected output

    happychance
    What didn't work first

    Tried: Just run python3 bloat.flag.py immediately to see what the script does without reading it first.

    The script asks for a password immediately, and without it you get no further; guessing, or pressing Enter, gives a decryption failure or garbled output. Read the source, evaluate the index expressions statically to recover the password, then run the script with it.

    Tried: Use strings bloat.flag.py to search for the password as a plain text literal.

    strings extracts contiguous printable runs, but the password is never stored as a single literal: it is assembled at runtime by joining characters from the array through an index list. strings shows the raw array elements and stray Python keywords, never the assembled word. Evaluate the index expression in a REPL to rebuild it.

    Learn more

    The trick is "character-array string building": instead of "happychance" the code writes a[i0] + a[i1] + a[i2] + .... At runtime the interpreter joins the same string; statically it's just integers. Defining a in a REPL and running ''.join([a[i] for i in [...]]) on each obfuscated expression instantly reveals the plaintext.

    Don't run untrusted scripts on your host. Run inside a VM, container, or with python3 -c "..." capturing only the print output. Real malware uses the same character-array, base64, and eval-chain patterns you see here.

  2. Step 2Recover the password
    Observation
    One variable is built by joining individual characters from that array through an index list, then passed straight to the decryption routine. Walk those indices against the array and the plaintext password spells itself out, with no cryptanalysis needed.
    Walking the index expression for the credential variable spells happychance. That string is what flag.txt.enc was encrypted with.
    Learn more

    Specifically, the script's password variable is assigned something like password = a[i0] + a[i1] + ... . Walking the index list against your a array produces h-a-p-p-y-c-h-a-n-c-e. Because the array and the index sequence are both right there in the source, the obfuscation is purely cosmetic.

    Hardcoded credentials in source - even obfuscated ones - are a classic antipattern. Real systems store secrets outside source (environment variables, AWS Secrets Manager, HashiCorp Vault) or prompt the user at runtime and derive a key via PBKDF2/Argon2.

  3. Step 3Decrypt the flag
    Observation
    flag.txt.enc is the encrypted output, and the script holds the exact decryption routine keyed to the recovered password. Running the script and supplying that credential is the only route to a readable flag.
    Run the script with the recovered password, then verify the output starts with picoCTF{ before submitting.
    python
    python3 bloat.flag.py | tee output.txt
    bash
    # enter: happychance
    bash
    sed -n '2p' output.txt
    bash
    grep -oE 'picoCTF\{[^}]+\}' output.txt
    What didn't work first

    Tried: Try to decrypt flag.txt.enc directly with openssl enc -d or gpg --decrypt without running the Python script.

    The script uses its own XOR or symmetric cipher logic, not an OpenSSL suite or a GPG envelope. Point openssl at the encrypted file and it errors on a bad magic number or unrecognized header, because the format matches no standard container. Run the Python script, which holds the exact routine that produced the file.

    Tried: Press Enter (empty password) or type a guess like password when the script prompts, hoping to bypass the check.

    The script checks your input against the obfuscated credential before it calls the decryption function. A wrong password gives either an explicit failure or binary garbage, neither of which is a flag. Recover the credential by evaluating the index expression from the source.

    Learn more

    Decryption is success when the output is well-formed: starts with picoCTF{, ends with }, only printable ASCII inside. If the second line is binary garbage, the password is wrong or the script's decryption logic differs from what you reconstructed. The grep -oE regex is a robust alternative to sed -n '2p' when the output ordering shifts.

    tee writes stdin to both stdout and a file - useful when you want to see output live while also keeping a log. Combined with line-selection tools (sed, awk, grep), it beats copy-paste for any script that produces more than a couple of lines.

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.
  • Hex ViewerView text or raw hex bytes as a xxd-style hex dump with byte offset, hex columns, and ASCII sidebar. Highlights printable characters and null bytes.

Flag

Reveal flag

picoCTF{d30bfu5c4710n_f7w_b80...}

Never run opaque scripts blindly. Printing the decoded payload first keeps you safe and shows the password immediately.

Key takeaway

Obfuscation swaps readable identifiers and literals for encoded or indexed equivalents without changing what the program does at runtime. Anything the interpreter can reverse while running, a human can reverse statically by repeating those steps in a REPL. The same character-array and eval-chain patterns run through malware loaders, JavaScript droppers, and packed shellcode stubs, so undoing them mechanically is core to both CTF reversing and malware analysis.

Related reading

Useful tools for Reverse Engineering

Where to go next