Skip to main content

PW Crack 5 Beginner picoMini 2022 Solution

Find the correct password by running a dictionary attack against a hashed credential. A Python and hash cracking challenge.

Published: April 2, 2026Updated: August 13, 2026

Description

No candidate list this time - a large dictionary file is provided. Read each word, hash it with MD5, and compare it to the stored hash to find the password.

Download level5.py (the checker script) and dictionary.txt (the wordlist).

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 script
    Observation
    The challenge ships a separate dictionary.txt rather than a hardcoded candidate list, which makes this a classic dictionary attack. Understanding level5.py's existing hash-comparison structure comes before extending it.
    Open level5.py. The stored MD5 hash is visible. The script needs to be extended to read each line from dictionary.txt, strip whitespace, hash it, and compare.
    Learn more

    This challenge is a full dictionary attack - the most common real-world technique for cracking hashed passwords. Instead of a curated short list, you now have a wordlist file that must be read line by line and each entry tested against the target hash.

    Wordlists (also called dictionaries) are files containing commonly used passwords, words, phrases, and variations. The most famous real-world wordlist is rockyou.txt, which contains over 14 million passwords leaked from the RockYou social gaming breach in 2009. Security professionals use it as a baseline for password auditing - if a password appears in rockyou.txt, it is considered trivially crackable.

    The key difference from pw-crack-4 is that you do not know the candidate list in advance - you must read it from disk. This is the standard approach for any serious password cracking: maintain a large wordlist file and stream through it programmatically.

  2. Step 2Modify the script to loop through the dictionary file
    Observation
    level5.py already has the MD5 comparison but no file-reading loop, and Python's readlines() leaves a trailing newline on every entry. So add a loop that strips each line before hashing it.
    Open level5.py and add code that reads dictionary.txt line by line. Each line includes a trailing newline character - remove it before hashing. Use line[:-1] (slice off the last character) or line.strip(). When the hash matches, set user_pw to that word and break.
    Learn more

    When Python reads a line from a file, each line includes the trailing newline character (\n). In ASCII, newline is character 10. Hashing a word with the newline attached produces a completely different MD5 than hashing the word alone - so every single comparison fails if you do not remove it first.

    Two equivalent ways to remove the trailing newline:

    • line[:-1] - slice notation that takes everything except the last character
    • line.strip() - removes all leading and trailing whitespace including newlines

    The loop to add inside level5.py looks like:

    with open('dictionary.txt') as f:
        lines = f.readlines()
    for pw in lines:
        pw = pw[:-1]
        if hash_pw(pw) == correct_pw_hash:
            user_pw = pw
            user_pw_hash = hash_pw(pw)
            break

    The time complexity of this attack is O(n) where n is the number of words in the dictionary. Professional cracking tools add GPU acceleration and multiple worker threads - hashcat on a modern GPU can test tens of billions of MD5 hashes per second. The fundamental algorithm is identical.

  3. Step 3Run with the correct password
    Observation
    With the dictionary loop in place, the script iterates through dictionary.txt and finds '9581' as the matching MD5 preimage. Running it feeds that password into the existing XOR decryption and prints the flag.
    Once '9581' is identified, use it with the XOR decryption function to print the flag.
    python
    python3 level5.py

    Expected output

    picoCTF{...}
    What didn't work first

    Tried: Running the unmodified level5.py before adding the dictionary-reading loop

    The original script never loops over dictionary.txt, so user_pw stays at its default empty value, the hash comparison never matches, and the script exits without a flag. Add the file-reading loop: iterate each line, strip the newline, hash it, and set user_pw when the hash matches.

    Tried: Using line.split() instead of line[:-1] or line.strip() to remove the trailing newline before hashing

    line.split() splits on all whitespace and returns a list, so pw becomes ['9581'] rather than '9581'. Passing a list to hash_pw() raises a TypeError, because hashlib.md5() wants a string or bytes. Use line[:-1] to slice off the last character, or line.strip(); both return a string with the newline gone.

    Learn more

    Completing this challenge demonstrates a complete understanding of the dictionary attack pipeline:

    • Identify the target hash and the hashing algorithm used
    • Select or obtain an appropriate wordlist
    • Stream the wordlist, hash each candidate, compare to the target
    • Use the recovered password to access the protected resource

    The real-world defense against dictionary attacks is salting: prepending or appending a unique random value (the salt) to each password before hashing. The salt is stored alongside the hash. Even if two users have the same password, their hashes are different due to different salts. Salting also defeats precomputed hash tables (rainbow tables) because each salt requires its own precomputed table.

    Modern password hashing algorithms like bcrypt and Argon2 automatically handle salting and are designed to be computationally expensive, making them far more resistant to dictionary attacks than raw MD5. If you ever design a system that stores user passwords, always use one of these purpose-built algorithms - never raw MD5 or SHA.

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{h45h_sl1ng1ng_...}

Each line read from a file carries a trailing newline - use line[:-1] or line.strip() to remove it before hashing, otherwise every comparison fails.

Key takeaway

Dictionary attacks succeed because people reuse predictable passwords, and any list of common ones can be hashed offline and compared against a stolen hash database in seconds. Raw MD5 and SHA hashes offer no resistance, since modern GPUs test billions of candidates per second. Salting each password with a unique random value defeats precomputed rainbow tables, and slow functions like bcrypt and Argon2 force an attacker to spend real time on every candidate, making large-scale cracking uneconomic.

Related reading

Useful tools for General Skills

Where to go next