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
- Parse
oak.hashstyle input. Version1means the digest753a7277c956277fc6a3bb8e31822b25is a straight MD5. - Brute force every
SVIUSCG{<nature>_<pokemon>}over the 25 natures and 151 gen 1 Pokemon.solve.pycarries 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 - The match is
SVIUSCG{adamant_zubat}.
Flag: SVIUSCG{adamant_zubat}