Skip to content

Adds auto selection of cracker for password crackers #20418

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 30 commits into from
Aug 5, 2025
Merged
Show file tree
Hide file tree
Changes from 28 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
7450d72
Fix issue #20396
H4k1l Jul 26, 2025
56f138c
Fix issue #20396
H4k1l Jul 26, 2025
bd3ce5f
Fix issue #20396
H4k1l Jul 26, 2025
5aee8d5
Fix issue #20396
H4k1l Jul 26, 2025
d88c4bd
Fix issue #20396
H4k1l Jul 26, 2025
06c17a6
Update crack_webapps.rb
H4k1l Jul 26, 2025
d484191
Fix issue #20396
H4k1l Jul 26, 2025
ecfdec9
Fix issue #20396
H4k1l Jul 26, 2025
4b4e7cc
requested change resolved, PR #20418
H4k1l Jul 29, 2025
87d7dec
requested change resolved, PR #20418
H4k1l Jul 29, 2025
424e4fb
Update crack_databases.rb
H4k1l Jul 29, 2025
99ac369
requested change resolved, PR #20418
H4k1l Jul 29, 2025
c38cc44
Update crack_osx.rb
H4k1l Jul 29, 2025
9c03306
requested change resolved, PR #20418
H4k1l Jul 29, 2025
f454954
requested change resolved, PR #20418
H4k1l Jul 29, 2025
cf243b5
Adds auto option support, updates crack_database.rb accordingly
msutovsky-r7 Jul 29, 2025
d3f6faa
Adjust cracker modules
msutovsky-r7 Jul 29, 2025
d20494d
Removes puts
msutovsky-r7 Jul 30, 2025
96ba71b
Removes incorrect comment
msutovsky-r7 Jul 30, 2025
16a5fa2
Fixing typos
msutovsky-r7 Jul 30, 2025
6c9f8ef
Merge pull request #1 from msutovsky-r7/collab/feat/auto_cracker_sele…
H4k1l Jul 30, 2025
6ccc495
correcting a double assignment: tbl = tbl = cracker_results_table
H4k1l Jul 30, 2025
2a70b78
correcting a double assignment: tbl = tbl = cracker_results_table
H4k1l Jul 30, 2025
e44f54f
correcting a double assignment: tbl = tbl = cracker_results_table
H4k1l Jul 30, 2025
dc787b1
correcting a double assignment: tbl = tbl = cracker_results_table
H4k1l Jul 30, 2025
18b611f
correcting a double assignment: tbl = tbl = cracker_results_table
H4k1l Jul 30, 2025
1161954
correcting a double assignment: tbl = tbl = cracker_results_table
H4k1l Jul 30, 2025
3e47e4a
Fixed "]}" -> "}]"
H4k1l Aug 2, 2025
f691d81
prefer john over hashcat for more compatibility
H4k1l Aug 4, 2025
c4a2189
removed blank line as requested
H4k1l Aug 4, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 28 additions & 13 deletions lib/metasploit/framework/password_crackers/cracker.rb
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,10 @@ def initialize(attributes = {})
public_send("#{attribute}=", value)
end
end

def get_type
self.cracker
end

# This method takes a {framework.db.cred.private.jtr_format} (string), and
# returns the string number associated to the hashcat format
Expand Down Expand Up @@ -300,22 +304,19 @@ def binary_path
if cracker_path && ::File.file?(cracker_path)
return cracker_path
else
# Look in the Environment PATH for the john binary
if cracker == 'john'
path = Rex::FileUtils.find_full_path('john') ||
Rex::FileUtils.find_full_path('john.exe')
elsif cracker == 'hashcat'
path = Rex::FileUtils.find_full_path('hashcat') ||
Rex::FileUtils.find_full_path('hashcat.exe')
case cracker
when 'hashcat'
path = get_hashcat
when 'john'
path = get_john
when 'auto'
path = get_hashcat || get_john
else
raise PasswordCrackerNotFoundError, 'No suitable Cracker was selected, so a binary could not be found on the system'
end

if path && ::File.file?(path)
return path
raise PasswordCrackerNotFoundError, 'No suitable Cracker was selected, so a binary could not be found on the system JOHN || HASHCAT'
end
raise PasswordCrackerNotFoundError, 'No suitable john/hashcat binary was found on the system' unless path && ::File.file?(path)

