Files
metasploit-gs/lib/metasploit/framework/password_crackers/cracker.rb
T
2020-01-18 14:28:10 -05:00

536 lines
18 KiB
Ruby

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
# @!attribute increment_length
# @return [Array] The incremental min and max to use
attr_accessor :increment_length
# @!attribute mask
# 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
attr_accessor :mask
# @!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
# @!attribute optimize
# @return [Boolean] If the Optimize flag should be given to Hashcat
attr_accessor :optimize
# @!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?'
validates :cracker, inclusion: {in: %w[john hashcat]}
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'
'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'
else
nil
end
end
# This method sets the appropriate parameters to run a cracker in incremental mode
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
# 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)
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
# 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
end
end
# This method sets the john to 'normal' mode
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
# 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
self.increment_length = nil
self.mask = nil
end
end
# 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)
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
# 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)? ([^\[]+)/
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
cmd = [cmd_string, '--session=' + cracker_session_id, '--nolog']
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
# 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
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
# 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
::File.join(::Msf::Config.data_directory, "jtr", "john.conf")
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
::File.join(::Msf::Config.config_directory, "john.pot")
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'
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
private
end
end
end
end