Description

From picoCTF:

Can you crack the password to get the flag? Download the password checker here and you'll need the encrypted flag and the hash in the same directory too. There are 7 potential passwords with 1 being correct. You can find these by examining the password checker script.

None
picoCTF — PW Crack 3

The Story Behind Solving the Challenge

When I first looked at this challenge, it appeared to be a simple password checker program. The challenge provided three files:

level3.py
level3.flag.txt.enc
level3.hash.bin

The goal was clear: find the correct password so the program reveals the flag. However, the password itself was not given directly. Instead, the script contained clues about how the password is validated.

The first step in any CTF challenge like this is reading the source code carefully.

Understanding the Password Checker

Opening level3.py, the program prompts the user to enter a password:

user_pw = input("Please enter correct password for flag: ")

The password is then passed to a hashing function:

def hash_pw(pw_str):
    pw_bytes = bytearray()
    pw_bytes.extend(pw_str.encode())
    m = hashlib.md5()
    m.update(pw_bytes)
    return m.digest()

This tells us something important:

The program does not store the password directly. Instead, it stores the MD5 hash of the password.

Next, the script reads a binary file:

correct_pw_hash = open('level3.hash.bin', 'rb').read()

Then it compares the hash of the user's input with the stored hash:

if user_pw_hash == correct_pw_hash:

If the hashes match, the program decrypts the encrypted flag using an XOR function and prints it.

So the logic becomes:

User Password
      ↓
MD5 Hash
      ↓
Compare With Stored Hash
      ↓
If Match → Decrypt Flag

Finding the Possible Passwords

Scrolling further down the script reveals a very useful clue:

pos_pw_list = ["8799", "d3ab", "1ea2", "acaf", "2295", "a9de", "6f3d"]

This means the correct password must be one of these seven values.

Instead of brute-forcing millions of passwords, we only need to check seven possibilities.

At this point, there are two ways to solve the challenge:

  1. Manual hash comparison
  2. Automated brute-force script

Method 1 — Manual Approach

First, we inspect the stored hash inside the binary file.

xxd level3.hash.bin

or

hexdump -C level3.hash.bin

This shows the raw MD5 digest stored in the file.

Next, we generate the MD5 hash for each candidate password using md5sum.

Important: use -n to avoid adding a newline.

Example:

echo -n "8799" | md5sum

We repeat this for all possible passwords:

echo -n "8799" | md5sum
echo -n "d3ab" | md5sum
echo -n "1ea2" | md5sum
echo -n "acaf" | md5sum
echo -n "2295" | md5sum
echo -n "a9de" | md5sum
echo -n "6f3d" | md5sum

By comparing the generated hashes with the value inside level3.hash.bin, we can identify which password matches.

Once the correct password is found, we run the program:

python3 level3.py

Then enter the discovered password to reveal the flag.

Method 2 — Automated Approach

Instead of checking each password manually, we can automate the process using a short Python script.

The script simply:

  1. Reads the correct hash from the file
  2. Computes MD5 hashes for all candidate passwords
  3. Compares them
import hashlib
with open("level3.hash.bin", "rb") as f:
    correct_hash = f.read()
passwords = ["8799", "d3ab", "1ea2", "acaf", "2295", "a9de", "6f3d"]
for pw in passwords:
    test_hash = hashlib.md5(pw.encode()).digest()
    if test_hash == correct_hash:
        print("Password found:", pw)

Running the script will immediately reveal the correct password.

Once we have the password, we simply run:

python3 level3.py

and provide the discovered password to decrypt the flag.

What This Challenge Teaches

This challenge highlights several important concepts:

  • Reading and understanding source code
  • Identifying how password verification works
  • Understanding MD5 hashing
  • Performing dictionary-based brute force attacks
  • Automating repetitive tasks using Python

Even though the challenge is simple, it reflects real-world scenarios where applications store password hashes rather than plain text passwords.

Conclusion

By analyzing the Python script, we discovered that the password validation relies on comparing MD5 hashes. Since the script conveniently provides a list of possible passwords, the task becomes a straightforward hash comparison problem.

Whether solved manually using command-line tools or automatically using a short script, the key idea is the same: find the password whose MD5 hash matches the stored hash.

Once the correct password is entered, the program decrypts the XOR-encrypted flag and completes the challenge.