RULES

mdxfind documentation

Rule Reference

mdxfind (via -r and -R) and procrule implement hashcat/JtR-compatible password mutation rules.

Rules are written one per line in a rule file. Each rule is a sequence of operations applied left to right. Multiple operations can be combined on a single line to create complex transformations.

Positions are encoded as 09 for positions 0–9, AZ for positions 10–35, and az for positions 36–61. This allows rules to address characters up to position 61 in very long passwords.

The Implicit No-Rule Pass

When -r (or -R) is active, mdxfind tests every word in the wordlist as-is before applying any rule. The unmodified word is always part of the candidate stream. This is intentional: the original input is part of the search space whether or not rules are supplied.

Concretely, with a wordlist of [apple, banana] and a rule file containing [$1, $2], mdxfind generates six candidates per pass:

apple        (no-rule pass)
apple1       ($1)
apple2       ($2)
banana       (no-rule pass)
banana1      ($1)
banana2      ($2)

Per word, the candidate count is 1 + N_rules. The leading 1 is the no-rule pass.

Comparison with hashcat

hashcat's rule engine omits the no-rule pass; only rule-mutated outputs are tested. To reproduce mdxfind's coverage with hashcat, prepend the no-op rule : to the rule file. mdxfind, by contrast, treats the no-rule pass as a property of the search loop itself rather than a rule that can be omitted.

A practical consequence: the no-op rule : is structurally redundant in mdxfind. When mdxfind reports rule counts, the : rule is filtered out of the working set:

$ mdxfind -r best64.rule -f hashes.txt wordlist.txt
103 rules read from best64.rule
77 total rules in use

The 26-rule difference between "read" and "in use" includes comments, blank lines, the : rule, and any rules that failed to parse.

Future optimization: rule no-op skip

Because the no-rule pass is guaranteed to fire, any rule whose output is bytewise identical to its input is redundant — its hash will already be computed from the unmodified word. mdxfind can in principle detect this case at rule-application time and skip the redundant hash. hashcat cannot perform this optimization because it has no guaranteed unmodified-input pass to defer to. This optimization is not yet enabled in the released CPU path; rules that no-op on a given input still incur a hash computation today.

Rules with Masks: Cross-Product

As of mdxfind rev 1.342, rules (-r, -R) and masks (-n, -N) compose as a full cross-product. Each rule output is combined with each mask value to form a candidate, and the candidate count per word is:

(1 + N_rules) × M_masks

The leading 1 is the implicit no-rule pass described above; it participates in the cross-product on equal footing with rule outputs. With 100 words, 5 rules, and the mask ?d (10 values), the candidate count is 100 × 6 × 10 = 6,000.

Worked example

mdxfind -m e1 -r rules.txt -n '?d?d' -f hashes.txt wordlist.txt

For each word in wordlist.txt:

  1. Apply the no-rule pass and each rule in rules.txt to produce 1 + N_rules ruled bases.
  2. For each ruled base, append every value of the mask ?d?d (100 values, 00 through 99).
  3. Hash each (ruled_base, mask_value) candidate as MD5 (-m e1) and compare against hashes.txt.

If rules.txt contains 5 rules, each word produces 6 × 100 = 600 candidates.

GPU and CPU paths

The cross-product semantics are identical on the GPU and CPU paths.

The GPU and CPU paths must produce the same hit set for any given input. Validation runs compare hit sets and counts between -G auto (GPU eligible) and -G none (CPU only) on identical input.

Performance note

A 5,000-rule MD5 run on a single NVIDIA RTX 4070 Ti SUPER establishes the rules-only GPU baseline at approximately 401 MH/s. Workloads combining rules with short masks dispatch through the same packed kernel and inherit this throughput as a starting point; absolute numbers vary with rule complexity, base-word length distribution, and mask cardinality. See BENCHMARK.md for tracked configurations.

Case Rules

