Files
metasploit-gs/lib/metasploit/framework/password_crackers/cracker.rb
T

536 lines
18 KiB
Ruby
Raw Normal View History

2019-04-04 20:50:52 -04:00
module Metasploit
module Framework
module PasswordCracker
class PasswordCrackerNotFoundError < StandardError
end
class Cracker
include ActiveModel::Validations
# @!attribute attack
# @return [String] The attack mode for hashcat to use (not applicable to John)
attr_accessor :attack
# @!attribute config
# @return [String] The path to an optional config file for John to use
attr_accessor :config
# @!attribute cracker
# @return [String] Which cracker to use. 'john' and 'hashcat' are valid
attr_accessor :cracker
# @!attribute cracker_path
# 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.
#
# @return [String] The file path to an alternative cracker binary to use
attr_accessor :cracker_path
# @!attribute format
# If the cracker type is john, this format will automatically be translated
# to the hashcat equivalent via jtr_format_to_hashcat_format
#
# @return [String] The hash format to try.
attr_accessor :format
# @!attribute fork
# If the cracker type is john, the amount of forks to specify
#
# @return [String] The hash format to try.
attr_accessor :fork
# @!attribute hash_path
# @return [String] The path to the file containing the hashes
attr_accessor :hash_path
# @!attribute incremental
# @return [String] The incremental mode to use
attr_accessor :incremental
2019-11-09 15:55:53 -05:00
# @!attribute increment_length
# @return [Array] The incremental min and max to use
attr_accessor :increment_length
# @!attribute mask
2020-01-04 13:51:56 -05:00
# If the cracker type is hashcat, If set, the mask to use. Should consist of the character sets
2019-11-09 15:55:53 -05:00
# pre-defined by hashcat, such as ?d ?s ?l etc
#
# @return [String] The mask to use
attr_accessor :mask
2019-04-04 20:50:52 -04:00
# @!attribute max_runtime
# @return [Integer] An optional maximum duration of the cracking attempt in seconds
attr_accessor :max_runtime
# @!attribute max_length
# @return [Integer] An optional maximum length of password to attempt cracking
attr_accessor :max_length
2020-01-04 13:38:48 -05:00
# @!attribute optimize
# @return [Boolean] If the Optimize flag should be given to Hashcat
attr_accessor :optimize
2019-04-04 20:50:52 -04:00
# @!attribute pot
# @return [String] The file path to an alternative John pot file to use
attr_accessor :pot
# @!attribute rules
# @return [String] The wordlist mangling rules to use inside John/Hashcat
attr_accessor :rules
# @!attribute wordlist
# @return [String] The file path to the wordlist to use
attr_accessor :wordlist
validates :config, :'Metasploit::Framework::File_path' => true, if: 'config.present?'
2020-01-17 20:37:12 -06:00
validates :cracker, inclusion: {in: %w[john hashcat]}
2019-04-04 20:50:52 -04:00
validates :cracker_path, :'Metasploit::Framework::Executable_path' => true, if: 'cracker_path.present?'
validates :fork,
numericality: {
only_integer: true,
greater_than_or_equal_to: 1
}, if: 'fork.present?'
validates :hash_path, :'Metasploit::Framework::File_path' => true, if: 'hash_path.present?'
validates :pot, :'Metasploit::Framework::File_path' => true, if: 'pot.present?'
validates :max_runtime,
numericality: {
only_integer: true,
greater_than_or_equal_to: 0
}, if: 'max_runtime.present?'
validates :max_length,
numericality: {
only_integer: true,
greater_than_or_equal_to: 0
}, if: 'max_length.present?'
validates :wordlist, :'Metasploit::Framework::File_path' => true, if: 'wordlist.present?'
# @param attributes [Hash{Symbol => String,nil}]
def initialize(attributes={})
attributes.each do |attribute, value|
public_send("#{attribute}=", value)
end
end
# This method takes a {framework.db.cred.private.jtr_format} (string), and
# returns the string number associated to the hashcat format
#
# @param[String] a jtr_format string
# @return [String] the format number for Hashcat
def jtr_format_to_hashcat_format(format)
case format
when 'md5crypt'
2019-07-15 19:57:39 -04:00
'500'
2019-04-04 20:50:52 -04:00
when 'descrypt'
2019-07-15 19:57:39 -04:00
'1500'
2019-04-04 20:50:52 -04:00
when 'bsdicrypt'
2019-07-15 19:57:39 -04:00
'12400'
2019-04-04 20:50:52 -04:00
when 'sha256crypt'
2019-07-15 19:57:39 -04:00
'7400'
2019-04-04 20:50:52 -04:00
when 'sha512crypt'
2019-07-15 19:57:39 -04:00
'1800'
2019-04-04 20:50:52 -04:00
when 'bcrypt'
2019-07-15 19:57:39 -04:00
'3200'
2019-04-04 20:50:52 -04:00
when 'lm', 'lanman'
2019-07-15 19:57:39 -04:00
'3000'
2019-04-04 20:50:52 -04:00
when 'nt', 'ntlm'
2019-07-15 19:57:39 -04:00
'1000'
2019-04-04 20:50:52 -04:00
when 'mssql'
2019-07-15 19:57:39 -04:00
'131'
2019-04-04 20:50:52 -04:00
when 'mssql05'
2019-07-15 19:57:39 -04:00
'132'
2019-04-04 20:50:52 -04:00
when 'mssql12'
2019-07-15 19:57:39 -04:00
'1731'
2019-04-04 20:50:52 -04:00
# 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'
2019-07-15 19:57:39 -04:00
'112'
2019-04-04 20:50:52 -04:00
when 'oracle12c', 'pbkdf2,oracle12c'
2019-07-15 19:57:39 -04:00
'12300'
2019-04-04 20:50:52 -04:00
when 'postgres', 'dynamic_1034', 'raw-md5,postgres'
2019-07-15 19:57:39 -04:00
'12'
2019-04-04 20:50:52 -04:00
when 'mysql'
2019-07-15 19:57:39 -04:00
'200'
2019-04-04 20:50:52 -04:00
when 'mysql-sha1'
2019-07-15 19:57:39 -04:00
'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'
2019-05-31 19:11:01 -04:00
when 'mediawiki' # mediawiki b type
2019-07-15 19:57:39 -04:00
'3711'
2019-11-13 21:48:25 -05:00
when 'android-samsung-sha1'
2019-11-09 15:55:53 -05:00
'5800'
2019-11-13 21:48:25 -05:00
when 'android-sha1'
'110'
when 'android-md5'
'10'
2019-07-15 19:57:39 -04:00
else
nil
2019-04-04 20:50:52 -04:00
end
end
2019-05-31 17:13:34 -04:00
# This method sets the appropriate parameters to run a cracker in incremental mode
def mode_incremental
2019-11-09 15:55:53 -05:00
self.increment_length = nil
self.wordlist = nil
self.mask = nil
self.max_runtime = nil
2019-05-31 17:13:34 -04:00
if cracker == 'john'
self.rules = nil
self.incremental = 'Digits'
elsif cracker == 'hashcat'
self.attack = '3'
self.incremental = true
end
end
# This method sets the appropriate parameters to run a cracker in wordlist mode
#
# @param[String] a file location of the wordlist to use
def mode_wordlist(file)
2019-11-09 15:55:53 -05:00
self.increment_length = nil
self.incremental = nil
self.max_runtime = nil
self.mask = nil
2019-05-31 17:13:34 -04:00
if cracker == 'john'
self.wordlist = file
self.rules = 'wordlist'
elsif cracker == 'hashcat'
self.wordlist = file
self.attack = '0'
2019-11-09 15:55:53 -05:00
end
end
# This method sets the appropriate parameters to run a cracker in a pin mode (4-8 digits) on hashcat
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
2019-05-31 17:13:34 -04:00
end
end
# This method sets the john to 'normal' mode
def mode_normal
if cracker == 'john'
2019-11-09 15:55:53 -05:00
self.max_runtime = nil
self.mask = nil
2019-05-31 17:13:34 -04:00
self.wordlist = nil
self.rules = nil
self.incremental = nil
2019-11-09 15:55:53 -05:00
self.increment_length = nil
2019-05-31 17:13:34 -04:00
end
end
# This method sets the john to single mode
#
# @param[String] a file location of the wordlist to use
def mode_single(file)
if cracker == 'john'
self.wordlist = file
self.rules = 'single'
self.incremental = nil
2019-11-09 15:55:53 -05:00
self.increment_length = nil
self.mask = nil
2019-05-31 17:13:34 -04:00
end
end
2019-04-04 20:50:52 -04:00
# This method follows a decision tree to determine the path
# to the cracker binary we should use.
#
# @return [NilClass] if a binary path could not be found
# @return [String] the path to the selected JtR binary
def binary_path
# Always prefer a manually entered path
if cracker_path && ::File.file?(cracker_path)
2019-07-15 19:57:39 -04:00
return cracker_path
2019-04-04 20:50:52 -04:00
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)
2019-07-15 19:57:39 -04:00
return path
2019-04-04 20:50:52 -04:00
end
2019-07-15 19:57:39 -04:00
raise PasswordCrackerNotFoundError, 'No suitable john/hashcat binary was found on the system'
2019-04-04 20:50:52 -04:00
end
end
# This method runs the command from {#crack_command} and yields each line of output.
#
# @yield [String] a line of output from the cracker command
# @return [void]
def crack
if cracker == 'john'
results = john_crack_command
elsif cracker == 'hashcat'
results = hashcat_crack_command
end
::IO.popen(results, "rb") do |fd|
fd.each_line do |line|
yield line
end
end
end
# This method returns the version of John the Ripper or Hashcat being used.
#
# @raise [PasswordCrackerNotFoundError] if a suitable cracker binary was never found
# @return [Sring] the version detected
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 $1.strip if line =~ /John the Ripper(?: password cracker, version)? ([^\[]+)/
2019-04-04 20:50:52 -04:00
elsif cracker == 'hashcat'
# v5.1.0
return $1 if line =~ /(v[\d\.]+)/
end
end
end
nil
end
# 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.
#
# @raise [PasswordCrackerNotFoundError] if a suitable John binary was never found
# @return [Array] An array set up for {::IO.popen} to use
def john_crack_command
cmd_string = binary_path
2020-01-18 14:28:10 -05:00
cmd = [cmd_string, '--session=' + cracker_session_id, '--nolog']
2019-04-04 20:50:52 -04:00
if config.present?
2020-01-18 14:28:10 -05:00
cmd << ("--config=" + config)
2019-04-04 20:50:52 -04:00
else
2020-01-18 14:28:10 -05:00
cmd << ("--config=" + john_config_file)
2019-04-04 20:50:52 -04:00
end
if pot.present?
2020-01-18 14:28:10 -05:00
cmd << ("--pot=" + pot)
2019-04-04 20:50:52 -04:00
else
2020-01-18 14:28:10 -05:00
cmd << ("--pot=" + john_pot_file)
2019-04-04 20:50:52 -04:00
end
if fork.present? && fork > 1
2020-01-18 14:28:10 -05:00
cmd << ("--fork=" + fork.to_s)
2019-04-04 20:50:52 -04:00
end
if format.present?
2020-01-18 14:28:10 -05:00
cmd << ("--format=" + format)
2019-04-04 20:50:52 -04:00
end
if wordlist.present?
2020-01-18 14:28:10 -05:00
cmd << ("--wordlist=" + wordlist)
2019-04-04 20:50:52 -04:00
end
if incremental.present?
2020-01-18 14:28:10 -05:00
cmd << ("--incremental=" + incremental)
2019-04-04 20:50:52 -04:00
end
if rules.present?
2020-01-18 14:28:10 -05:00
cmd << ("--rules=" + rules)
2019-04-04 20:50:52 -04:00
end
if max_runtime.present?
2020-01-18 14:28:10 -05:00
cmd << ("--max-run-time=" + max_runtime.to_s)
2019-04-04 20:50:52 -04:00
end
if max_length.present?
2020-01-18 14:28:10 -05:00
cmd << ("--max-len=" + max_length.to_s)
2019-04-04 20:50:52 -04:00
end
cmd << hash_path
end
# 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.
#
# @raise [PasswordCrackerNotFoundError] if a suitable Hashcat binary was never found
# @return [Array] An array set up for {::IO.popen} to use
def hashcat_crack_command
cmd_string = binary_path
2020-01-18 14:28:10 -05:00
cmd = [cmd_string, '--session=' + cracker_session_id, '--logfile-disable']
2019-04-04 20:50:52 -04:00
if pot.present?
2020-01-18 14:28:10 -05:00
cmd << ("--potfile-path=" + pot)
2019-04-04 20:50:52 -04:00
else
2020-01-18 14:28:10 -05:00
cmd << ("--potfile-path=" + john_pot_file)
2019-04-04 20:50:52 -04:00
end
if format.present?
2020-01-18 14:28:10 -05:00
cmd << ("--hash-type=" + jtr_format_to_hashcat_format(format))
2019-04-04 20:50:52 -04:00
end
2020-01-04 13:38:48 -05:00
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)
2020-01-18 14:28:10 -05:00
cmd << ("-O")
2020-01-04 13:38:48 -05:00
end
2019-04-04 20:50:52 -04:00
if incremental.present?
2020-01-18 14:28:10 -05:00
cmd << ("--increment")
2019-11-09 15:55:53 -05:00
if increment_length.present?
2020-01-18 14:28:10 -05:00
cmd << ("--increment-min=" + increment_length[0].to_s)
cmd << ("--increment-max=" + increment_length[1].to_s)
2019-11-09 15:55:53 -05:00
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
2020-01-18 14:28:10 -05:00
cmd << ("--increment-max=4")
2019-11-09 15:55:53 -05:00
end
2019-04-04 20:50:52 -04:00
end
if rules.present?
2020-01-18 14:28:10 -05:00
cmd << ("--rules-file=" + rules)
2019-04-04 20:50:52 -04:00
end
if attack.present?
2020-01-18 14:28:10 -05:00
cmd << ("--attack-mode=" + attack)
2019-04-04 20:50:52 -04:00
end
if max_runtime.present?
2020-01-18 14:28:10 -05:00
cmd << ("--runtime=" + max_runtime.to_s)
2019-04-04 20:50:52 -04:00
end
cmd << hash_path
2019-11-09 15:55:53 -05:00
if mask.present?
cmd << mask.to_s
end
2019-04-04 20:50:52 -04:00
# must be last
if wordlist.present?
2020-01-18 14:28:10 -05:00
cmd << (wordlist)
2019-04-04 20:50:52 -04:00
end
cmd
end
# This runs the show command in john and yields cracked passwords.
#
# @return [Array] the output from teh command split on newlines
def each_cracked_password
::IO.popen(show_command, "rb").readlines
end
# This method returns the path to a default john.conf file.
#
# @return [String] the path to the default john.conf file
def john_config_file
2020-01-18 14:28:10 -05:00
::File.join(::Msf::Config.data_directory, "jtr", "john.conf")
2019-04-04 20:50:52 -04:00
end
# This method returns the path to a default john.pot file.
#
# @return [String] the path to the default john.pot file
def john_pot_file
2020-01-18 14:28:10 -05:00
::File.join(::Msf::Config.config_directory, "john.pot")
2019-04-04 20:50:52 -04:00
end
# 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
def cracker_session_id
@session_id ||= ::Rex::Text.rand_text_alphanumeric(8)
end
# This method builds the command to show the cracked passwords.
#
# @raise [JohnNotFoundError] if a suitable John binary was never found
# @return [Array] An array set up for {::IO.popen} to use
def show_command
cmd_string = binary_path
pot_file = pot || john_pot_file
if cracker=='hashcat'
2020-01-18 14:28:10 -05:00
cmd = [cmd_string, "--show", "--potfile-path=#{pot_file}", "--hash-type=#{jtr_format_to_hashcat_format(format)}"]
2019-04-04 20:50:52 -04:00
elsif cracker=='john'
2020-01-18 14:28:10 -05:00
cmd = [cmd_string, "--show", "--pot=#{pot_file}", "--format=#{format}"]
2019-04-04 20:50:52 -04:00
if config
cmd << "--config=#{config}"
else
2020-01-18 14:28:10 -05:00
cmd << ("--config=" + john_config_file)
2019-04-04 20:50:52 -04:00
end
end
cmd << hash_path
end
private
end
end
end
end