Password Cracking and Wordlist Engineering Part 5: The Rule Engine, best64, and Writing Your Own Mutations
Part 4 built wordlists. This post covers the layer that turns a static wordlist into a far larger, far smarter candidate set without writing a single new word to disk: hashcat’s rule engine. -r rules/best64.rule is the single highest-value flag in this entire series, and it’s worth understanding exactly what those 102 lines do rather than treating the file as a magic incantation.
What a rule actually is
A hashcat rule is a short sequence of single- or two-character function codes, applied in order to every word in the wordlist, that produce one mutated candidate per rule per word. Point -r best64.rule at a 14-million-line wordlist and the effective candidate count isn’t 14 million — it’s 14 million times however many of those 102 rules apply cleanly, which is most of them. That’s the actual mechanism behind Part 2’s throughput numbers mattering as much as they do: rules are how a modest wordlist turns into billions of realistic candidates.
Reading best64.rule directly off the installed hashcat release — not a summary of it — the function codes it actually uses:
| Code | Meaning | Example (on password) |
|---|---|---|
: | No-op — try the word unchanged | password |
r | Reverse the whole word | drowssap |
u | Uppercase everything | PASSWORD |
T0 | Toggle case of the character at position 0 | Password |
$X | Append character X | $1 $2 $3 → password123 |
^X | Prepend character X | ^1 → 1password |
] | Delete the last character (truncate) | password → passwor |
sXY | Substitute every X with Y | so0 → passw0rd |
DN | Delete the character at position N | D2 → pasword |
'N | Truncate to length N | '5 → passw |
That’s a small, composable instruction set, and best64.rule’s own section comments (kept verbatim in the file) show exactly what categories of human behavior it’s encoding, roughly in order of how often each pattern actually shows up in cracked-password datasets:
- “nothing, reverse, case… base stuff” —
:,r,u,T0: the word as typed, reversed, shouted, or with just the first letter capitalized. - “simple number append” and “special number append” —
$0through$9individually, then common pairs and triples ($1 $2,$6 $9,$1 $2 $3) — appending single digits and the specific short number sequences that show up disproportionately often in real breach data. - “high frequency append” —
$e,$s— the two single characters most likely to end a real human password. - “high frequency overwrite at end” — combinations of
](truncate) with append codes, encoding “take off the last character or two and replace it with something else” —] ] $e $rtruncates twice then appendser, catching plural/possessive variants. - “leetify” —
so0,si1,se3: the three most common single-character leetspeak substitutions, o→0, i→1, e→3. - “rotates” and an entire “unknown” section — longer, denser chains (
Z5 *75 '5 { O02) that were kept in the set because they empirically improve crack rate on real password corpora, even without an easily statable rationale for each one individually. That “unknown” label, left in the file’s own comments, is an honest admission:best64.ruleisn’t a hand-designed rule set, it’s a data-derived one, built by testing rule candidates against real cracked-password datasets and keeping whichever ones actually paid for their runtime cost.
What best64 doesn’t cover — and why that matters here
Running se3 (e→3) against scanner produces scann3r. But Part 3 of the Impacket series actually cracked Sc4nn3r2019! — which needs both e→3 and a→4, plus a capital first letter, plus a year, plus a trailing symbol. best64.rule has se3 but no sa4; a→4 didn’t make the cut for a general-purpose 64-rule set tuned across arbitrary breach data, because it’s a less universally common substitution than o→0, i→1, and e→3. Against a wordlist containing the bare word scanner, best64.rule alone will not produce Sc4nn3r2019! — which is exactly the case for a custom rule, not a bigger stock ruleset.
Writing a rule for this specific target
Chaining function codes on a single line applies them in sequence — visible directly in best64.rule’s own multi-function lines like ] ] $e $r (truncate twice, then append er). A custom rule file for the naming convention CONTOSO.LOCAL’s service accounts evidently follow — capitalize, leetspeak the vowels, append a year, append a symbol — is just more of the same:
# contoso-pattern.rule
c sa4 se3 $2 $0 $1 $9 $!
c sa4 se3 $2 $0 $2 $0 $!
c sa4 se3 $2 $0 $2 $1 $!
c capitalizes the first letter, sa4 and se3 apply the two leetspeak substitutions best64 doesn’t chain together for this pattern, and the $ sequence appends a four-digit year followed by ! — one rule line per plausible year, since hashcat rule syntax has no loop construct. Run against a wordlist containing scanner and backup, this rule file produces Sc4nn3r2019!-shaped candidates directly, and it’s a two-minute exercise to extend the year range or the trailing-symbol set once one crack confirms the pattern for a given organization. This is the actual payoff of Part 4’s cewl-scraped, organization-specific wordlist: a custom rule this narrow is worthless against a random target and extremely effective against the one it was built for.
When best64 isn’t enough: OneRuleToRuleThemAll
For a broader net than a hand-tuned 64-rule set, OneRuleToRuleThemAll — originally around 52,000 rules, compiled by researcher stealthsploit and published in NotSoSecure’s password_cracking_rules repository — takes the opposite approach: rather than 64 carefully chosen general-purpose rules, it’s the union of the best-performing rules across every major public rule set, tested against roughly 4.3 million real unsalted MD5 hashes from the 2016 Lifeboat Minecraft community breach, where it cracked 68.36% of that corpus. The tradeoff against best64.rule is exactly what the 800×-larger rule count implies: proportionally more candidates generated per wordlist word, proportionally more GPU time spent per attack — worth it for a thorough, no-stone-unturned pass once best64.rule and a targeted custom rule have both already run and come up short, not a sensible first move against a large wordlist and a fast time budget.
An honest note on verifying rule output here
hashcat’s --stdout mode expands a rule file against a wordlist and prints the resulting candidates without touching a hash at all — the normal way to sanity-check a rule file’s output before spending GPU time on it. It still initializes hashcat’s device backend even in stdout-only mode, and this sandbox has no OpenCL/CUDA runtime available (no GPU, no pocl CPU runtime, and no ability to install one without root) — the same constraint that shaped Part 2’s decision to cite a published benchmark rather than run one locally. The rule traces in this post were worked through by hand, function by function, against best64.rule’s actual bundled source rather than a summary of it, and cross-checked against the file’s own section comments — verified against the real rule definitions, even where a live --stdout run wasn’t possible in this environment.
Where this leaves the series
Rules turn a wordlist into a far larger, targeted candidate set through string mutation. Part 6 covers the other way to generate candidates — masks, which build passwords character-position by character-position from scratch, no wordlist required at all — and the keyspace math that determines when that approach is the rational choice instead of a last resort.