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