Rule Description Example
l Lowercase all characters PaSsWoRdpassword
u Uppercase all characters passwordPASSWORD
c Capitalize first letter, lowercase rest passwordPassword
C Lowercase first letter, uppercase rest PasswordpASSWORD
t Toggle case of all characters PaSsWoRdpAsSwOrD
TN Toggle case at position N passwordpAssword (T1)
E Title case (capitalize after each space) hello worldHello World
eX Title case with custom separator X hello-worldHello-World (e-)
3NX Toggle case of first letter after the Nth occurrence of separator X foo.bar.bazfoo.Bar.baz (30.)

Insertion and Deletion

Rule Description Example
$X Append character X passpass1 ($1)
^X Prepend character X pass1pass (^1)
[ Delete first character passwordassword
] Delete last character passwordpasswor
DN Delete character at position N passwordpasword (D3)
iNX Insert character X at position N passwordpas-sword (i3-)
oNX Overwrite character at position N with X passwordpas@word (o3@)
'N Truncate word at length N passwordpass ('4)
xNM Extract M characters starting at position N passwordswor (x3 4)
ONM Delete M characters starting at position N passwordpasd (O3 4)

Duplication

Rule Description Example
d Duplicate entire word passpasspass
f Reflect — append reversed copy abcabccba
pN Append duplicated word N times abababab (p2)
q Duplicate every character abcaabbcc
zN Duplicate first character N times passpppass (z2)
ZN Duplicate last character N times passpassss (Z2)
yN Duplicate first N characters, prepend them passwordpapassword (y2)
YN Duplicate last N characters, append them passwordpasswordrd (Y2)

Rearrangement

Rule Description Example
r Reverse the word passworddrowssap
{ Rotate left — move first character to end passwordasswordp
} Rotate right — move last character to front passworddpasswor
k Swap first two characters passwordapssword
K Swap last two characters passwordpasswodr
*NM Swap characters at positions N and M passwordpsasword (*1 2)

Character Manipulation

Rule Description Example
sXY Replace all occurrences of X with Y passwordp@ssword (sa@)
@X Purge — remove all occurrences of X passwordpssword (@a)
+N Increment ASCII value at position N passwordqassword (+0)
-N Decrement ASCII value at position N passwordoassword (-0)
.N Replace character at N with character at N+1 passwordpaasword (.1)
,N Replace character at N with character at N-1 passwordppssword (,1)
LN Bit-shift left character at position N
RN Bit-shift right character at position N
vNX Insert character X every N characters passwordpa-ss-wo-rd (v2-)

Encoding

Rule Description Example
Ctrl-B (\x02) Base64 encode the word testdGVzdA==
h Hex-encode each byte (lowercase) test74657374
H Hex-encode each byte (uppercase) test74657374

Memory

Memory rules allow saving and recalling the word state, enabling complex multi-step transformations.

Rule Description
M Memorize current word state
4 Append memorized word
6 Prepend memorized word
Q Reject word if it equals the memorized word (use after M to ensure the rule changed something)
XNMI Insert M characters from memorized word at offset N, at position I in current word

Example: Mc$1Q — memorize original, capitalize, append "1", reject if unchanged. This ensures only words that were actually modified are emitted.

Rejection and Control

Rejection rules filter candidates based on conditions. If the condition is met, the candidate is rejected (not emitted).

Rule Description
<N Reject if word length is less than N
>N Reject if word length is greater than N
_N Reject unless original word length equals N
!X Reject if word contains character X
/X Reject if word does not contain character X
(X Reject if first character is not X
)X Reject if last character is not X

Combining Rules

Multiple operations on a single line are applied left to right. This allows powerful combinations:

Rule Effect Example with "password"
c$1 Capitalize + append 1 Password1
u$!$! Uppercase + append !! PASSWORD!!
sa@so0 a→@ and o→0 (leetspeak) p@ssw0rd
^!c Prepend ! + capitalize !Password
r$1 Reverse + append 1 drowssap1
d'8 Duplicate + truncate to 8 password (passpass → passp... truncated)
Mc$1Q Capitalize + append 1 (only if changed) Password1 (rejects if already capitalized with 1)

Using Rules with mdxfind

