Class: Metasploit::Framework::PasswordCracker::Cracker

Inherits:
Object
  • Object
show all
Includes:
ActiveModel::Validations
Defined in:
lib/metasploit/framework/password_crackers/cracker.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(attributes = {}) ⇒ Cracker

Returns a new instance of Cracker.

Parameters:

  • attributes (Hash{Symbol => String,nil}) (defaults to: {})

117
118
119
120
121
# File 'lib/metasploit/framework/password_crackers/cracker.rb', line 117

def initialize(attributes = {})
  attributes.each do |attribute, value|
    public_send("#{attribute}=", value)
  end
end

Instance Attribute Details

#attackString

Returns The attack mode for hashcat to use (not applicable to John).

Returns:

  • (String)

    The attack mode for hashcat to use (not applicable to John)


12
13
14
# File 'lib/metasploit/framework/password_crackers/cracker.rb', line 12

def attack
  @attack
end

#configString

Returns The path to an optional config file for John to use.

Returns:

  • (String)

    The path to an optional config file for John to use


16
17
18
# File 'lib/metasploit/framework/password_crackers/cracker.rb', line 16

def config
  @config
end

#crackerString

Returns Which cracker to use. 'john' and 'hashcat' are valid.

Returns:

  • (String)

    Which cracker to use. 'john' and 'hashcat' are valid


20
21
22
# File 'lib/metasploit/framework/password_crackers/cracker.rb', line 20

def cracker
  @cracker
end

#cracker_pathString

This attribute allows the user to specify a cracker binary to use. If not supplied, the Cracker will search the PATH for a suitable john or hashcat binary and finally fall back to the pre-compiled john versions shipped with Metasploit.

Returns:

  • (String)

    The file path to an alternative cracker binary to use


28
29
30
# File 'lib/metasploit/framework/password_crackers/cracker.rb', line 28

def cracker_path
  @cracker_path
end

#forkString

If the cracker type is john, the amount of forks to specify

Returns:

  • (String)

    The hash format to try.


41
42
43
# File 'lib/metasploit/framework/password_crackers/cracker.rb', line 41

def fork
  @fork
end

#formatString

If the cracker type is john, this format will automatically be translated to the hashcat equivalent via jtr_format_to_hashcat_format

Returns:

  • (String)

    The hash format to try.


35
36
37
# File 'lib/metasploit/framework/password_crackers/cracker.rb', line 35

def format
  @format
end

#hash_pathString

Returns The path to the file containing the hashes.

Returns:

  • (String)

    The path to the file containing the hashes


45
46
47
# File 'lib/metasploit/framework/password_crackers/cracker.rb', line 45

def hash_path
  @hash_path
end

#increment_lengthArray

Returns The incremental min and max to use.

Returns:

  • (Array)

    The incremental min and max to use


53
54
55
# File 'lib/metasploit/framework/password_crackers/cracker.rb', line 53

def increment_length
  @increment_length
end

#incrementalString

Returns The incremental mode to use.

Returns:

  • (String)

    The incremental mode to use


49
50
51
# File 'lib/metasploit/framework/password_crackers/cracker.rb', line 49

def incremental
  @incremental
end

#maskObject

If the cracker type is hashcat, If set, the mask to use. Should consist of the character sets pre-defined by hashcat, such as ?d ?s ?l etc

@return [String] The mask to use

60
61
62
# File 'lib/metasploit/framework/password_crackers/cracker.rb', line 60

def mask
  @mask
end

#max_lengthInteger

Returns An optional maximum length of password to attempt cracking.

Returns:

  • (Integer)

    An optional maximum length of password to attempt cracking


68
69
70
# File 'lib/metasploit/framework/password_crackers/cracker.rb', line 68

def max_length
  @max_length
end

#max_runtimeInteger

Returns An optional maximum duration of the cracking attempt in seconds.

Returns:

  • (Integer)

    An optional maximum duration of the cracking attempt in seconds


64
65
66
# File 'lib/metasploit/framework/password_crackers/cracker.rb', line 64

def max_runtime
  @max_runtime
end

#optimizeBoolean

Returns If the Optimize flag should be given to Hashcat.

Returns:

  • (Boolean)

    If the Optimize flag should be given to Hashcat


