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