# Apply rules from a file (concatenated)
mdxfind -r rules.txt -f hashes.txt wordlist.txt

# Apply multiple rule files (concatenated — rules are additive)
mdxfind -r best64.rule -r toggles.rule -f hashes.txt wordlist.txt

# Apply multiple rule files (dot-product — every combination)
mdxfind -R transforms.rule -R suffixes.rule -f hashes.txt wordlist.txt

# Show which rules were most effective
mdxfind -r rules.txt -Z -f hashes.txt wordlist.txt

See EXAMPLES.md for detailed examples with sample output.

How mdxfind Processes Rules

Compilation

When mdxfind reads a rule file, it goes through three phases:

  1. Parse — each line is read and comments (lines starting with #) and blank lines are discarded.
  2. Validate — each rule is checked for correct syntax. Invalid rules are reported on stderr and discarded. This means you can safely use hashcat rule files that may contain rules mdxfind doesn't support — they'll be skipped with a warning.
  3. Compile — valid rules are converted into an internal bytecode format for fast application during the search loop. This avoids re-parsing the rule string for every candidate.
$ mdxfind -r best64.rule -f hashes.txt wordlist.txt
103 rules read from best64.rule
77 total rules in use

In this example, best64.rule has 103 lines. After removing comments, blank lines, and the no-op : rule (which reproduces the original word and is always tried implicitly), 77 rules remain.

SIMD Rule Batching

For certain algorithms — notably MD5 — mdxfind can apply multiple rules to the same input word and compute the hashes in parallel using SIMD instructions. On x86_64, the MD5 implementation processes 4 candidates simultaneously in 128-bit SSE registers. This means that applying 4 rules costs approximately the same as computing a single MD5 hash.

This dramatically reduces the cost of rules. Here is a real benchmark using 1 million MD5 hashes, 13.5 million candidate words, and the best64 rule set (77 effective rules):

$ mdxfind -f 1m.txt 10m.pass                          # no rules
13,484,773 lines processed
1.00 seconds hashing, 13,484,773 total hash calculations
13.45M hashes per second
16,898 MD5x01 hashes found

$ mdxfind -r best64.rule -f 1m.txt 10m.pass           # with 77 rules
103 rules read from best64.rule
77 total rules in use
13,484,773 lines processed
1,038,192,924 total rule-generated passwords tested
53.22 seconds hashing, 1,038,192,924 total hash calculations
19.51M hashes per second
77,093 MD5x01 hashes found

Analysis:

No rules With 77 rules
Candidates tested 13.5M 1,038M (77x)
Time 1.0s 53.2s
Throughput 13.5M/s 19.5M/s
Hashes found 16,898 77,093

Without SIMD batching, 77 rules would take 77 × 1.0s = 77 seconds. The actual time is 53 seconds — a 1.45x speedup from the 4-wide SIMD rule batching. The effective throughput of 19.5M/s (higher than the no-rule 13.5M/s) reflects the amortization of per-word overhead across multiple rule applications.

The 77,093 hashes found (vs 16,898 without rules) show the dramatic improvement in coverage: 4.6x more hashes solved by trying common password mutations.

Internal Rules vs External Pipeline

For comparison, the same candidates can be generated externally using procrule and piped into mdxfind. Since procrule suppresses rules that don't change the word, we also pass the original wordlist to ensure identical coverage:

$ procrule -r best64.rule 10m.pass | mdxfind -f 1m.txt stdin 10m.pass
948,460,875 lines processed
32.12 seconds hashing, 948,460,875 total hash calculations
29.53M hashes per second
77,093 MD5x01 hashes found
Approach Candidates Wall time CPU time Throughput Hashes found
Internal rules (-r) 1,038M 53s 1m32s 19.5M/s 77,093
External pipe (procrule) 948M 32s 4m56s 29.5M/s 77,093

Several things to note:

Both approaches find the same 77,093 hashes. Use internal rules (-r) when CPU efficiency matters or cores are limited; use procrule when you need the candidate list for other tools (hashcat, etc.) or when wall-clock time on an idle multi-core system is the priority.