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_pathString, NilClass

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

Returns:

  • (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
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
# File 'lib/metasploit/framework/password_crackers/cracker.rb', line 298

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



326
327
328
329
330
331
332
333
334
335
# File 'lib/metasploit/framework/password_crackers/cracker.rb', line 326

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



553
554
555
# File 'lib/metasploit/framework/password_crackers/cracker.rb', line 553

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

#cracker_versionString

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

Returns:

  • (String)

    the version detected

Raises:



341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
# File 'lib/metasploit/framework/password_crackers/cracker.rb', line 341

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 the command split on newlines



531
532
533
# File 'lib/metasploit/framework/password_crackers/cracker.rb', line 531

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:



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
516
517
518
519
520
521
522
523
524
525
526
# File 'lib/metasploit/framework/password_crackers/cracker.rb', line 446

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

  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



538
539
540
# File 'lib/metasploit/framework/password_crackers/cracker.rb', line 538

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:



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
429
430
431
432
433
434
435
436
437
438
439
# File 'lib/metasploit/framework/password_crackers/cracker.rb', line 393

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



370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
# File 'lib/metasploit/framework/password_crackers/cracker.rb', line 370

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



545
546
547
# File 'lib/metasploit/framework/password_crackers/cracker.rb', line 545

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

Parameters:

  • format (String)

    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
212
213
214
215
216
217
218
219
220
221
222
223
# File 'lib/metasploit/framework/password_crackers/cracker.rb', line 128

def jtr_format_to_hashcat_format(format)
  case format
  # nix
  when 'md5crypt'
    '500'
  when 'descrypt'
    '1500'
  when 'bsdicrypt'
    '12400'
  when 'sha256crypt'
    '7400'
  when 'sha512crypt'
    '1800'
  when 'bcrypt'
    '3200'
  # windows
  when 'lm', 'lanman'
    '3000'
  when 'nt', 'ntlm'
    '1000'
  when 'mscash'
    '1100'
  when 'mscash2'
    '2100'
  when 'netntlm'
    '5500'
  when 'netntlmv2'
    '5600'
  # dbs
  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'
  # osx
  when 'xsha' # osx 10.4-6
    '122'
  when 'xsha512' # osx 10.7
    '1722'
  # webapps
  when 'PBKDF2-HMAC-SHA1' # Atlassian
    '12001'
  when 'phpass' # Wordpress/PHPass, Joomla, phpBB3
    '400'
  when 'mediawiki' # mediawiki b type
    '3711'
  # mobile
  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 'Raw-MD5u'
    '30'
  when 'pbkdf2-sha256'
    '10900'
  end
end

#mode_incrementalObject

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



226
227
228
229
230
231
232
233
234
235
236
237
238
# File 'lib/metasploit/framework/password_crackers/cracker.rb', line 226

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



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

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



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

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

Parameters:

  • file (String)

    A file location of the wordlist to use



284
285
286
287
288
289
290
291
292
# File 'lib/metasploit/framework/password_crackers/cracker.rb', line 284

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

Parameters:

  • file (String)

    A file location of the wordlist to use



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

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



561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
# File 'lib/metasploit/framework/password_crackers/cracker.rb', line 561

def show_command
  cmd_string = binary_path

  pot_file = pot || john_pot_file
  if cracker == 'hashcat'
    cmd = [cmd_string, '--show', '--username', "--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