Path: blob/master/lib/metasploit/framework/password_crackers/cracker.rb
21545 views
module Metasploit1module Framework2module PasswordCracker3class PasswordCrackerNotFoundError < StandardError4end56class Cracker7include ActiveModel::Validations89# @!attribute attack10# @return [String] The attack mode for hashcat to use (not applicable to John)11attr_accessor :attack1213# @!attribute config14# @return [String] The path to an optional config file for John to use15attr_accessor :config1617# @!attribute cracker18# @return [String] Which cracker to use. 'john' and 'hashcat' are valid19attr_accessor :cracker2021# @!attribute cracker_path22# This attribute allows the user to specify a cracker binary to use.23# If not supplied, the Cracker will search the PATH for a suitable john or hashcat binary24# and finally fall back to the pre-compiled john versions shipped with Metasploit.25#26# @return [String] The file path to an alternative cracker binary to use27attr_accessor :cracker_path2829# @!attribute format30# If the cracker type is john, this format will automatically be translated31# to the hashcat equivalent via jtr_format_to_hashcat_format32#33# @return [String] The hash format to try.34attr_accessor :format3536# @!attribute fork37# If the cracker type is john, the amount of forks to specify38#39# @return [String] The hash format to try.40attr_accessor :fork4142# @!attribute hash_path43# @return [String] The path to the file containing the hashes44attr_accessor :hash_path4546# @!attribute incremental47# @return [String] The incremental mode to use48attr_accessor :incremental4950# @!attribute increment_length51# @return [Array] The incremental min and max to use52attr_accessor :increment_length5354# @!attribute mask55# If the cracker type is hashcat, If set, the mask to use. Should consist of the character sets56# pre-defined by hashcat, such as ?d ?s ?l etc57#58# @return [String] The mask to use59attr_accessor :mask6061# @!attribute max_runtime62# @return [Integer] An optional maximum duration of the cracking attempt in seconds63attr_accessor :max_runtime6465# @!attribute max_length66# @return [Integer] An optional maximum length of password to attempt cracking67attr_accessor :max_length6869# @!attribute optimize70# @return [Boolean] If the Optimize flag should be given to Hashcat71attr_accessor :optimize7273# @!attribute pot74# @return [String] The file path to an alternative John pot file to use75attr_accessor :pot7677# @!attribute rules78# @return [String] The wordlist mangling rules to use inside John/Hashcat79attr_accessor :rules8081# @!attribute wordlist82# @return [String] The file path to the wordlist to use83attr_accessor :wordlist8485validates :config, 'Metasploit::Framework::File_path': true, if: -> { config.present? }8687validates :cracker, inclusion: { in: %w[john hashcat] }8889validates :cracker_path, 'Metasploit::Framework::Executable_path': true, if: -> { cracker_path.present? }9091validates :fork,92numericality: {93only_integer: true,94greater_than_or_equal_to: 195}, if: -> { fork.present? }9697validates :hash_path, 'Metasploit::Framework::File_path': true, if: -> { hash_path.present? }9899validates :pot, 'Metasploit::Framework::File_path': true, if: -> { pot.present? }100101validates :max_runtime,102numericality: {103only_integer: true,104greater_than_or_equal_to: 0105}, if: -> { max_runtime.present? }106107validates :max_length,108numericality: {109only_integer: true,110greater_than_or_equal_to: 0111}, if: -> { max_length.present? }112113validates :wordlist, 'Metasploit::Framework::File_path': true, if: -> { wordlist.present? }114115# @param attributes [Hash{Symbol => String,nil}]116def initialize(attributes = {})117attributes.each do |attribute, value|118public_send("#{attribute}=", value)119end120end121122def get_type123self.cracker124end125126# This method takes a {framework.db.cred.private.jtr_format} (string), and127# returns the string number associated to the hashcat format128#129# @param format [String] A jtr_format string130# @return [String] The format number for Hashcat131def jtr_format_to_hashcat_format(format)132case format133# nix134when 'md5crypt'135'500'136when 'descrypt'137'1500'138when 'bsdicrypt'139'12400'140when 'sha256crypt'141'7400'142when 'sha512crypt'143'1800'144when 'bcrypt'145'3200'146# windows147when 'lm', 'lanman'148'3000'149when 'nt', 'ntlm'150'1000'151when 'mscash'152'1100'153when 'mscash2'154'2100'155when 'netntlm'156'5500'157when 'netntlmv2'158'5600'159# dbs160when 'mssql'161'131'162when 'mssql05'163'132'164when 'mssql12'165'1731'166# hashcat requires a format we dont have all the data for167# in the current dumper, so this is disabled in module and lib168# when 'oracle', 'des,oracle'169# return '3100'170when 'oracle11', 'raw-sha1,oracle'171'112'172when 'oracle12c', 'pbkdf2,oracle12c'173'12300'174when 'postgres', 'dynamic_1034', 'raw-md5,postgres'175'12'176when 'mysql'177'200'178when 'mysql-sha1'179'300'180when 'PBKDF2-HMAC-SHA512' # osx 10.8+181'7100'182# osx183when 'xsha' # osx 10.4-6184'122'185when 'xsha512' # osx 10.7186'1722'187# webapps188when 'PBKDF2-HMAC-SHA1' # Atlassian189'12001'190when 'phpass' # Wordpress/PHPass, Joomla, phpBB3191'400'192when 'mediawiki' # mediawiki b type193'3711'194# mobile195when 'android-samsung-sha1'196'5800'197when 'android-sha1'198'110'199when 'android-md5'200'10'201when 'hmac-md5'202'10200'203when 'dynamic_82'204'1710'205when 'ssha'206'111'207when 'raw-sha512'208'1700'209when 'raw-sha256'210'1400'211when 'raw-sha1'212'100'213when 'raw-md5'214'0'215when 'smd5'216'6300'217when 'ssha256'218'1411'219when 'ssha512'220'1711'221when 'Raw-MD5u'222'30'223when 'pbkdf2-sha256'224'10900'225end226end227228# This method sets the appropriate parameters to run a cracker in incremental mode229def mode_incremental230self.increment_length = nil231self.wordlist = nil232self.mask = nil233self.max_runtime = nil234if cracker == 'john'235self.rules = nil236self.incremental = 'Digits'237elsif cracker == 'hashcat'238self.attack = '3'239self.incremental = true240end241end242243# This method sets the appropriate parameters to run a cracker in wordlist mode244#245# @param file [String] A file location of the wordlist to use246def mode_wordlist(file)247self.increment_length = nil248self.incremental = nil249self.max_runtime = nil250self.mask = nil251if cracker == 'john'252self.wordlist = file253self.rules = 'wordlist'254elsif cracker == 'hashcat'255self.wordlist = file256self.attack = '0'257end258end259260# This method sets the appropriate parameters to run a cracker in a pin mode (4-8 digits) on hashcat261def mode_pin262self.rules = nil263if cracker == 'hashcat'264self.attack = '3'265self.mask = '?d' * 8266self.incremental = true267self.increment_length = [4, 8]268self.max_runtime = 300 # 5min on an i7 got through 4-7 digits. 8digit was 32min more269end270end271272# This method sets the john to 'normal' mode273def mode_normal274if cracker == 'john'275self.max_runtime = nil276self.mask = nil277self.wordlist = nil278self.rules = nil279self.incremental = nil280self.increment_length = nil281end282end283284# This method sets the john to single mode285#286# @param file [String] A file location of the wordlist to use287def mode_single(file)288if cracker == 'john'289self.wordlist = file290self.rules = 'single'291self.incremental = nil292self.increment_length = nil293self.mask = nil294end295end296297# This method follows a decision tree to determine the path298# to the cracker binary we should use.299#300# @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.301def binary_path302# Always prefer a manually entered path303if cracker_path && ::File.file?(cracker_path)304return cracker_path305else306case cracker307when 'hashcat'308path = get_hashcat309when 'john'310path = get_john311when 'auto'312path = get_john || get_hashcat313else314raise PasswordCrackerNotFoundError, 'No suitable Cracker was selected, so a binary could not be found on the system JOHN || HASHCAT'315end316raise PasswordCrackerNotFoundError, 'No suitable john/hashcat binary was found on the system' unless path && ::File.file?(path)317318return path319end320end321322# This method runs the command from {#crack_command} and yields each line of output.323#324# @yield [String] a line of output from the cracker command325# @return [void]326def crack(&block)327if cracker == 'john'328results = john_crack_command329elsif cracker == 'hashcat'330results = hashcat_crack_command331end332::IO.popen(results, 'rb') do |fd|333fd.each_line(&block)334end335end336337# This method returns the version of John the Ripper or Hashcat being used.338#339# @raise [PasswordCrackerNotFoundError] if a suitable cracker binary was never found340# @return [String] the version detected341def cracker_version342if cracker == 'john'343cmd = binary_path344elsif cracker == 'hashcat'345cmd = binary_path346cmd << (' -V')347end348::IO.popen(cmd, 'rb') do |fd|349fd.each_line do |line|350if cracker == 'john'351# 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]352# John the Ripper 1.9.0-jumbo-1 OMP [linux-gnu 64-bit x86_64 AVX2 AC]353# John the Ripper password cracker, version 1.8.0.2-bleeding-jumbo_omp [64-bit AVX-autoconf]354# John the Ripper password cracker, version 1.8.0355return Regexp.last_match(1).strip if line =~ /John the Ripper(?: password cracker, version)? ([^\[]+)/356elsif cracker == 'hashcat'357# v5.1.0358return Regexp.last_match(1) if line =~ /(v[\d.]+)/359end360end361end362nil363end364365# This method is used to determine which format of the no log option should be used366# --no-log vs --nolog https://github.com/openwall/john/commit/8982e4f7a2e874aab29807a05b421373015c9b61367# We base this either on a date being in the version, or running the command and checking the output368#369# @return [String] The nolog format to use370def john_nolog_format371if /(\d{4}-\d{2}-\d{2})/ =~ cracker_version372# we lucked out and theres a date, we'll check its older than the commit that changed the nolog373if Date.parse(Regexp.last_match(1)) < Date.parse('2020-11-27')374return '--nolog'375end376377return '--no-log'378end379380# no date, so lets give it a run with the old format and check if we raise an error381# on *nix 'unknown option' goes to stderr382::IO.popen([binary_path, '--nolog', { err: %i[child out] }], 'rb') do |fd|383return '--nolog' unless fd.read.include? 'Unknown option'384end385'--no-log'386end387388# This method builds an array for the command to actually run the cracker.389# It builds the command from all of the attributes on the class.390#391# @raise [PasswordCrackerNotFoundError] if a suitable John binary was never found392# @return [Array] An array set up for {::IO.popen} to use393def john_crack_command394cmd_string = binary_path395396cmd = [cmd_string, '--session=' + cracker_session_id, john_nolog_format]397398if config.present?399cmd << ('--config=' + config)400else401cmd << ('--config=' + john_config_file)402end403404if pot.present?405cmd << ('--pot=' + pot)406else407cmd << ('--pot=' + john_pot_file)408end409410if fork.present? && fork > 1411cmd << ('--fork=' + fork.to_s)412end413414if format.present?415cmd << ('--format=' + format)416end417418if wordlist.present?419cmd << ('--wordlist=' + wordlist)420end421422if incremental.present?423cmd << ('--incremental=' + incremental)424end425426if rules.present?427cmd << ('--rules=' + rules)428end429430if max_runtime.present?431cmd << ('--max-run-time=' + max_runtime.to_s)432end433434if max_length.present?435cmd << ('--max-len=' + max_length.to_s)436end437438cmd << hash_path439end440441# This method builds an array for the command to actually run the cracker.442# It builds the command from all of the attributes on the class.443#444# @raise [PasswordCrackerNotFoundError] if a suitable Hashcat binary was never found445# @return [Array] An array set up for {::IO.popen} to use446def hashcat_crack_command447cmd_string = binary_path448cmd = [cmd_string, '--session=' + cracker_session_id, '--logfile-disable', '--quiet', '--username']449450if pot.present?451cmd << ('--potfile-path=' + pot)452else453cmd << ('--potfile-path=' + john_pot_file)454end455456if format.present?457cmd << ('--hash-type=' + jtr_format_to_hashcat_format(format))458end459460if optimize.present?461# https://hashcat.net/wiki/doku.php?id=frequently_asked_questions#what_is_the_maximum_supported_password_length_for_optimized_kernels462# Optimized Kernels has a large impact on speed. Here are some stats from Hashcat 5.1.0:463464# Kali Linux on Dell Precision M3800465## hashcat -b -w 2 -m 0466# * Device #1: Quadro K1100M, 500/2002 MB allocatable, 2MCU467# Speed.#1.........: 185.9 MH/s (11.15ms) @ Accel:64 Loops:16 Thr:1024 Vec:1468469## hashcat -b -w 2 -O -m 0470# * Device #1: Quadro K1100M, 500/2002 MB allocatable, 2MCU471# Speed.#1.........: 463.6 MH/s (8.92ms) @ Accel:64 Loops:32 Thr:1024 Vec:1472473# Windows 10474# PS C:\hashcat-5.1.0> .\hashcat64.exe -b -O -w 2 -m 0475# * Device #1: GeForce RTX 2070 SUPER, 2048/8192 MB allocatable, 40MCU476# Speed.#1.........: 13914.0 MH/s (5.77ms) @ Accel:128 Loops:64 Thr:256 Vec:1477478# PS C:\hashcat-5.1.0> .\hashcat64.exe -b -O -w 2 -m 0479# * Device #1: GeForce RTX 2070 SUPER, 2048/8192 MB allocatable, 40MCU480# Speed.#1.........: 31545.6 MH/s (10.36ms) @ Accel:256 Loops:128 Thr:256 Vec:1481482# This change should result in 225%-250% speed boost at the sacrifice of some password length, which most likely483# wouldn't be tested inside of MSF since most users are using the MSF modules for word list and easy cracks.484# Anything of length where this would cut off is most likely being done independently (outside MSF)485486cmd << ('-O')487end488489if incremental.present?490cmd << ('--increment')491if increment_length.present?492cmd << ('--increment-min=' + increment_length[0].to_s)493cmd << ('--increment-max=' + increment_length[1].to_s)494else495# anything more than max 4 on even des took 8+min on an i7.496# maybe in the future this can be adjusted or made a variable497# but current time, we'll leave it as this seems like reasonable498# time expectation for a module to run499cmd << ('--increment-max=4')500end501end502503if rules.present?504cmd << ('--rules-file=' + rules)505end506507if attack.present?508cmd << ('--attack-mode=' + attack)509end510511if max_runtime.present?512cmd << ('--runtime=' + max_runtime.to_s)513end514515cmd << hash_path516517if mask.present?518cmd << mask.to_s519end520521# must be last522if wordlist.present?523cmd << (wordlist)524end525cmd526end527528# This runs the show command in john and yields cracked passwords.529#530# @return [Array] the output from the command split on newlines531def each_cracked_password532::IO.popen(show_command, 'rb').readlines533end534535# This method returns the path to a default john.conf file.536#537# @return [String] the path to the default john.conf file538def john_config_file539::File.join(::Msf::Config.data_directory, 'jtr', 'john.conf')540end541542# This method returns the path to a default john.pot file.543#544# @return [String] the path to the default john.pot file545def john_pot_file546::File.join(::Msf::Config.config_directory, 'john.pot')547end548549# This method is a getter for a random Session ID for the cracker.550# It allows us to dinstiguish between cracking sessions.551#552# @ return [String] the Session ID to use553def cracker_session_id554@session_id ||= ::Rex::Text.rand_text_alphanumeric(8)555end556557# This method builds the command to show the cracked passwords.558#559# @raise [JohnNotFoundError] if a suitable John binary was never found560# @return [Array] An array set up for {::IO.popen} to use561def show_command562cmd_string = binary_path563564pot_file = pot || john_pot_file565if cracker == 'hashcat'566cmd = [cmd_string, '--show', '--username', "--potfile-path=#{pot_file}", "--hash-type=#{jtr_format_to_hashcat_format(format)}"]567elsif cracker == 'john'568cmd = [cmd_string, '--show', "--pot=#{pot_file}", "--format=#{format}"]569570if config571cmd << "--config=#{config}"572else573cmd << ('--config=' + john_config_file)574end575end576cmd << hash_path577end578579def get_hashcat580# Look in the Environment PATH for the hashcat binary581self.cracker = 'hashcat'582Rex::FileUtils.find_full_path('hashcat') ||583Rex::FileUtils.find_full_path('hashcat.exe')584end585586def get_john587self.cracker = 'john'588# Look in the Environment PATH for the john binary589Rex::FileUtils.find_full_path('john') ||590Rex::FileUtils.find_full_path('john.exe')591end592593end594end595end596end597598599