arrow_backUS Cyber Games 2026
Forensics / Pwnhard

OakHash

by Pavel Knespl (capyplivl)

A custom hash string $oak$1$<md5 hex> where the digest is just plain MD5. The password fits SVIUSCG{<nature>_<gen1_pokemon>}, a tiny keyspace to brute force.

Solve

  1. Parse oak.hash style input. Version 1 means the digest 753a7277c956277fc6a3bb8e31822b25 is a straight MD5.
  2. Brute force every SVIUSCG{<nature>_<pokemon>} over the 25 natures and 151 gen 1 Pokemon. solve.py carries both tables inline, so the whole keyspace runs in a fraction of a second:
    #!/usr/bin/env python3
    import hashlib
    
    NATURES = [
        "hardy", "lonely", "brave", "adamant", "naughty",
        "bold", "docile", "relaxed", "impish", "lax",
        "timid", "hasty", "serious", "jolly", "naive",
        "modest", "mild", "quiet", "bashful", "rash",
        "calm", "gentle", "sassy", "careful", "quirky",
    ]
    
    POKEMON = [
        "bulbasaur", "ivysaur", "venusaur", "charmander", "charmeleon",
        "charizard", "squirtle", "wartortle", "blastoise", "caterpie",
        "metapod", "butterfree", "weedle", "kakuna", "beedrill",
        "pidgey", "pidgeotto", "pidgeot", "rattata", "raticate",
        "spearow", "fearow", "ekans", "arbok", "pikachu",
        "raichu", "sandshrew", "sandslash", "nidoranf", "nidorina",
        "nidoqueen", "nidoranm", "nidorino", "nidoking", "clefairy",
        "clefable", "vulpix", "ninetales", "jigglypuff", "wigglytuff",
        "zubat", "golbat", "oddish", "gloom", "vileplume",
        "paras", "parasect", "venonat", "venomoth", "diglett",
        "dugtrio", "meowth", "persian", "psyduck", "golduck",
        "mankey", "primeape", "growlithe", "arcanine", "poliwag",
        "poliwhirl", "poliwrath", "abra", "kadabra", "alakazam",
        "machop", "machoke", "machamp", "bellsprout", "weepinbell",
        "victreebel", "tentacool", "tentacruel", "geodude", "graveler",
        "golem", "ponyta", "rapidash", "slowpoke", "slowbro",
        "magnemite", "magneton", "farfetchd", "doduo", "dodrio",
        "seel", "dewgong", "grimer", "muk", "shellder",
        "cloyster", "gastly", "haunter", "gengar", "onix",
        "drowzee", "hypno", "krabby", "kingler", "voltorb",
        "electrode", "exeggcute", "exeggutor", "cubone", "marowak",
        "hitmonlee", "hitmonchan", "lickitung", "koffing", "weezing",
        "rhyhorn", "rhydon", "chansey", "tangela", "kangaskhan",
        "horsea", "seadra", "goldeen", "seaking", "staryu",
        "starmie", "mrmime", "scyther", "jynx", "electabuzz",
        "magmar", "pinsir", "tauros", "magikarp", "gyarados",
        "lapras", "ditto", "eevee", "vaporeon", "jolteon",
        "flareon", "porygon", "omanyte", "omastar", "kabuto",
        "kabutops", "aerodactyl", "snorlax", "articuno", "zapdos",
        "moltres", "dratini", "dragonair", "dragonite", "mewtwo", "mew",
    ]
    
    HASH = "$oak$1$753a7277c956277fc6a3bb8e31822b25"
    
    def oakhash(password):
        return hashlib.md5(password.encode()).hexdigest()
    
    def build_flag(nature, pokemon):
        return f"SVIUSCG{{{nature}_{pokemon}}}"
    
    def candidates():
        for nature in NATURES:
            for pokemon in POKEMON:
                yield nature, pokemon
    
    def main():
        parts = HASH.split("$")
        assert parts[1] == "oak" and parts[2] == "1", "unexpected hash format"
        target = parts[3]
        total = len(NATURES) * len(POKEMON)
        print(f"[*] target:   {target}")
        print(f"[*] keyspace: {total} candidates")
        for i, (nature, pokemon) in enumerate(candidates()):
            flag = build_flag(nature, pokemon)
            if oakhash(flag) == target:
                print(f"\n[+] found: {flag}")
                return
            if i % 250 == 0:
                print(f"\r[*] {i}/{total} tested …", end="", flush=True)
        print("\n[-] not found")
    
    if __name__ == "__main__":
        main()
    
    python3 solve.py
    
  3. The match is SVIUSCG{adamant_zubat}.

Flag: SVIUSCG{adamant_zubat}