unpackme.py

Published: July 20, 2023

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.

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.

Run the script locally to print the hidden password and flag.

wget https://artifacts.picoctf.net/c/48/unpackme.flag.py
sed -n '1,80p' unpackme.flag.py
python3 unpackme.flag.py

Solution

  1. Step 1Inspect the decoder
    The script reads a base64 blob, XORs it, and finally calls exec on the decrypted source. Printing `plain.decode()` reveals the cleartext code.
    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) with print(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. Tools like PyCDC, uncompyle6, and Python's built-in dis module help decompile and analyze Python payloads.

  2. Step 2Recover the credentials
    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.
    Learn more

    The XOR operation used in the decoding step is a symmetric operation: A XOR B XOR B = A. This makes XOR useful for simple obfuscation - XOR each byte of the payload with a key byte to "encrypt," then XOR again with the same key to decrypt. While trivially reversible if the key is embedded in the same script, it's enough to defeat simple string searches.

    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 exec with print is the manual equivalent.

    Python's compile() and dis.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.

Flag

picoCTF{175_chr157m45_5274...}

Always inspect self-modifying scripts before running them; a simple print statement exposes the payload safely.

Want more picoCTF 2022 writeups?

Useful tools for Reverse Engineering

Related reading

Do these first

What to try next