72
73
74
# File 'lib/metasploit/framework/password_crackers/cracker.rb', line 72

def optimize
  @optimize
end

#potString

Returns The file path to an alternative John pot file to use.

Returns:

  • (String)

    The file path to an alternative John pot file to use


76
77
78
# File 'lib/metasploit/framework/password_crackers/cracker.rb', line 76

def pot
  @pot
end

#rulesString

Returns The wordlist mangling rules to use inside John/Hashcat.

Returns:

  • (String)

    The wordlist mangling rules to use inside John/Hashcat


80
81
82
# File 'lib/metasploit/framework/password_crackers/cracker.rb', line 80

def rules
  @rules
end

#wordlistString

Returns The file path to the wordlist to use.

Returns:

  • (String)

    The file path to the wordlist to use


84
85
86
# File 'lib/metasploit/framework/password_crackers/cracker.rb', line 84

def wordlist
  @wordlist
end

Instance Method Details

#binary_pathNilClass, String

This method follows a decision tree to determine the path to the cracker binary we should use.

Returns:

  • (NilClass)

    if a binary path could not be found

  • (String)

    the path to the selected JtR binary


287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
# File 'lib/metasploit/framework/password_crackers/cracker.rb', line 287

def binary_path
  # Always prefer a manually entered path
  if cracker_path && ::File.file?(cracker_path)
    return cracker_path
  else
    # Look in the Environment PATH for the john binary
    if cracker == 'john'
      path = Rex::FileUtils.find_full_path('john') ||
             Rex::FileUtils.find_full_path('john.exe')
    elsif cracker == 'hashcat'
      path = Rex::FileUtils.find_full_path('hashcat') ||
             Rex::FileUtils.find_full_path('hashcat.exe')
    else
      raise PasswordCrackerNotFoundError, 'No suitable Cracker was selected, so a binary could not be found on the system'
    end

    if path && ::File.file?(path)
      return path
    end

    raise PasswordCrackerNotFoundError, 'No suitable john/hashcat binary was found on the system'
  end
end

#crack {|String| ... } ⇒ void

This method returns an undefined value.

This method runs the command from #crack_command and yields each line of output.

Yields:

  • (String)

    a line of output from the cracker command


315
316
317
318
319
320
321
322
323
324
# File 'lib/metasploit/framework/password_crackers/cracker.rb', line 315

def crack(&block)
  if cracker == 'john'
    results = john_crack_command
  elsif cracker == 'hashcat'
    results = hashcat_crack_command
  end
  ::IO.popen(results, 'rb') do |fd|
    fd.each_line(&block)
  end
end

#cracker_session_idObject

This method is a getter for a random Session ID for the cracker. It allows us to dinstiguish between cracking sessions.

@ return [String] the Session ID to use


542
543
544
# File 'lib/metasploit/framework/password_crackers/cracker.rb', line 542

def cracker_session_id
  @session_id ||= ::Rex::Text.rand_text_alphanumeric(8)
end

#cracker_versionSring

This method returns the version of John the Ripper or Hashcat being used.

Returns:

  • (Sring)

    the version detected

Raises:


330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
# File 'lib/metasploit/framework/password_crackers/cracker.rb', line 330

