Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
197 changes: 106 additions & 91 deletions docs/metasploit-framework.wiki/Hashes-and-Password-Cracking.md

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion documentation/modules/auxiliary/gather/kerberoast.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ $krb5tgs$23$*kerber.roastable$MSF.LOCAL$http/abc2.msf.local*$d335dc07b2c018de2a1
$krb5tgs$17$low.admin$MSF.LOCAL$*http/abc.msf.local*$a1c7c1c1e31e36cdb0721928$b69b48...
[!] NOTE: Multiple encryption types returned - will require separate cracking runs for each type.
[*] To obtain the crackable values for a praticular type, run `creds`:
[*] creds -t krb5tgs-rc4 -O 20.248.208.9 -o <outfile.(jtr|hcat)>
[*] creds -t krb5tgs -O 20.248.208.9 -o <outfile.(jtr|hcat)>
[*] creds -t krb5tgs-aes128 -O 20.248.208.9 -o <outfile.(jtr|hcat)>
[*] Auxiliary module execution completed
```
11 changes: 5 additions & 6 deletions lib/metasploit/framework/hashes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ module Framework
# http://pentestmonkey.net/cheat-sheet/john-the-ripper-hash-formats
# https://openwall.info/wiki/john/sample-hashes
# QNX formats -> https://moar.so/blog/qnx-password-hash-formats.html
# rubocop:disable Metrics/ModuleLength
module Hashes
JTR_NTLMV1 = 'netntlm'.freeze
JTR_NTLMV2 = 'netntlmv2'.freeze
Expand Down Expand Up @@ -67,9 +66,9 @@ def self.identify_hash(hash)
return 'nt'
when hash.length == 65 && hash =~ /^[\da-fA-F]{32}:[\da-fA-F]{32}$/
return 'lm'
when hash =~ /^[^\\\/:*?"<>|]{1,20}[:]{2,3}([^\\\/:*?"<>|]{1,20})?:[a-f0-9]{48}:[a-f0-9]{48}:[a-f0-9]{16}$/
when hash =~ %r{^[^\\/:*?"<>|]{1,20}:{2,3}([^\\/:*?"<>|]{1,20})?:[a-f0-9]{48}:[a-f0-9]{48}:[a-f0-9]{16}$}
return 'netntlm'
when hash =~ /^([^\\\/:*?"<>|]{1,20}\\)?[^\\\/:*?"<>|]{1,20}[:]{2,3}([^\\\/:*?"<>|]{1,20}:)?[^\\\/:*?"<>|]{1,20}:[a-f0-9]{32}:[a-f0-9]+$/
when hash =~ %r{^([^\\/:*?"<>|]{1,20}\\)?[^\\/:*?"<>|]{1,20}:{2,3}([^\\/:*?"<>|]{1,20}:)?[^\\/:*?"<>|]{1,20}:[a-f0-9]{32}:[a-f0-9]+$}
return 'netntlmv2'
# OSX
when hash.start_with?('$ml$') && hash.split('$').last.length == 256
Expand Down Expand Up @@ -124,18 +123,18 @@ def self.identify_hash(hash)
when hash =~ /^\*?[\da-fA-F]{32}\*[\da-fA-F]{32}$/
# we accept the beginning star as optional
return 'vnc'
when hash =~ /^\$pbkdf2-sha256\$[0-9]+\$[a-z0-9\/.]+\$[a-z0-9\/.]{43}$/i
when hash =~ %r{^\$pbkdf2-sha256\$[0-9]+\$[a-z0-9/.]+\$[a-z0-9/.]{43}$}i
return 'pbkdf2-sha256'
when hash =~ /^\$sntp-ms\$[\da-fA-F]{32}\$[\da-fA-F]{96}$/
return 'timeroast'
when hash =~ /^\$krb5tgs\$23\$\*.+\$[\da-fA-F]{32}\$[\da-fA-F]+$/
return 'krb5tgs-rc4'
return 'krb5tgs'
when hash =~ /^\$krb5tgs\$18\$.+\$[\da-fA-F]{24}\$[\da-fA-F]+$/
return 'krb5tgs-aes256'
when hash =~ /^\$krb5tgs\$17\$.+\$[\da-fA-F]{24}\$[\da-fA-F]+$/
return 'krb5tgs-aes128'
when hash =~ /^\$krb5asrep\$23\$[^:]+:[\da-fA-F]{32}\$[\da-fA-F]+$/
return 'krb5asrep-rc4'
return 'krb5asrep' # hashcat is krb5asrep-rc4, but we use john format
end
''
end
Expand Down
34 changes: 29 additions & 5 deletions lib/metasploit/framework/password_crackers/cracker.rb
Original file line number Diff line number Diff line change
Expand Up @@ -119,9 +119,9 @@ def initialize(attributes = {})
public_send("#{attribute}=", value)
end
end

def get_type
self.cracker
cracker
end

# This method takes a {framework.db.cred.private.jtr_format} (string), and
Expand Down Expand Up @@ -157,6 +157,16 @@ def jtr_format_to_hashcat_format(format)
'5500'
when 'netntlmv2'
'5600'
when 'krb5tgs'
'13100'
when 'krb5tgs-aes128'
'19600'
when 'krb5tgs-aes256'
'19700'
when 'krb5asrep'
'18200'
when 'timeroast'
'31300'
# dbs
when 'mssql'
'131'
Expand Down Expand Up @@ -226,6 +236,20 @@ def jtr_format_to_hashcat_format(format)
end
end

# Translates a jtr_format name to the format name john actually expects.
# Most formats pass through unchanged; a few have case/name discrepancies.
#
# @param format [String] A jtr_format string
# @return [String] The format name for John the Ripper
def jtr_format_to_john_format(format)
case format
when 'pbkdf2-sha256'
'PBKDF2-HMAC-SHA256'
else
format
end
end

# This method sets the appropriate parameters to run a cracker in incremental mode
def mode_incremental
self.increment_length = nil
Expand Down Expand Up @@ -413,7 +437,7 @@ def john_crack_command
end

if format.present?
cmd << ('--format=' + format)
cmd << ('--format=' + jtr_format_to_john_format(format))
end

if wordlist.present?
Expand Down Expand Up @@ -566,7 +590,7 @@ def show_command
if cracker == 'hashcat'
cmd = [cmd_string, '--show', '--username', "--potfile-path=#{pot_file}", "--hash-type=#{jtr_format_to_hashcat_format(format)}"]
elsif cracker == 'john'
cmd = [cmd_string, '--show', "--pot=#{pot_file}", "--format=#{format}"]
cmd = [cmd_string, '--show', "--pot=#{pot_file}", "--format=#{jtr_format_to_john_format(format)}"]

if config
cmd << "--config=#{config}"
Expand All @@ -576,7 +600,7 @@ def show_command
end
cmd << hash_path
end

def get_hashcat
# Look in the Environment PATH for the hashcat binary
self.cracker = 'hashcat'
Expand Down
22 changes: 9 additions & 13 deletions lib/metasploit/framework/password_crackers/hashcat/formatter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -51,13 +51,7 @@ def self.hash_to_hashcat(cred)
# https://hashcat.net/forum/thread-7854-post-42417.html#pid42417 ironically gives Token encoding exception
c = cred.private.data.sub('$pbkdf2-sha256', 'sha256').split('$')

# This method takes a string which is likely base64 encoded
# however, there is an arbitrary amount of = missing from the end
# so we attempt to add = until we are able to decode it
#
# @param str [String] the base64-ish string
# @return [String] the corrected string
def add_equals_to_base64(str)
add_equals_to_base64 = lambda do |str|
['', '=', '=='].each do |equals|
to_test = "#{str}#{equals}"
Base64.strict_decode64(to_test)
Expand All @@ -68,9 +62,9 @@ def add_equals_to_base64(str)
nil
end

c[2] = add_equals_to_base64(c[2].gsub('.', '+')) # pad back out
c[3] = add_equals_to_base64(c[3].gsub('.', '+')) # pad back out
return c.join(':')
c[2] = add_equals_to_base64.call(c[2].gsub('.', '+')) # pad back out
c[3] = add_equals_to_base64.call(c[3].gsub('.', '+')) # pad back out
return "#{cred.id}:#{c.join(':')}"
when /hmac-md5/
data = cred.private.data.split('#')
password = Rex::Text.encode_base64("#{cred.public.username} #{data[1]}")
Expand Down Expand Up @@ -131,10 +125,12 @@ def add_equals_to_base64(str)
# https://hashcat.net/forum/thread-8833.html
# while we can do the transformation, we'd have to throw extra flags at hashcat which aren't currently written into the lib for automation
nil
when /^krb5$/
return "#{cred.id}:#{cred.private.data}"
# when /^krb5$/

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we concerned about the exact krb5 string not being matched against any cred private type anymore? e.g. would we miss any matches and fall through? /^(krb5.|timeroast$)/ matches different values than /^krb5$/.

# return "#{cred.id}:#{cred.private.data}"
when /^(krb5.|timeroast$)/
return cred.private.data
# krb5tgs, krb5tgs-aes128, krb5tgs-aes256, krb5asrep, timeroast
## hash-mode: 13100 19600 19700 18200 31300
return "#{cred.id}:#{cred.private.data}"
end
end
nil
Expand Down
7 changes: 6 additions & 1 deletion lib/metasploit/framework/password_crackers/jtr/formatter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ def self.params_to_jtr(username, private_data, private_type, format: nil, db_id:
if format.start_with?('des') # 'des,oracle', not oracle11/12c
return "#{username}:O$#{username}##{private_data}:#{db_id}:"
end
when /^pbkdf2-sha256/
return "#{username}:#{private_data}:#{db_id}:"
when /md5|des|bsdi|crypt|bf|sha256|sha512|xsha512/
# md5(crypt), des(crypt), b(crypt), sha256(crypt), sha512(crypt), xsha512
return "#{username}:#{private_data}:::::#{db_id}:"
Expand Down Expand Up @@ -97,7 +99,10 @@ def self.params_to_jtr(username, private_data, private_type, format: nil, db_id:
# for this we overload the username field with the db_id so we can find it in the db easier later
# https://github.com/openwall/john/issues/5944
private_data = private_data.split('$')
private_data[5] = private_data[5].split(':').first
# remove port if there is one
if private_data[5].include?(':')
private_data[5] = private_data[5].split(':').first + '*'
end
private_data = private_data.join('$')
return "#{db_id}:#{private_data}"
when /^(krb5.|timeroast$)/
Expand Down
20 changes: 20 additions & 0 deletions modules/auxiliary/analyze/apply_pot.rb
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ def run
# 'android-sha1', 'android-samsung-sha1', 'android-md5', # mobile is done with hashcat, so skip these
'PBKDF2-HMAC-SHA1', 'phpass', 'mediawiki', 'pbkdf2-sha256', # webapps
'xsha', 'xsha512', 'PBKDF2-HMAC-SHA512', # osx
'krb5asrep', 'krb5tgs', 'krb5tgs-aes128', 'krb5tgs-aes256', 'timeroast', # kerberos
'nt', # nt needs to be 2nd to last because it can hit on android hashes
'crypt' # crypt NEEDS TO BE LAST so it doesn't accidentally read in other compatible hashes
].each do |format|
Expand Down Expand Up @@ -159,6 +160,25 @@ def run
break
end
end
when 'krb5asrep', 'krb5tgs', 'krb5tgs-aes128', 'krb5tgs-aes256'
# john outputs db_id:password since the hash file replaces username with db_id
next unless fields.count >= 2

core_id = fields.shift
lookup = lookups.find { |l| l.id.to_s == core_id.to_s }
next if lookup.nil?

username = lookup.username.username
when 'timeroast'
# john outputs $sntp-ms$...$...:password since hash file has the raw hash
next unless fields.count >= 2

hash_field = fields.shift
lookup = lookups.find { |l| l.jtr_hash == hash_field }
next if lookup.nil?

core_id = lookup.id
username = lookup.username.username
when 'lm', 'nt'
next unless fields.count >= 7

Expand Down
6 changes: 1 addition & 5 deletions modules/auxiliary/analyze/crack_mobile.rb
Original file line number Diff line number Diff line change
Expand Up @@ -86,11 +86,7 @@ def check_results(passwords, results, hash_type, method)
end

def run
tbl = Rex::Text::Table.new(
'Header' => 'Cracked Hashes',
'Indent' => 1,
'Columns' => ['DB ID', 'Hash Type', 'Username', 'Cracked Password', 'Method']
)
tbl = cracker_results_table

# array of hashes in jtr_format in the db, converted to an OR combined regex
hash_types_to_crack = []
Expand Down
11 changes: 9 additions & 2 deletions modules/auxiliary/analyze/crack_webapps.rb
Original file line number Diff line number Diff line change
Expand Up @@ -75,10 +75,17 @@ def check_results(passwords, results, hash_type, method)
next unless fields.count >= 2

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
next if cred['core_id'].include?("Hashfile '") && cred['core_id'].include?("' on line ") # skip error lines

case hash_type
when 'pbkdf2-sha256'
# hashcat format: cred_id:sha256:iterations:b64salt:b64hash:password
4.times { fields.shift } # skip sha256, iterations, b64salt, b64hash
else
cred['hash'] = fields.shift
end
cred['password'] = fields.join(':') # Anything left must be the password. This accounts for passwords with semi-colons in it

# we don't have the username since we overloaded it with the core_id (since its a better fit for us)
# so we can now just go grab the username from the DB
cred['username'] = framework.db.creds(workspace: myworkspace, id: cred['core_id'])[0].public.username
Expand Down
32 changes: 26 additions & 6 deletions modules/auxiliary/analyze/crack_windows.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ def initialize
MSCASH2 is format 2100 in hashcat.
NetNTLM is format 5500 in hashcat.
NetNTLMv2 is format 5600 in hashcat.
krb5tgs is format 13100 in hashcat.
krb5tgs-aes128 is format 19600 in hashcat.
krb5tgs-aes256 is format 19700 in hashcat.
krb5asrep is format 18200 in hashcat.
timeroast is format 31300 in hashcat.
),
'Author' => [
'theLightCosine',
Expand Down Expand Up @@ -50,6 +55,7 @@ def initialize
OptBool.new('MSCASH', [false, 'Crack M$ CASH hashes (1 and 2)', true]),
OptBool.new('NETNTLM', [false, 'Crack NetNTLM', true]),
OptBool.new('NETNTLMV2', [false, 'Crack NetNTLMv2', true]),
OptBool.new('KERBEROS', [false, 'Crack krb5/timeroast related hashes', true]),
OptBool.new('INCREMENTAL', [false, 'Run in incremental mode', true]),
OptBool.new('WORDLIST', [false, 'Run in wordlist mode', true]),
OptBool.new('NORMAL', [false, 'Run in normal mode (John the Ripper only)', true])
Expand Down Expand Up @@ -108,17 +114,23 @@ def check_results(passwords, results, hash_type, method)
cred = { 'hash_type' => hash_type, 'method' => method }
if @cracker_type == 'john'
# If we don't have an expected minimum number of fields, this is probably not a hash line
next unless fields.count > 2
next unless fields.count >= 2 # krb5asrep and similar kerberoast fields have 2 fields only

cred['username'] = fields.shift
cred['core_id'] = fields.pop
case hash_type
when 'krb5asrep', 'krb5tgs'
cred['core_id'] = fields.shift
cred['password'] = fields.pop
when 'mscash', 'mscash2', 'netntlm', 'netntlmv2'
cred['username'] = fields.shift
cred['core_id'] = fields.pop
cred['password'] = fields.shift
when 'lm', 'nt'
# If we don't have an expected minimum number of fields, this is probably not a NTLM hash
next unless fields.count >= 6

cred['username'] = fields.shift
cred['core_id'] = fields.pop

2.times { fields.pop } # Get rid of extra :
nt_hash = fields.pop
fields.pop
Expand All @@ -145,14 +157,17 @@ def check_results(passwords, results, hash_type, method)

cred['core_id'] = fields.shift

if ['netntlm', 'netntlmv2'].include? hash_type
case hash_type
when 'netntlm', 'netntlmv2'
# we could grab the username here, but no need since we grab it later based on core_id, which is safer
6.times { fields.shift } # Get rid of a bunch of extra fields
else
when 'krb5asrep'
2.times { fields.shift } # Get rid of extra hash fields
else # 'krb5tgs'
cred['hash'] = fields.shift
end

fields.pop if hash_type == 'mscash' # Get rid of username
fields.pop if ['mscash'].include? hash_type # Get rid of username

cred['password'] = fields.join(':') # Anything left must be the password. This accounts for passwords with semi-colons in it
next if cred['core_id'].include?("Hashfile '") && cred['core_id'].include?("' on line ") # skip error lines
Expand Down Expand Up @@ -182,6 +197,11 @@ def run
hash_types_to_crack << 'mscash2' if datastore['MSCASH']
hash_types_to_crack << 'netntlm' if datastore['NETNTLM']
hash_types_to_crack << 'netntlmv2' if datastore['NETNTLMV2']
hash_types_to_crack << 'krb5tgs' if datastore['KERBEROS']
hash_types_to_crack << 'krb5tgs-aes128' if datastore['KERBEROS']
hash_types_to_crack << 'krb5tgs-aes256' if datastore['KERBEROS']
hash_types_to_crack << 'krb5asrep' if datastore['KERBEROS']
hash_types_to_crack << 'timeroast' if datastore['KERBEROS']

jobs_to_do = []

Expand Down
Loading
Loading