Description
The password is encoded as chr() calls in the source. Decode it to run the script.
Setup
Download level2.py and level2.flag.txt.enc from the challenge page.
Solution
Want to try it yourself first?
The guided walkthrough reveals hints one step at a time.
Step 1
Find the encoded password in the sourceObservationI noticed the challenge description says the password is encoded as chr() calls, which indicated the source file contained character-code arithmetic instead of a plain string literal and made inspecting level2.py the obvious first step.Open level2.py and locate the password comparison. Instead of a plaintext string, the password is built from chr() calls: chr(0x34) + chr(0x65) + chr(0x63) + chr(0x39).Learn more
Code obfuscation is the practice of making source code harder to read and understand without changing what it does. Using
chr()calls instead of literal characters is a very basic form of obfuscation - the password is technically "hidden" from a casual glance, but anyone who knows Python can reconstruct it instantly by evaluating the expression.This technique is sometimes used by malware authors or by developers who mistakenly believe it provides security. It does not. Obfuscation only raises the bar slightly; it does not prevent a determined analyst from recovering the secret. The terms security through obscurity describe the (incorrect) belief that hiding implementation details is a substitute for real cryptographic security.
Reading hex values in source code is a skill worth developing. Common ASCII hex values you will encounter frequently:
0x41-0x5A- uppercase A through Z0x61-0x7A- lowercase a through z0x30-0x39- digits 0 through 90x7B/0x7D- curly braces{}(appear in every picoCTF flag!)
Step 2
Decode the chr() expressionObservationI noticed the password comparison in level2.py used the expression chr(0x34)+chr(0x65)+chr(0x63)+chr(0x39), which is valid Python syntax, suggesting I could evaluate it directly with python3 -c to instantly recover the plaintext string.Evaluate the expression in Python to recover the plaintext password.pythonpython3 -c "print(chr(0x34)+chr(0x65)+chr(0x63)+chr(0x39))"bash# Output: 4ec9Expected output
4ec9
What didn't work first
Tried: Manually look up each hex value in an ASCII table and piece together the characters by hand without running Python.
This works in principle but is error-prone for longer expressions and skips the faster approach. Running the exact expression with python3 -c lets Python do the arithmetic and chr() conversion in one step, eliminating transcription errors and scaling to any length expression.
Tried: Try treating the hex values as a raw byte string using bytes.fromhex() instead of evaluating chr() calls.
bytes.fromhex() expects a flat hex string like '34ec39', not individual 0x-prefixed integers joined by +. The chr() expression is a Python concatenation of character objects, not a hex blob - you must evaluate it as Python code, not decode it as a hex buffer.
Learn more
Python's
chr()function converts an integer to a Unicode character. Its inverse,ord(), converts a character back to its integer code point. Together they form the bridge between numeric representations (used in memory and encodings) and human-readable text.Using
python3 -cto evaluate arbitrary expressions is a quick and powerful technique for decoding obfuscated values during CTF challenges. You can paste the exact expression from the source code and wrap it inprint()to immediately see the decoded result - no file creation or script editing required.More advanced obfuscation techniques include base64 encoding, XOR encoding, and multi-layer encoding chains. Each layer adds complexity but can still be peeled back with the right tools or by running the decoding logic itself.
Step 3
Run the script with the decoded passwordObservationI noticed that evaluating the chr() expression produced the string '4ec9', which is the value the script expects at its password prompt, so entering it into level2.py would satisfy the comparison and print the flag.Execute level2.py and enter 4ec9 when prompted. The flag is printed.pythonpython3 level2.pybash# Enter password: 4ec9What didn't work first
Tried: Enter the raw hex bytes 0x34 0x65 0x63 0x39 or the string '0x34+0x65+0x63+0x39' as the password at the prompt.
The script compares the input against the evaluated Python expression chr(0x34)+chr(0x65)+chr(0x63)+chr(0x39), which resolves to the four ASCII characters '4ec9'. Entering the literal hex notation fails because the comparison is against a decoded string, not its numeric or source-code representation.
Tried: Try running the script without decoding first, guessing common passwords like 'password', 'admin', or '1234'.
The password is deterministic and fixed in the source as a specific chr() expression - brute-forcing by hand or with a short wordlist will miss it unless '4ec9' happens to be in the list. Reading the source and evaluating the expression directly is always faster than guessing when source code is available.
Learn more
This challenge illustrates an important point about static analysis - examining code without running it. By reading the source and mentally (or programmatically) evaluating the
chr()expression, you recovered the password without ever interacting with the script's password prompt. Static analysis is often faster and safer than running unknown code.In real security assessments, static analysis is used to audit code for vulnerabilities, recover hardcoded secrets, and understand malware behavior without executing potentially dangerous code. Tools like
strings,Ghidra, andIDA Proextend this capability to compiled binaries where the source code is not available.
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.
- Hash IdentifierIdentify unknown hash types by length and prefix. Covers MD5, SHA-1, SHA-256, SHA-512, bcrypt, NTLM, and more.
Flag
Reveal flag
picoCTF{tr45h_51ng1ng_...}
chr() obfuscation is a trivial encoding - evaluating the expression in Python immediately reveals the literal string, providing no real protection.