def cracker_version
  if cracker == 'john'
    cmd = binary_path
  elsif cracker == 'hashcat'
    cmd = binary_path
    cmd << (' -V')
  end
  ::IO.popen(cmd, 'rb') do |fd|
    fd.each_line do |line|
      if cracker == 'john'
        # 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]
        # John the Ripper 1.9.0-jumbo-1 OMP [linux-gnu 64-bit x86_64 AVX2 AC]
        # John the Ripper password cracker, version 1.8.0.2-bleeding-jumbo_omp [64-bit AVX-autoconf]
        # John the Ripper password cracker, version 1.8.0
        return Regexp.last_match(1).strip if line =~ /John the Ripper(?: password cracker, version)? ([^\[]+)/
      elsif cracker == 'hashcat'
        # v5.1.0
        return Regexp.last_match(1) if line =~ /(v[\d.]+)/
      end
    end
  end
  nil
end

#each_cracked_passwordArray

This runs the show command in john and yields cracked passwords.

Returns:

  • (Array)

    the output from teh command split on newlines


520
521
522
# File 'lib/metasploit/framework/password_crackers/cracker.rb', line 520

def each_cracked_password
  ::IO.popen(show_command, 'rb').readlines
end

#hashcat_crack_commandArray

This method builds an array for the command to actually run the cracker. It builds the command from all of the attributes on the class.

Returns:

  • (Array)

    An array set up for IO.popen to use

Raises:


435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
# File 'lib/metasploit/framework/password_crackers/cracker.rb', line 435

def hashcat_crack_command
  cmd_string = binary_path
  cmd = [cmd_string, '--session=' + cracker_session_id, '--logfile-disable']

  if pot.present?
    cmd << ('--potfile-path=' + pot)
  else
    cmd << ('--potfile-path=' + john_pot_file)
  end

  if format.present?
    cmd << ('--hash-type=' + jtr_format_to_hashcat_format(format))
  end

  if optimize.present?
    # https://hashcat.net/wiki/doku.php?id=frequently_asked_questions#what_is_the_maximum_supported_password_length_for_optimized_kernels
    # Optimized Kernels has a large impact on speed.  Here are some stats from Hashcat 5.1.0:

    # Kali Linux on Dell Precision M3800
    ## hashcat -b -w 2 -m 0
    # * Device #1: Quadro K1100M, 500/2002 MB allocatable, 2MCU
    # Speed.#1.........:   185.9 MH/s (11.15ms) @ Accel:64 Loops:16 Thr:1024 Vec:1

    ## hashcat -b -w 2 -O -m 0
    # * Device #1: Quadro K1100M, 500/2002 MB allocatable, 2MCU
    # Speed.#1.........:   463.6 MH/s (8.92ms) @ Accel:64 Loops:32 Thr:1024 Vec:1

    # Windows 10
    # PS C:\hashcat-5.1.0> .\hashcat64.exe -b -O -w 2 -m 0
    # * Device #1: GeForce RTX 2070 SUPER, 2048/8192 MB allocatable, 40MCU
    # Speed.#1.........: 13914.0 MH/s (5.77ms) @ Accel:128 Loops:64 Thr:256 Vec:1

    # PS C:\hashcat-5.1.0> .\hashcat64.exe -b -O -w 2 -m 0
    # * Device #1: GeForce RTX 2070 SUPER, 2048/8192 MB allocatable, 40MCU
    # Speed.#1.........: 31545.6 MH/s (10.36ms) @ Accel:256 Loops:128 Thr:256 Vec:1

    # This change should result in 225%-250% speed boost at the sacrifice of some password length, which most likely
    # wouldn't be tested inside of MSF since most users are using the MSF modules for word list and easy cracks.
    # Anything of length where this would cut off is most likely being done independently (outside MSF)

    cmd << ('-O')
  end

  if incremental.present?
    cmd << ('--increment')
    if increment_length.present?
      cmd << ('--increment-min=' + increment_length[0].to_s)
      cmd << ('--increment-max=' + increment_length[1].to_s)
    else
      # anything more than max 4 on even des took 8+min on an i7.
      # maybe in the future this can be adjusted or made a variable
      # but current time, we'll leave it as this seems like reasonable
      # time expectation for a module to run
      cmd << ('--increment-max=4')
    end
  end

  if rules.present?
    cmd << ('--rules-file=' + rules)
  end

  if attack.present?
    cmd << ('--attack-mode=' + attack)
  end

  if max_runtime.present?
    cmd << ('--runtime=' + max_runtime.to_s)
  end

  cmd << hash_path

  if mask.present?
    cmd << mask.to_s
  end

  # must be last
  if wordlist.present?
    cmd << (wordlist)
  end
  cmd
end

#john_config_fileString

This method returns the path to a default john.conf file.

Returns:

  • (String)

    the path to the default john.conf file


527
528
529
# File 'lib/metasploit/framework/password_crackers/cracker.rb', line 527

def john_config_file
  ::File.join(::Msf::Config.data_directory, 'jtr', 'john.conf')
end

#john_crack_commandArray

This method builds an array for the command to actually run the cracker. It builds the command from all of the attributes on the class.

Returns:

  • (Array)

    An array set up for IO.popen to use

Raises:


382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
# File 'lib/metasploit/framework/password_crackers/cracker.rb', line 382

def john_crack_command
  cmd_string = binary_path

  cmd = [cmd_string, '--session=' + cracker_session_id, john_nolog_format]

  if config.present?
    cmd << ('--config=' + config)
  else
    cmd << ('--config=' + john_config_file)
  end

  if pot.present?
    cmd << ('--pot=' + pot)
  else
    cmd << ('--pot=' + john_pot_file)
  end

  if fork.present? && fork > 1
    cmd << ('--fork=' + fork.to_s)
  end

  if format.present?
    cmd << ('--format=' + format)
  end

  if wordlist.present?
    cmd << ('--wordlist=' + wordlist)
  end

  if incremental.present?
    cmd << ('--incremental=' + incremental)
  end

  if rules.present?
    cmd << ('--rules=' + rules)
  end

  if max_runtime.present?
    cmd << ('--max-run-time=' + max_runtime.to_s)
  end

  if max_length.present?
    cmd << ('--max-len=' + max_length.to_s)
  end

  cmd << hash_path
end

#john_nolog_formatString

This method is used to determine which format of the no log option should be used –no-log vs –nolog github.com/openwall/john/commit/8982e4f7a2e874aab29807a05b421373015c9b61 We base this either on a date being in the version, or running the command and checking the output

Returns:

  • (String)

    The nolog format to use


359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
# File 'lib/metasploit/framework/password_crackers/cracker.rb', line 359

def john_nolog_format
  if /(\d{4}-\d{2}-\d{2})/ =~ cracker_version
    # we lucked out and theres a date, we'll check its older than the commit that changed the nolog
    if Date.parse(Regexp.last_match(1)) < Date.parse('2020-11-27')
      return '--nolog'
    end

    return '--no-log'
  end

  # no date, so lets give it a run with the old format and check if we raise an error
  # on *nix 'unknown option' goes to stderr
  ::IO.popen([binary_path, '--nolog', { err: %i[child out] }], 'rb') do |fd|
    return '--nolog' unless fd.read.include? 'Unknown option'
  end
  '--no-log'
end

#john_pot_fileString

This method returns the path to a default john.pot file.

Returns:

  • (String)

    the path to the default john.pot file


534
535
536
# File 'lib/metasploit/framework/password_crackers/cracker.rb', line 534

def john_pot_file
  ::File.join(::Msf::Config.config_directory, 'john.pot')
end

#jtr_format_to_hashcat_format(format) ⇒ String

This method takes a Metasploit::Framework::PasswordCracker::Cracker.frameworkframework.dbframework.db.credframework.db.cred.privateframework.db.cred.private.jtr_format (string), and returns the string number associated to the hashcat format

@param a jtr_format string

Returns:

  • (String)

    the format number for Hashcat


128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
# File 'lib/metasploit/framework/password_crackers/cracker.rb', line 128

def jtr_format_to_hashcat_format(format)
  case format
  when 'md5crypt'
    '500'
  when 'descrypt'
    '1500'
  when 'bsdicrypt'
    '12400'
  when 'sha256crypt'
    '7400'
  when 'sha512crypt'
    '1800'
  when 'bcrypt'
    '3200'
  when 'lm', 'lanman'
    '3000'
  when 'nt', 'ntlm'
    '1000'
  when 'mssql'
    '131'
  when 'mssql05'
    '132'
  when 'mssql12'
    '1731'
  # hashcat requires a format we dont have all the data for
  # in the current dumper, so this is disabled in module and lib
  # when 'oracle', 'des,oracle'
  #  return '3100'
  when 'oracle11', 'raw-sha1,oracle'
    '112'
  when 'oracle12c', 'pbkdf2,oracle12c'
    '12300'
  when 'postgres', 'dynamic_1034', 'raw-md5,postgres'
    '12'
  when 'mysql'
    '200'
  when 'mysql-sha1'
    '300'
  when 'PBKDF2-HMAC-SHA512' # osx 10.8+
    '7100'
  when 'xsha' # osx 10.4-6
    '122'
  when 'xsha512' # osx 10.7
    '1722'
  when 'PBKDF2-HMAC-SHA1' # Atlassian
    '12001'
  when 'phpass' # Wordpress/PHPass, Joomla, phpBB3
    '400'
  when 'mediawiki' # mediawiki b type
    '3711'
  when 'android-samsung-sha1'
    '5800'
  when 'android-sha1'
    '110'
  when 'android-md5'
    '10'
  when 'hmac-md5'
    '10200'
  when 'dynamic_82'
    '1710'
  when 'ssha'
    '111'
  when 'raw-sha512'
    '1700'
  when 'raw-sha256'
    '1400'
  when 'raw-sha1'
    '100'
  when 'raw-md5'
    '0'
  when 'smd5'
    '6300'
  when 'ssha256'
    '1411'
  when 'ssha512'
    '1711'
  when 'mscash'
    '1100'
  when 'mscash2'
    '2100'
  when 'Raw-MD5u'
    '30'
  end
end

#mode_incrementalObject

This method sets the appropriate parameters to run a cracker in incremental mode


214
215
216
217
218
219
220
221
222
223
224
225
226
# File 'lib/metasploit/framework/password_crackers/cracker.rb', line 214

def mode_incremental
  self.increment_length = nil
  self.wordlist = nil
  self.mask = nil
  self.max_runtime = nil
  if cracker == 'john'
    self.rules = nil
    self.incremental = 'Digits'
  elsif cracker == 'hashcat'
    self.attack = '3'
    self.incremental = true
  end
end

#mode_normalObject

This method sets the john to 'normal' mode


258
259
260
261
262
263
264
265
266
267
# File 'lib/metasploit/framework/password_crackers/cracker.rb', line 258

def mode_normal
  if cracker == 'john'
    self.max_runtime = nil
    self.mask = nil
    self.wordlist = nil
    self.rules = nil
    self.incremental = nil
    self.increment_length = nil
  end
end

#mode_pinObject

This method sets the appropriate parameters to run a cracker in a pin mode (4-8 digits) on hashcat


246
247
248
249
250
251
252
253
254
255
# File 'lib/metasploit/framework/password_crackers/cracker.rb', line 246

def mode_pin
  self.rules = nil
  if cracker == 'hashcat'
    self.attack = '3'
    self.mask = '?d' * 8
    self.incremental = true
    self.increment_length = [4, 8]
    self.max_runtime = 300 # 5min on an i7 got through 4-7 digits. 8digit was 32min more
  end
end

#mode_single(file) ⇒ Object

This method sets the john to single mode

@param a file location of the wordlist to use


272
273
274
275
276
277
278
279
280
# File 'lib/metasploit/framework/password_crackers/cracker.rb', line 272

def mode_single(file)
  if cracker == 'john'
    self.wordlist = file
    self.rules = 'single'
    self.incremental = nil
    self.increment_length = nil
    self.mask = nil
  end
end

#mode_wordlist(file) ⇒ Object

This method sets the appropriate parameters to run a cracker in wordlist mode

@param a file location of the wordlist to use


231
232
233
234
235
236
237
238
239
240
241
242
243
# File 'lib/metasploit/framework/password_crackers/cracker.rb', line 231

def mode_wordlist(file)
  self.increment_length = nil
  self.incremental = nil
  self.max_runtime = nil
  self.mask = nil
  if cracker == 'john'
    self.wordlist = file
    self.rules = 'wordlist'
  elsif cracker == 'hashcat'
    self.wordlist = file
    self.attack = '0'
  end
end

#show_commandArray

This method builds the command to show the cracked passwords.

Returns:

  • (Array)

    An array set up for IO.popen to use

Raises:

  • (JohnNotFoundError)

    if a suitable John binary was never found


550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
# File 'lib/metasploit/framework/password_crackers/cracker.rb', line 550

def show_command
  cmd_string = binary_path

  pot_file = pot || john_pot_file
  if cracker == 'hashcat'
    cmd = [cmd_string, '--show', "--potfile-path=#{pot_file}", "--hash-type=#{jtr_format_to_hashcat_format(format)}"]
  elsif cracker == 'john'
    cmd = [cmd_string, '--show', "--pot=#{pot_file}", "--format=#{format}"]

    if config
      cmd << "--config=#{config}"
    else
      cmd << ('--config=' + john_config_file)
    end
  end
  cmd << hash_path
end