CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!

GitHub Repository: rapid7/metasploit-framework
Path: blob/master/lib/metasploit/framework/password_crackers/cracker.rb
Views: 1904
1
module Metasploit
2
module Framework
3
module PasswordCracker
4
class PasswordCrackerNotFoundError < StandardError
5
end
6
7
class Cracker
8
include ActiveModel::Validations
9
10
# @!attribute attack
11
# @return [String] The attack mode for hashcat to use (not applicable to John)
12
attr_accessor :attack
13
14
# @!attribute config
15
# @return [String] The path to an optional config file for John to use
16
attr_accessor :config
17
18
# @!attribute cracker
19
# @return [String] Which cracker to use. 'john' and 'hashcat' are valid
20
attr_accessor :cracker
21
22
# @!attribute cracker_path
23
# This attribute allows the user to specify a cracker binary to use.
24
# If not supplied, the Cracker will search the PATH for a suitable john or hashcat binary
25
# and finally fall back to the pre-compiled john versions shipped with Metasploit.
26
#
27
# @return [String] The file path to an alternative cracker binary to use
28
attr_accessor :cracker_path
29
30
# @!attribute format
31
# If the cracker type is john, this format will automatically be translated
32
# to the hashcat equivalent via jtr_format_to_hashcat_format
33
#
34
# @return [String] The hash format to try.
35
attr_accessor :format
36
37
# @!attribute fork
38
# If the cracker type is john, the amount of forks to specify
39
#
40
# @return [String] The hash format to try.
41
attr_accessor :fork
42
43
# @!attribute hash_path
44
# @return [String] The path to the file containing the hashes
45
attr_accessor :hash_path
46
47
# @!attribute incremental
48
# @return [String] The incremental mode to use
49
attr_accessor :incremental
50
51
# @!attribute increment_length
52
# @return [Array] The incremental min and max to use
53
attr_accessor :increment_length
54
55
# @!attribute mask
56
# If the cracker type is hashcat, If set, the mask to use. Should consist of the character sets
57
# pre-defined by hashcat, such as ?d ?s ?l etc
58
#
59
# @return [String] The mask to use
60
attr_accessor :mask
61
62
# @!attribute max_runtime
63
# @return [Integer] An optional maximum duration of the cracking attempt in seconds
64
attr_accessor :max_runtime
65
66
# @!attribute max_length
67
# @return [Integer] An optional maximum length of password to attempt cracking
68
attr_accessor :max_length
69
70
# @!attribute optimize
71
# @return [Boolean] If the Optimize flag should be given to Hashcat
72
attr_accessor :optimize
73
74
# @!attribute pot
75
# @return [String] The file path to an alternative John pot file to use
76
attr_accessor :pot
77
78
# @!attribute rules
79
# @return [String] The wordlist mangling rules to use inside John/Hashcat
80
attr_accessor :rules
81
82
# @!attribute wordlist
83
# @return [String] The file path to the wordlist to use
84
attr_accessor :wordlist
85
86
validates :config, 'Metasploit::Framework::File_path': true, if: -> { config.present? }
87
88
validates :cracker, inclusion: { in: %w[john hashcat] }
89
90
validates :cracker_path, 'Metasploit::Framework::Executable_path': true, if: -> { cracker_path.present? }
91
92
validates :fork,
93
numericality: {
94
only_integer: true,
95
greater_than_or_equal_to: 1
96
}, if: -> { fork.present? }
97
98
validates :hash_path, 'Metasploit::Framework::File_path': true, if: -> { hash_path.present? }
99
100
validates :pot, 'Metasploit::Framework::File_path': true, if: -> { pot.present? }
101
102
validates :max_runtime,
103
numericality: {
104
only_integer: true,
105
greater_than_or_equal_to: 0
106
}, if: -> { max_runtime.present? }
107
108
validates :max_length,
109
numericality: {
110
only_integer: true,
111
greater_than_or_equal_to: 0
112
}, if: -> { max_length.present? }
113
114
validates :wordlist, 'Metasploit::Framework::File_path': true, if: -> { wordlist.present? }
115
116
# @param attributes [Hash{Symbol => String,nil}]
117
def initialize(attributes = {})
118
attributes.each do |attribute, value|
119
public_send("#{attribute}=", value)
120
end
121
end
122
123
# This method takes a {framework.db.cred.private.jtr_format} (string), and
124
# returns the string number associated to the hashcat format
125
#
126
# @param format [String] A jtr_format string
127
# @return [String] The format number for Hashcat
128
def jtr_format_to_hashcat_format(format)
129
case format
130
# nix
131
when 'md5crypt'
132
'500'
133
when 'descrypt'
134
'1500'
135
when 'bsdicrypt'
136
'12400'
137
when 'sha256crypt'
138
'7400'
139
when 'sha512crypt'
140
'1800'
141
when 'bcrypt'
142
'3200'
143
# windows
144
when 'lm', 'lanman'
145
'3000'
146
when 'nt', 'ntlm'
147
'1000'
148
when 'mscash'
149
'1100'
150
when 'mscash2'
151
'2100'
152
when 'netntlm'
153
'5500'
154
when 'netntlmv2'
155
'5600'
156
# dbs
157
when 'mssql'
158
'131'
159
when 'mssql05'
160
'132'
161
when 'mssql12'
162
'1731'
163
# hashcat requires a format we dont have all the data for
164
# in the current dumper, so this is disabled in module and lib
165
# when 'oracle', 'des,oracle'
166
# return '3100'
167
when 'oracle11', 'raw-sha1,oracle'
168
'112'
169
when 'oracle12c', 'pbkdf2,oracle12c'
170
'12300'
171
when 'postgres', 'dynamic_1034', 'raw-md5,postgres'
172
'12'
173
when 'mysql'
174
'200'
175
when 'mysql-sha1'
176
'300'
177
when 'PBKDF2-HMAC-SHA512' # osx 10.8+
178
'7100'
179
# osx
180
when 'xsha' # osx 10.4-6
181
'122'
182
when 'xsha512' # osx 10.7
183
'1722'
184
# webapps
185
when 'PBKDF2-HMAC-SHA1' # Atlassian
186
'12001'
187
when 'phpass' # Wordpress/PHPass, Joomla, phpBB3
188
'400'
189
when 'mediawiki' # mediawiki b type
190
'3711'
191
# mobile
192
when 'android-samsung-sha1'
193
'5800'
194
when 'android-sha1'
195
'110'
196
when 'android-md5'
197
'10'
198
when 'hmac-md5'
199
'10200'
200
when 'dynamic_82'
201
'1710'
202
when 'ssha'
203
'111'
204
when 'raw-sha512'
205
'1700'
206
when 'raw-sha256'
207
'1400'
208
when 'raw-sha1'
209
'100'
210
when 'raw-md5'
211
'0'
212
when 'smd5'
213
'6300'
214
when 'ssha256'
215
'1411'
216
when 'ssha512'
217
'1711'
218
when 'Raw-MD5u'
219
'30'
220
when 'pbkdf2-sha256'
221
'10900'
222
end
223
end
224
225
# This method sets the appropriate parameters to run a cracker in incremental mode
226
def mode_incremental
227
self.increment_length = nil
228
self.wordlist = nil
229
self.mask = nil
230
self.max_runtime = nil
231
if cracker == 'john'
232
self.rules = nil
233
self.incremental = 'Digits'
234
elsif cracker == 'hashcat'
235
self.attack = '3'
236
self.incremental = true
237
end
238
end
239
240
# This method sets the appropriate parameters to run a cracker in wordlist mode
241
#
242
# @param file [String] A file location of the wordlist to use
243
def mode_wordlist(file)
244
self.increment_length = nil
245
self.incremental = nil
246
self.max_runtime = nil
247
self.mask = nil
248
if cracker == 'john'
249
self.wordlist = file
250
self.rules = 'wordlist'
251
elsif cracker == 'hashcat'
252
self.wordlist = file
253
self.attack = '0'
254
end
255
end
256
257
# This method sets the appropriate parameters to run a cracker in a pin mode (4-8 digits) on hashcat
258
def mode_pin
259
self.rules = nil
260
if cracker == 'hashcat'
261
self.attack = '3'
262
self.mask = '?d' * 8
263
self.incremental = true
264
self.increment_length = [4, 8]
265
self.max_runtime = 300 # 5min on an i7 got through 4-7 digits. 8digit was 32min more
266
end
267
end
268
269
# This method sets the john to 'normal' mode
270
def mode_normal
271
if cracker == 'john'
272
self.max_runtime = nil
273
self.mask = nil
274
self.wordlist = nil
275
self.rules = nil
276
self.incremental = nil
277
self.increment_length = nil
278
end
279
end
280
281
# This method sets the john to single mode
282
#
283
# @param file [String] A file location of the wordlist to use
284
def mode_single(file)
285
if cracker == 'john'
286
self.wordlist = file
287
self.rules = 'single'
288
self.incremental = nil
289
self.increment_length = nil
290
self.mask = nil
291
end
292
end
293
294
# This method follows a decision tree to determine the path
295
# to the cracker binary we should use.
296
#
297
# @return [String, NilClass] Returns Nil if a binary path could not be found, or a String containing the path to the selected JTR binary on success.
298
def binary_path
299
# Always prefer a manually entered path
300
if cracker_path && ::File.file?(cracker_path)
301
return cracker_path
302
else
303
# Look in the Environment PATH for the john binary
304
if cracker == 'john'
305
path = Rex::FileUtils.find_full_path('john') ||
306
Rex::FileUtils.find_full_path('john.exe')
307
elsif cracker == 'hashcat'
308
path = Rex::FileUtils.find_full_path('hashcat') ||
309
Rex::FileUtils.find_full_path('hashcat.exe')
310
else
311
raise PasswordCrackerNotFoundError, 'No suitable Cracker was selected, so a binary could not be found on the system'
312
end
313
314
if path && ::File.file?(path)
315
return path
316
end
317
318
raise PasswordCrackerNotFoundError, 'No suitable john/hashcat binary was found on the system'
319
end
320
end
321
322
# This method runs the command from {#crack_command} and yields each line of output.
323
#
324
# @yield [String] a line of output from the cracker command
325
# @return [void]
326
def crack(&block)
327
if cracker == 'john'
328
results = john_crack_command
329
elsif cracker == 'hashcat'
330
results = hashcat_crack_command
331
end
332
::IO.popen(results, 'rb') do |fd|
333
fd.each_line(&block)
334
end
335
end
336
337
# This method returns the version of John the Ripper or Hashcat being used.
338
#
339
# @raise [PasswordCrackerNotFoundError] if a suitable cracker binary was never found
340
# @return [String] the version detected
341
def cracker_version
342
if cracker == 'john'
343
cmd = binary_path
344
elsif cracker == 'hashcat'
345
cmd = binary_path
346
cmd << (' -V')
347
end
348
::IO.popen(cmd, 'rb') do |fd|
349
fd.each_line do |line|
350
if cracker == 'john'
351
# John the Ripper 1.8.0.13-jumbo-1-bleeding-973a245b96 2018-12-17 20:12:51 +0100 OMP [linux-gnu 64-bit x86_64 AVX2 AC]
352
# John the Ripper 1.9.0-jumbo-1 OMP [linux-gnu 64-bit x86_64 AVX2 AC]
353
# John the Ripper password cracker, version 1.8.0.2-bleeding-jumbo_omp [64-bit AVX-autoconf]
354
# John the Ripper password cracker, version 1.8.0
355
return Regexp.last_match(1).strip if line =~ /John the Ripper(?: password cracker, version)? ([^\[]+)/
356
elsif cracker == 'hashcat'
357
# v5.1.0
358
return Regexp.last_match(1) if line =~ /(v[\d.]+)/
359
end
360
end
361
end
362
nil
363
end
364
365
# This method is used to determine which format of the no log option should be used
366
# --no-log vs --nolog https://github.com/openwall/john/commit/8982e4f7a2e874aab29807a05b421373015c9b61
367
# We base this either on a date being in the version, or running the command and checking the output
368
#
369
# @return [String] The nolog format to use
370
def john_nolog_format
371
if /(\d{4}-\d{2}-\d{2})/ =~ cracker_version
372
# we lucked out and theres a date, we'll check its older than the commit that changed the nolog
373
if Date.parse(Regexp.last_match(1)) < Date.parse('2020-11-27')
374
return '--nolog'
375
end
376
377
return '--no-log'
378
end
379
380
# no date, so lets give it a run with the old format and check if we raise an error
381
# on *nix 'unknown option' goes to stderr
382
::IO.popen([binary_path, '--nolog', { err: %i[child out] }], 'rb') do |fd|
383
return '--nolog' unless fd.read.include? 'Unknown option'
384
end
385
'--no-log'
386
end
387
388
# This method builds an array for the command to actually run the cracker.
389
# It builds the command from all of the attributes on the class.
390
#
391
# @raise [PasswordCrackerNotFoundError] if a suitable John binary was never found
392
# @return [Array] An array set up for {::IO.popen} to use
393
def john_crack_command
394
cmd_string = binary_path
395
396
cmd = [cmd_string, '--session=' + cracker_session_id, john_nolog_format]
397
398
if config.present?
399
cmd << ('--config=' + config)
400
else
401
cmd << ('--config=' + john_config_file)
402
end
403
404
if pot.present?
405
cmd << ('--pot=' + pot)
406
else
407
cmd << ('--pot=' + john_pot_file)
408
end
409
410
if fork.present? && fork > 1
411
cmd << ('--fork=' + fork.to_s)
412
end
413
414
if format.present?
415
cmd << ('--format=' + format)
416
end
417
418
if wordlist.present?
419
cmd << ('--wordlist=' + wordlist)
420
end
421
422
if incremental.present?
423
cmd << ('--incremental=' + incremental)
424
end
425
426
if rules.present?
427
cmd << ('--rules=' + rules)
428
end
429
430
if max_runtime.present?
431
cmd << ('--max-run-time=' + max_runtime.to_s)
432
end
433
434
if max_length.present?
435
cmd << ('--max-len=' + max_length.to_s)
436
end
437
438
cmd << hash_path
439
end
440
441
# This method builds an array for the command to actually run the cracker.
442
# It builds the command from all of the attributes on the class.
443
#
444
# @raise [PasswordCrackerNotFoundError] if a suitable Hashcat binary was never found
445
# @return [Array] An array set up for {::IO.popen} to use
446
def hashcat_crack_command
447
cmd_string = binary_path
448
cmd = [cmd_string, '--session=' + cracker_session_id, '--logfile-disable', '--quiet', '--username']
449
450
if pot.present?
451
cmd << ('--potfile-path=' + pot)
452
else
453
cmd << ('--potfile-path=' + john_pot_file)
454
end
455
456
if format.present?
457
cmd << ('--hash-type=' + jtr_format_to_hashcat_format(format))
458
end
459
460
if optimize.present?
461
# https://hashcat.net/wiki/doku.php?id=frequently_asked_questions#what_is_the_maximum_supported_password_length_for_optimized_kernels
462
# Optimized Kernels has a large impact on speed. Here are some stats from Hashcat 5.1.0:
463
464
# Kali Linux on Dell Precision M3800
465
## hashcat -b -w 2 -m 0
466
# * Device #1: Quadro K1100M, 500/2002 MB allocatable, 2MCU
467
# Speed.#1.........: 185.9 MH/s (11.15ms) @ Accel:64 Loops:16 Thr:1024 Vec:1
468
469
## hashcat -b -w 2 -O -m 0
470
# * Device #1: Quadro K1100M, 500/2002 MB allocatable, 2MCU
471
# Speed.#1.........: 463.6 MH/s (8.92ms) @ Accel:64 Loops:32 Thr:1024 Vec:1
472
473
# Windows 10
474
# PS C:\hashcat-5.1.0> .\hashcat64.exe -b -O -w 2 -m 0
475
# * Device #1: GeForce RTX 2070 SUPER, 2048/8192 MB allocatable, 40MCU
476
# Speed.#1.........: 13914.0 MH/s (5.77ms) @ Accel:128 Loops:64 Thr:256 Vec:1
477
478
# PS C:\hashcat-5.1.0> .\hashcat64.exe -b -O -w 2 -m 0
479
# * Device #1: GeForce RTX 2070 SUPER, 2048/8192 MB allocatable, 40MCU
480
# Speed.#1.........: 31545.6 MH/s (10.36ms) @ Accel:256 Loops:128 Thr:256 Vec:1
481
482
# This change should result in 225%-250% speed boost at the sacrifice of some password length, which most likely
483
# wouldn't be tested inside of MSF since most users are using the MSF modules for word list and easy cracks.
484
# Anything of length where this would cut off is most likely being done independently (outside MSF)
485
486
cmd << ('-O')
487
end
488
489
if incremental.present?
490
cmd << ('--increment')
491
if increment_length.present?
492
cmd << ('--increment-min=' + increment_length[0].to_s)
493
cmd << ('--increment-max=' + increment_length[1].to_s)
494
else
495
# anything more than max 4 on even des took 8+min on an i7.
496
# maybe in the future this can be adjusted or made a variable
497
# but current time, we'll leave it as this seems like reasonable
498
# time expectation for a module to run
499
cmd << ('--increment-max=4')
500
end
501
end
502
503
if rules.present?
504
cmd << ('--rules-file=' + rules)
505
end
506
507
if attack.present?
508
cmd << ('--attack-mode=' + attack)
509
end
510
511
if max_runtime.present?
512
cmd << ('--runtime=' + max_runtime.to_s)
513
end
514
515
cmd << hash_path
516
517
if mask.present?
518
cmd << mask.to_s
519
end
520
521
# must be last
522
if wordlist.present?
523
cmd << (wordlist)
524
end
525
cmd
526
end
527
528
# This runs the show command in john and yields cracked passwords.
529
#
530
# @return [Array] the output from the command split on newlines
531
def each_cracked_password
532
::IO.popen(show_command, 'rb').readlines
533
end
534
535
# This method returns the path to a default john.conf file.
536
#
537
# @return [String] the path to the default john.conf file
538
def john_config_file
539
::File.join(::Msf::Config.data_directory, 'jtr', 'john.conf')
540
end
541
542
# This method returns the path to a default john.pot file.
543
#
544
# @return [String] the path to the default john.pot file
545
def john_pot_file
546
::File.join(::Msf::Config.config_directory, 'john.pot')
547
end
548
549
# This method is a getter for a random Session ID for the cracker.
550
# It allows us to dinstiguish between cracking sessions.
551
#
552
# @ return [String] the Session ID to use
553
def cracker_session_id
554
@session_id ||= ::Rex::Text.rand_text_alphanumeric(8)
555
end
556
557
# This method builds the command to show the cracked passwords.
558
#
559
# @raise [JohnNotFoundError] if a suitable John binary was never found
560
# @return [Array] An array set up for {::IO.popen} to use
561
def show_command
562
cmd_string = binary_path
563
564
pot_file = pot || john_pot_file
565
if cracker == 'hashcat'
566
cmd = [cmd_string, '--show', '--username', "--potfile-path=#{pot_file}", "--hash-type=#{jtr_format_to_hashcat_format(format)}"]
567
elsif cracker == 'john'
568
cmd = [cmd_string, '--show', "--pot=#{pot_file}", "--format=#{format}"]
569
570
if config
571
cmd << "--config=#{config}"
572
else
573
cmd << ('--config=' + john_config_file)
574
end
575
end
576
cmd << hash_path
577
end
578
579
end
580
end
581
end
582
end
583
584