Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
rapid7
GitHub Repository: rapid7/metasploit-framework
Path: blob/master/modules/auxiliary/analyze/crack_windows.rb
70746 views
1
##
2
# This module requires Metasploit: https://metasploit.com/download
3
# Current source: https://github.com/rapid7/metasploit-framework
4
##
5
6
class MetasploitModule < Msf::Auxiliary
7
include Msf::Auxiliary::PasswordCracker
8
include Msf::Exploit::Deprecated
9
moved_from 'auxiliary/analyze/jtr_windows_fast'
10
11
def initialize
12
super(
13
'Name' => 'Password Cracker: Windows',
14
'Description' => %(
15
This module uses John the Ripper or Hashcat to identify weak passwords that have been
16
acquired from Windows systems.
17
LANMAN is format 3000 in hashcat.
18
NTLM is format 1000 in hashcat.
19
MSCASH is format 1100 in hashcat.
20
MSCASH2 is format 2100 in hashcat.
21
NetNTLM is format 5500 in hashcat.
22
NetNTLMv2 is format 5600 in hashcat.
23
krb5tgs is format 13100 in hashcat.
24
krb5tgs-aes128 is format 19600 in hashcat.
25
krb5tgs-aes256 is format 19700 in hashcat.
26
krb5asrep is format 18200 in hashcat.
27
timeroast is format 31300 in hashcat.
28
),
29
'Author' => [
30
'theLightCosine',
31
'hdm',
32
'h00die' # hashcat integration
33
],
34
'References' => [
35
[ 'ATT&CK', Mitre::Attack::Technique::T1110_002_PASSWORD_CRACKING ]
36
],
37
'License' => MSF_LICENSE, # JtR itself is GPLv2, but this wrapper is MSF (BSD)
38
'Actions' => [
39
['john', { 'Description' => 'Use John the Ripper' }],
40
['hashcat', { 'Description' => 'Use Hashcat' }],
41
['auto', { 'Description' => 'Auto-selection of cracker' }]
42
],
43
'DefaultAction' => 'auto',
44
'Notes' => {
45
'Stability' => [CRASH_SAFE],
46
'SideEffects' => [],
47
'Reliability' => []
48
}
49
)
50
51
register_options(
52
[
53
OptBool.new('NTLM', [false, 'Crack NTLM hashes', true]),
54
OptBool.new('LANMAN', [false, 'Crack LANMAN hashes', true]),
55
OptBool.new('MSCASH', [false, 'Crack M$ CASH hashes (1 and 2)', true]),
56
OptBool.new('NETNTLM', [false, 'Crack NetNTLM', true]),
57
OptBool.new('NETNTLMV2', [false, 'Crack NetNTLMv2', true]),
58
OptBool.new('KERBEROS', [false, 'Crack krb5/timeroast related hashes', true]),
59
OptBool.new('INCREMENTAL', [false, 'Run in incremental mode', true]),
60
OptBool.new('WORDLIST', [false, 'Run in wordlist mode', true]),
61
OptBool.new('NORMAL', [false, 'Run in normal mode (John the Ripper only)', true])
62
]
63
)
64
end
65
66
def half_lm_regex
67
# ^\?{7} is ??????? which is JTR format, so password would be ???????D
68
# ^[notfound] is hashcat format, so password would be [notfound]D
69
/^[?{7}|\[notfound\]]/
70
end
71
72
def show_command(cracker_instance)
73
return unless datastore['ShowCommand']
74
75
if @cracker_type == 'john'
76
cmd = cracker_instance.john_crack_command
77
elsif @cracker_type == 'hashcat'
78
cmd = cracker_instance.hashcat_crack_command
79
end
80
print_status(" Cracking Command: #{cmd.join(' ')}")
81
end
82
83
# we have to overload the process_cracker_results from password_cracker.rb since LANMAN
84
# is a special case where we may need to do some combining
85
def process_cracker_results(results, cred)
86
return results if cred['core_id'].nil? # make sure we have good data
87
88
# make sure we dont add the same one again
89
if results.select { |r| r.first == cred['core_id'] }.empty?
90
results << [cred['core_id'], cred['hash_type'], cred['username'], cred['password'], cred['method']]
91
end
92
93
# however, a special case for LANMAN where it may come back as ???????D (jtr) or [notfound]D (hashcat)
94
# we want to overwrite the one that was there *if* we have something better.
95
results.map! do |r|
96
if r.first == cred['core_id'] &&
97
r[3] =~ half_lm_regex
98
[cred['core_id'], cred['hash_type'], cred['username'], cred['password'], cred['method']]
99
else
100
r
101
end
102
end
103
104
create_cracked_credential(username: cred['username'], password: cred['password'], core_id: cred['core_id'])
105
results
106
end
107
108
def check_results(passwords, results, hash_type, method)
109
passwords.each do |password_line|
110
password_line.chomp!
111
next if password_line.blank?
112
113
fields = password_line.split(':')
114
cred = { 'hash_type' => hash_type, 'method' => method }
115
if @cracker_type == 'john'
116
# If we don't have an expected minimum number of fields, this is probably not a hash line
117
next unless fields.count >= 2 # krb5asrep and similar kerberoast fields have 2 fields only
118
119
case hash_type
120
when 'krb5asrep', 'krb5tgs'
121
cred['core_id'] = fields.shift
122
cred['password'] = fields.pop
123
when 'mscash', 'mscash2', 'netntlm', 'netntlmv2'
124
cred['username'] = fields.shift
125
cred['core_id'] = fields.pop
126
cred['password'] = fields.shift
127
when 'lm', 'nt'
128
# If we don't have an expected minimum number of fields, this is probably not a NTLM hash
129
next unless fields.count >= 6
130
131
cred['username'] = fields.shift
132
cred['core_id'] = fields.pop
133
134
2.times { fields.pop } # Get rid of extra :
135
nt_hash = fields.pop
136
fields.pop
137
fields.pop
138
password = fields.join(':') # Anything left must be the password. This accounts for passwords with semi-colons in it
139
if hash_type == 'lm' && password.blank?
140
if nt_hash == Metasploit::Credential::NTLMHash::BLANK_NT_HASH
141
password = ''
142
else
143
next
144
end
145
end
146
147
# password can be nil if the hash is broken (i.e., the NT and
148
# LM sides don't actually match) or if john was only able to
149
# crack one half of the LM hash. In the latter case, we'll
150
# have a line like:
151
# username:???????WORD:...:...:::
152
cred['password'] = john_lm_upper_to_ntlm(password, nt_hash)
153
end
154
next if cred['password'].nil?
155
elsif @cracker_type == 'hashcat'
156
next unless fields.count >= 2
157
158
cred['core_id'] = fields.shift
159
160
case hash_type
161
when 'netntlm', 'netntlmv2'
162
# we could grab the username here, but no need since we grab it later based on core_id, which is safer
163
6.times { fields.shift } # Get rid of a bunch of extra fields
164
when 'krb5asrep'
165
2.times { fields.shift } # Get rid of extra hash fields
166
else # 'krb5tgs'
167
cred['hash'] = fields.shift
168
end
169
170
fields.pop if ['mscash'].include? hash_type # Get rid of username
171
172
cred['password'] = fields.join(':') # Anything left must be the password. This accounts for passwords with semi-colons in it
173
next if cred['core_id'].include?("Hashfile '") && cred['core_id'].include?("' on line ") # skip error lines
174
175
# we don't have the username since we overloaded it with the core_id (since its a better fit for us)
176
# so we can now just go grab the username from the DB
177
cred['username'] = framework.db.creds(workspace: myworkspace, id: cred['core_id'])[0].public.username
178
end
179
results = process_cracker_results(results, cred)
180
end
181
results
182
end
183
184
def run
185
tbl = cracker_results_table
186
cracker = new_password_cracker(action.name)
187
if action.name == 'auto'
188
@cracker_type = cracker.get_type
189
else
190
@cracker_type = action.name
191
end
192
# array of hashes in jtr_format in the db, converted to an OR combined regex
193
hash_types_to_crack = []
194
hash_types_to_crack << 'lm' if datastore['LANMAN']
195
hash_types_to_crack << 'nt' if datastore['NTLM']
196
hash_types_to_crack << 'mscash' if datastore['MSCASH']
197
hash_types_to_crack << 'mscash2' if datastore['MSCASH']
198
hash_types_to_crack << 'netntlm' if datastore['NETNTLM']
199
hash_types_to_crack << 'netntlmv2' if datastore['NETNTLMV2']
200
hash_types_to_crack << 'krb5tgs' if datastore['KERBEROS']
201
hash_types_to_crack << 'krb5tgs-aes128' if datastore['KERBEROS']
202
hash_types_to_crack << 'krb5tgs-aes256' if datastore['KERBEROS']
203
hash_types_to_crack << 'krb5asrep' if datastore['KERBEROS']
204
hash_types_to_crack << 'timeroast' if datastore['KERBEROS']
205
206
jobs_to_do = []
207
208
# build our job list
209
hash_types_to_crack.each do |hash_type|
210
job = hash_job(hash_type, @cracker_type)
211
if job.nil?
212
print_status("No #{hash_type} found to crack")
213
else
214
jobs_to_do << job
215
end
216
end
217
218
# bail early of no jobs to do
219
if jobs_to_do.empty?
220
print_good("No uncracked password hashes found for: #{hash_types_to_crack.join(', ')}")
221
return
222
end
223
224
# array of arrays for cracked passwords.
225
# Inner array format: db_id, hash_type, username, password, method_of_crack
226
results = []
227
228
# generate our wordlist and close the file handle.
229
wordlist = wordlist_file
230
unless wordlist
231
print_error('This module cannot run without a database connected. Use db_connect to connect to a database.')
232
return
233
end
234
235
wordlist.close
236
print_status "Wordlist file written out to #{wordlist.path}"
237
238
cleanup_files = [wordlist.path]
239
240
jobs_to_do.each do |job|
241
format = job['type']
242
hash_file = Rex::Quickfile.new("hashes_#{job['type']}_")
243
hash_file.puts job['formatted_hashlist']
244
hash_file.close
245
cracker.hash_path = hash_file.path
246
cleanup_files << hash_file.path
247
# dupe our original cracker so we can safely change options between each run
248
cracker_instance = cracker.dup
249
cracker_instance.format = format
250
if @cracker_type == 'john'
251
cracker_instance.fork = datastore['FORK']
252
end
253
254
# first check if anything has already been cracked so we don't report it incorrectly
255
print_status "Checking #{format} hashes already cracked..."
256
results = check_results(cracker_instance.each_cracked_password, results, format, 'Already Cracked/POT')
257
vprint_good(append_results(tbl, results)) unless results.empty?
258
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
259
next if job['cred_ids_left_to_crack'].empty?
260
261
if @cracker_type == 'john'
262
print_status "Cracking #{format} hashes in single mode..."
263
cracker_instance.mode_single(wordlist.path)
264
show_command cracker_instance
265
cracker_instance.crack do |line|
266
vprint_status line.chomp
267
end
268
results = check_results(cracker_instance.each_cracked_password, results, format, 'Single')
269
vprint_good(append_results(tbl, results)) unless results.empty?
270
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
271
next if job['cred_ids_left_to_crack'].empty?
272
273
if datastore['NORMAL']
274
print_status "Cracking #{format} hashes in normal mode..."
275
cracker_instance.mode_normal
276
show_command cracker_instance
277
cracker_instance.crack do |line|
278
vprint_status line.chomp
279
end
280
results = check_results(cracker_instance.each_cracked_password, results, format, 'Normal')
281
vprint_good(append_results(tbl, results)) unless results.empty?
282
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
283
next if job['cred_ids_left_to_crack'].empty?
284
end
285
end
286
287
if datastore['INCREMENTAL']
288
print_status "Cracking #{format} hashes in incremental mode..."
289
cracker_instance.mode_incremental
290
show_command cracker_instance
291
cracker_instance.crack do |line|
292
vprint_status line.chomp
293
end
294
results = check_results(cracker_instance.each_cracked_password, results, format, 'Incremental')
295
vprint_good(append_results(tbl, results)) unless results.empty?
296
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
297
next if job['cred_ids_left_to_crack'].empty?
298
end
299
300
if datastore['WORDLIST']
301
print_status "Cracking #{format} hashes in wordlist mode..."
302
cracker_instance.mode_wordlist(wordlist.path)
303
# Turn on KoreLogic rules if the user asked for it
304
if @cracker_type == 'john' && datastore['KORELOGIC']
305
cracker_instance.rules = 'KoreLogicRules'
306
print_status 'Applying KoreLogic ruleset...'
307
end
308
show_command cracker_instance
309
cracker_instance.crack do |line|
310
vprint_status line.chomp
311
end
312
313
results = check_results(cracker_instance.each_cracked_password, results, format, 'Wordlist')
314
315
vprint_good(append_results(tbl, results)) unless results.empty?
316
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
317
next if job['cred_ids_left_to_crack'].empty?
318
end
319
320
# give a final print of results
321
print_good(append_results(tbl, results))
322
end
323
if datastore['DeleteTempFiles']
324
cleanup_files.each do |f|
325
File.delete(f)
326
end
327
end
328
end
329
end
330
331