raise PasswordCrackerNotFoundError, 'No suitable john/hashcat binary was found on the system'
return path
end
end

Expand Down Expand Up @@ -575,6 +576,20 @@ def show_command
end
cmd << hash_path
end

def get_hashcat
# Look in the Environment PATH for the hashcat binary
self.cracker = 'hashcat'
Rex::FileUtils.find_full_path('hashcat') ||
Rex::FileUtils.find_full_path('hashcat.exe')
end

def get_john
self.cracker = 'john'
# Look in the Environment PATH for the john binary
Rex::FileUtils.find_full_path('john') ||
Rex::FileUtils.find_full_path('john.exe')
end

end
end
Expand Down
1 change: 1 addition & 0 deletions lib/msf/core/auxiliary/password_cracker.rb
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ def new_password_cracker(cracking_application)
rescue Metasploit::Framework::PasswordCracker::PasswordCrackerNotFoundError => e
fail_with(Msf::Module::Failure::BadConfig, e.message)
end

# throw this to a local variable since it causes a shell out to pull the version
cracker_version = cracker.cracker_version
if cracker.cracker == 'john' && (cracker_version.nil? || !cracker_version.include?('jumbo'))
Expand Down
29 changes: 17 additions & 12 deletions modules/auxiliary/analyze/crack_aix.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,9 @@ def initialize
'Actions' => [
['john', { 'Description' => 'Use John the Ripper' }],
['hashcat', { 'Description' => 'Use Hashcat' }],
['auto', { 'Description' => 'Auto-selection of cracker' }]
],
'DefaultAction' => 'john',
'DefaultAction' => 'auto',
'Notes' => {
'Stability' => [CRASH_SAFE],
'SideEffects' => [],
Expand All @@ -45,9 +46,9 @@ def initialize
def show_command(cracker_instance)
return unless datastore['ShowCommand']

if action.name == 'john'
if @cracker_type == 'john'
cmd = cracker_instance.john_crack_command
elsif action.name == 'hashcat'
elsif @cracker_type == 'hashcat'
cmd = cracker_instance.hashcat_crack_command
end
print_status(" Cracking Command: #{cmd.join(' ')}")
Expand All @@ -63,12 +64,12 @@ def check_results(passwords, results, hash_type, method)
next unless fields.count >= 3

cred = { 'hash_type' => hash_type, 'method' => method }
if action.name == 'john'
if @cracker_type == 'john'
cred['username'] = fields.shift
cred['core_id'] = fields.pop
4.times { fields.pop } # Get rid of extra :
cred['password'] = fields.join(':') # Anything left must be the password. This accounts for passwords with semi-colons in it
elsif action.name == 'hashcat'
elsif @cracker_type == 'hashcat'
cred['core_id'] = fields.shift
cred['hash'] = fields.shift
cred['password'] = fields.join(':') # Anything left must be the password. This accounts for passwords with semi-colons in it
Expand All @@ -85,14 +86,20 @@ def check_results(passwords, results, hash_type, method)
end

def run
tbl = tbl = cracker_results_table
tbl = cracker_results_table
cracker = new_password_cracker(action.name)
if action.name == 'auto'
@cracker_type = cracker.get_type
else
@cracker_type = action.name
end

hash_types_to_crack = ['descrypt']
jobs_to_do = []

# build our job list
hash_types_to_crack.each do |hash_type|
job = hash_job(hash_type, action.name)
job = hash_job(hash_type, @cracker_type)
if job.nil?
print_status("No #{hash_type} found to crack")
else
Expand All @@ -110,8 +117,6 @@ def run
# Inner array format: db_id, hash_type, username, password, method_of_crack
results = []

cracker = new_password_cracker(action.name)

# generate our wordlist and close the file handle. max length of DES is 8
wordlist = wordlist_file(8)
unless wordlist
Expand All @@ -136,7 +141,7 @@ def run
cracker_instance = cracker.dup
cracker_instance.format = format

if action.name == 'john'
if @cracker_type == 'john'
cracker_instance.fork = datastore['FORK']
end

Expand All @@ -147,7 +152,7 @@ def run
job['cred_ids_left_to_crack'] = job['cred_ids_left_to_crack'] - results.map { |i| i[0].to_i } # remove cracked hashes from the hash list
next if job['cred_ids_left_to_crack'].empty?

if action.name == 'john'
if @cracker_type == 'john'
print_status "Cracking #{format} hashes in single mode..."
cracker_instance.mode_single(wordlist.path)
show_command cracker_instance
Expand Down Expand Up @@ -189,7 +194,7 @@ def run
print_status "Cracking #{format} hashes in wordlist mode..."
cracker_instance.mode_wordlist(wordlist.path)
# Turn on KoreLogic rules if the user asked for it
if action.name == 'john' && datastore['KORELOGIC']
if @cracker_type == 'john' && datastore['KORELOGIC']
cracker_instance.rules = 'KoreLogicRules'
print_status 'Applying KoreLogic ruleset...'
end
Expand Down
31 changes: 18 additions & 13 deletions modules/auxiliary/analyze/crack_databases.rb
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,9 @@ def initialize
'Actions' => [
['john', { 'Description' => 'Use John the Ripper' }],
['hashcat', { 'Description' => 'Use Hashcat' }],
['auto', { 'Description' => 'Auto-selection of cracker' }]
],
'DefaultAction' => 'john',
'DefaultAction' => 'auto',
'Notes' => {
'Stability' => [CRASH_SAFE],
'SideEffects' => [],
Expand All @@ -58,9 +59,9 @@ def initialize
def show_command(cracker_instance)
return unless datastore['ShowCommand']

if action.name == 'john'
if @cracker_type == 'john'
cmd = cracker_instance.john_crack_command
elsif action.name == 'hashcat'
elsif @cracker_type == 'hashcat'
cmd = cracker_instance.hashcat_crack_command
end
print_status(" Cracking Command: #{cmd.join(' ')}")
Expand All @@ -74,13 +75,13 @@ def check_results(passwords, results, hash_type, method)
fields = password_line.split(':')
cred = { 'hash_type' => hash_type, 'method' => method }

if action.name == 'john'
if @cracker_type == 'john'
next unless fields.count >= 3

cred['username'] = fields.shift
cred['core_id'] = fields.pop
cred['password'] = fields.join(':') # Anything left must be the password. This accounts for passwords with semi-colons in it
elsif action.name == 'hashcat'
elsif @cracker_type == 'hashcat'
next unless fields.count >= 2

cred['core_id'] = fields.shift
Expand Down Expand Up @@ -109,7 +110,13 @@ def check_results(passwords, results, hash_type, method)
end

def run
tbl = tbl = cracker_results_table
tbl = cracker_results_table
cracker = new_password_cracker(action.name)
if action.name == 'auto'
@cracker_type = cracker.get_type
else
@cracker_type = action.name
end

# array of hashes in jtr_format in the db, converted to an OR combined regex
hash_types_to_crack = []
Expand All @@ -128,7 +135,7 @@ def run

# hashcat requires a format we dont have all the data for
# in the current dumper, so this is disabled in module and lib
if action.name == 'john'
if @cracker_type == 'john'
hash_types_to_crack << 'oracle'
hash_types_to_crack << 'dynamic_1506'
end
Expand All @@ -143,7 +150,7 @@ def run

# build our job list
hash_types_to_crack.each do |hash_type|
job = hash_job(hash_type, action.name)
job = hash_job(hash_type, cracker.cracker)
if job.nil?
print_status("No #{hash_type} found to crack")
else
Expand All @@ -161,8 +168,6 @@ def run
# Inner array format: db_id, hash_type, username, password, method_of_crack
results = []

cracker = new_password_cracker(action.name)

# generate our wordlist and close the file handle.
wordlist = wordlist_file
unless wordlist
Expand All @@ -187,7 +192,7 @@ def run
cracker_instance = cracker.dup
cracker_instance.format = format

if action.name == 'john'
if @cracker_type == 'john'
cracker_instance.fork = datastore['FORK']
end

Expand All @@ -198,7 +203,7 @@ def run
job['cred_ids_left_to_crack'] = job['cred_ids_left_to_crack'] - results.map { |i| i[0].to_i } # remove cracked hashes from the hash list
next if job['cred_ids_left_to_crack'].empty?

if action.name == 'john'
if @cracker_type == 'john'
print_status "Cracking #{format} hashes in single mode..."
cracker_instance.mode_single(wordlist.path)
show_command cracker_instance
Expand Down Expand Up @@ -239,7 +244,7 @@ def run
print_status "Cracking #{format} hashes in wordlist mode..."
cracker_instance.mode_wordlist(wordlist.path)
# Turn on KoreLogic rules if the user asked for it
if action.name == 'john' && datastore['KORELOGIC']
if @cracker_type == 'john' && datastore['KORELOGIC']
cracker_instance.rules = 'KoreLogicRules'
print_status 'Applying KoreLogic ruleset...'
end
Expand Down
29 changes: 17 additions & 12 deletions modules/auxiliary/analyze/crack_linux.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,9 @@ def initialize
'Actions' => [
['john', { 'Description' => 'Use John the Ripper' }],
['hashcat', { 'Description' => 'Use Hashcat' }],
['auto', { 'Description' => 'Auto-selection of cracker' }]
],
'DefaultAction' => 'john',
'DefaultAction' => 'auto',
'Notes' => {
'Stability' => [CRASH_SAFE],
'SideEffects' => [],
Expand All @@ -58,9 +59,9 @@ def initialize
def show_command(cracker_instance)
return unless datastore['ShowCommand']

if action.name == 'john'
if @cracker_type == 'john'
cmd = cracker_instance.john_crack_command
elsif action.name == 'hashcat'
elsif @cracker_type == 'hashcat'
cmd = cracker_instance.hashcat_crack_command
end
print_status(" Cracking Command: #{cmd.join(' ')}")
Expand All @@ -74,14 +75,14 @@ def check_results(passwords, results, hash_type, method)
fields = password_line.split(':')
cred = { 'hash_type' => hash_type, 'method' => method }

if action.name == 'john'
if @cracker_type == 'john'
next unless fields.count >= 3 # If we don't have an expected minimum number of fields, this is probably not a hash line

cred['username'] = fields.shift
cred['core_id'] = fields.pop
4.times { fields.pop } # Get rid of extra :
cred['password'] = fields.join(':') # Anything left must be the password. This accounts for passwords with semi-colons in it
elsif action.name == 'hashcat'
elsif @cracker_type == 'hashcat'
next unless fields.count >= 2 # If we don't have an expected minimum number of fields, this is probably not a hash line

cred['core_id'] = fields.shift
Expand All @@ -100,7 +101,13 @@ def check_results(passwords, results, hash_type, method)
end

def run
tbl = tbl = cracker_results_table
tbl = cracker_results_table
cracker = new_password_cracker(action.name)
if action.name == 'auto'
@cracker_type = cracker.get_type
else
@cracker_type = action.name
end

# array of hashes in jtr_format in the db, converted to an OR combined regex
hash_types_to_crack = []
Expand All @@ -115,7 +122,7 @@ def run

# build our job list
hash_types_to_crack.each do |hash_type|
job = hash_job(hash_type, action.name)
job = hash_job(hash_type, @cracker_type)
if job.nil?
print_status("No #{hash_type} found to crack")
else
Expand All @@ -133,8 +140,6 @@ def run
# Inner array format: db_id, hash_type, username, password, method_of_crack
results = []

cracker = new_password_cracker(action.name)

# generate our wordlist and close the file handle.
wordlist = wordlist_file
unless wordlist
Expand All @@ -158,7 +163,7 @@ def run
cracker_instance = cracker.dup
cracker_instance.format = format

if action.name == 'john'
if @cracker_type == 'john'
cracker_instance.fork = datastore['FORK']
end

Expand All @@ -169,7 +174,7 @@ def run
job['cred_ids_left_to_crack'] = job['cred_ids_left_to_crack'] - results.map { |i| i[0].to_i } # remove cracked hashes from the hash list
next if job['cred_ids_left_to_crack'].empty?

if action.name == 'john'
if @cracker_type == 'john'
print_status "Cracking #{format} hashes in single mode..."
cracker_instance.mode_single(wordlist.path)
show_command cracker_instance
Expand Down Expand Up @@ -211,7 +216,7 @@ def run
print_status "Cracking #{format} hashes in wordlist mode..."
cracker_instance.mode_wordlist(wordlist.path)
# Turn on KoreLogic rules if the user asked for it
if action.name == 'john' && datastore['KORELOGIC']
if @cracker_type == 'john' && datastore['KORELOGIC']
cracker_instance.rules = 'KoreLogicRules'
print_status 'Applying KoreLogic ruleset...'
end
Expand Down
Loading