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