Description
The provided Python script base64-decodes an embedded payload and execs it. Intercept the decoded string to review the logic and recover the flag/password without blindly executing unknown code.
Setup
Download unpackme.flag.py and open it in your editor.
Before the exec(plain.decode()) line, insert a print(plain.decode()) (or store the decoded string) to view what will execute.
Wrap exec in try/except so a malformed payload doesn't kill the process silently. If the decode path itself fails, fall back to manually constructing a Fernet object with the embedded key and calling decrypt() on the base64 payload.
Run the script locally to print the hidden password and flag.
wget https://artifacts.picoctf.net/c/48/unpackme.flag.pyless unpackme.flag.pypython3 unpackme.flag.pypython3 - <<'PY'
# Defensive variant: print the payload, fall back to manual decode on error.
import base64
src = open('unpackme.flag.py').read()
try:
g = {'__name__': '__main__'}
exec(src, g) # original loader runs print(); replace exec(plain) with print(plain) inside the file first
except Exception as e:
print('Loader failed:', e)
# Manual fallback: extract the base64 payload and key string from the source by eye, then:
# from cryptography.fernet import Fernet
# import base64
# key = base64.urlsafe_b64encode(b'correctstaplecorrectstaplecorrec')
# plain = Fernet(key).decrypt(B64_PAYLOAD)
# print(plain.decode())
PYSolution
Want to try it yourself first?
The guided walkthrough reveals hints one step at a time.
Step 1
Inspect the decoderObservationI noticed the script used Fernet decryption with a hardcoded key and then called exec() on the plaintext result, which suggested that swapping exec() for print() would expose the hidden payload without blindly running unknown code.The script imports Fernet from the cryptography library, uses the hardcoded key string correctstaplecorrectstaplecorrec (base64-encoded) to construct a Fernet object, decrypts the embedded payload, and finally calls exec on the decrypted source. Printingplain.decode()reveals the cleartext code.What didn't work first
Tried: Running the original script unmodified to see what it outputs.
The unmodified script executes the decrypted payload directly via exec(), which may prompt for a password or perform other actions without ever printing the flag. You need to intercept the payload before exec() runs - replace exec(plain.decode()) with print(plain.decode()) so the hidden source is displayed instead of silently executed.
Tried: Trying to base64-decode the embedded payload manually without using the Fernet key.
The payload is Fernet-encrypted, not just base64-encoded. A raw base64 decode produces binary ciphertext, not readable Python source. Fernet decryption requires both the key and the correct library call - you must use the hardcoded key already in the script along with Fernet().decrypt() to get the plaintext.
Learn more
Self-modifying or self-decoding scripts use a two-stage approach: the outer script contains an encoded payload and decryption logic; when run, it decodes the payload and executes it dynamically. Python's
exec()function runs a string as Python code, making this pattern possible in a single file.This technique is widely used in malware obfuscation: malicious scripts are encoded (Base64, XOR, zlib, etc.) to evade signature-based antivirus detection. The outer loader decodes and executes the real payload in memory. Multiple layers of encoding are common - each layer decodes the next. Security analysts "unpack" each layer by intercepting the decoded payload before execution, exactly as this challenge demonstrates.
The safe approach is to replace
exec(payload)withprint(payload)- this reveals what the script would execute without actually running it. In a sandboxed environment, you can run the original safely, but in real incident response, you always analyze before executing unknown code.When PyCDC or uncompyle6 actually matter. They are only needed if the challenge ships compiled bytecode (a
.pycfile or a frozen executable like PyInstaller output) without the matching source. This particular challenge ships plain Python source with a Fernet-encrypted payload inside, so neither tool is required: the "decompile" step is just printing the decrypted string. Save PyCDC/uncompyle6 for cases where you only have.pycbytecode, and use Python's built-indismodule if you want to disassemble code objects you produced withcompile().Step 2
Recover the credentialsObservationI noticed the decrypted payload printed a password prompt alongside plaintext output when exec() was replaced, which suggested the hidden source embedded both the password and the flag in its printed output.Executing the modified file prints a message containing both the password (batteryhorse) and flag. Revert to the original script if desired and supply the password to reproduce the flag output.What didn't work first
Tried: Looking for the flag directly in the original .py source file by searching for 'picoCTF'.
The flag is inside the encrypted Fernet payload, not visible in the outer script source. A text search or strings command on the file will only find the base64-encoded ciphertext, which looks like random characters. The flag only becomes readable after decryption.
Tried: Installing pycdc or uncompyle6 to decompile the script, expecting a .pyc bytecode file.
The challenge ships plain Python source (.py), not compiled bytecode (.pyc). Decompilers like pycdc and uncompyle6 operate on bytecode and have no effect here. The correct approach is to intercept the decrypted string at runtime by swapping exec() for print().
Learn more
Fernet is a symmetric authenticated encryption scheme built on AES-128-CBC with HMAC-SHA256. It is designed so that decryption fails loudly if the ciphertext has been tampered with. Because the key is hardcoded in the same script as the payload, it provides obfuscation rather than real security - anyone who reads the source can extract the key and decrypt immediately, exactly as this challenge demonstrates.
Dynamic analysis - running code in a controlled environment and observing behavior - often reveals secrets that static analysis misses. For obfuscated malware, analysts run samples in sandboxes (like Any.run, Cuckoo Sandbox, or CAPE Sandbox) and capture: network connections, file system changes, registry modifications, and spawned processes. Replacing
execwithprintis the manual equivalent.Python's
compile()anddis.dis()provide another approach: compile the string to a code object and disassemble it to bytecode, which is readable without execution. This is safer for analyzing potentially dangerous payloads and is the foundation of Python reverse engineering tools.
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{175_chr157m45_5274...}
Always inspect self-modifying scripts before running them; a simple print statement exposes the payload safely.