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