CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.

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