Module: Msf::Auxiliary::PasswordCracker

Includes:
Report
Defined in:
lib/msf/core/auxiliary/password_cracker.rb

Overview

This module provides methods for working with a Password Cracker

Instance Method Summary collapse

Methods included from Report

#active_db?, #create_cracked_credential, #create_credential, #create_credential_and_login, #create_credential_login, #db, #db_warning_given?, #get_client, #get_host, #inside_workspace_boundary?, #invalidate_login, #mytask, #myworkspace, #myworkspace_id, #report_auth_info, #report_client, #report_exploit, #report_host, #report_loot, #report_note, #report_service, #report_vuln, #report_web_form, #report_web_page, #report_web_site, #report_web_vuln, #store_cred, #store_local, #store_loot

Methods included from Metasploit::Framework::Require

optionally, optionally_active_record_railtie, optionally_include_metasploit_credential_creation, #optionally_include_metasploit_credential_creation, optionally_require_metasploit_db_gem_engines

Instance Method Details

#append_results(tbl, cracked_hashes) ⇒ String

This method appends a list of cracked hashes to the list used to generate the printed table

Parameters:

  • tbl (Array)

    Array of all results that have been cracked

  • cracked_hashes (Array)

    Array of results to add to the table

Returns:

  • (String)

    the table in string format for printing



237
238
239
240
241
242
243
244
# File 'lib/msf/core/auxiliary/password_cracker.rb', line 237

def append_results(tbl, cracked_hashes)
  cracked_hashes.each do |row|
    next if tbl.rows.include? row

    tbl << row
  end
  tbl.to_s
end

#cracker_results_tableRex::Text::Table

This method returns a cracker results table

Returns:

  • (Rex::Text::Table)

    table for printing results



249
250
251
252
253
254
255
# File 'lib/msf/core/auxiliary/password_cracker.rb', line 249

def cracker_results_table
  Rex::Text::Table.new(
    'Header' => 'Cracked Hashes',
    'Indent' => 1,
    'Columns' => ['DB ID', 'Hash Type', 'Username', 'Cracked Password', 'Method']
  )
end

#hash_job(jtr_type, cracker) ⇒ Hash

This method creates a job for the password cracker to do. A job is categorized by the hash type and will include the hash type (type), formatted_hashlist (hashes in the cracker’s format), creds (db objects for each hash), and cred_ids_left_to_crack (array of db ids that aren’t cracked yet)

Parameters:

  • jtr_type (String)

    hash type we're cracking such as md5, sha1

  • cracker (String)

    the password cracker to use such as 'john' or 'hashcat'

Returns:

  • (Hash)

    of the data needed to crack as described above



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
# File 'lib/msf/core/auxiliary/password_cracker.rb', line 174

def hash_job(jtr_type, cracker)
  # create the base data
  job = { 'type' => jtr_type, 'formatted_hashlist' => [], 'creds' => [], 'cred_ids_left_to_crack' => [] }
  job['db_formats'] = Metasploit::Framework::PasswordCracker::JtR::Formatter.jtr_to_db(jtr_type)
  if jtr_type == 'dynamic_1034' # postgres
    creds = framework.db.creds(workspace: myworkspace, type: 'Metasploit::Credential::PostgresMD5')
  elsif ['lm', 'nt'].include? jtr_type
    creds = framework.db.creds(workspace: myworkspace, type: 'Metasploit::Credential::NTLMHash')
  else
    creds = framework.db.creds(workspace: myworkspace, type: 'Metasploit::Credential::NonreplayableHash')
  end
  creds.each do |core|
    jtr_format = core.private.jtr_format

    # Unfortunately NTLMHash always set JtR Format to 'nt,lm' so we have to do a special case here
    # to figure out which it is
    if jtr_format == 'nt,lm'
      jtr_format = core.private.data.start_with?('aad3b435b51404eeaad3b435b51404ee') ? 'nt' : 'lm'
    end

    next unless job['db_formats'].include? jtr_format
    # only add hashes which havne't been cracked
    next if password_cracked?(core.private.data)

    job['creds'] << core
    job['cred_ids_left_to_crack'] << core.id
    if cracker == 'john'
      job['formatted_hashlist'] << Metasploit::Framework::PasswordCracker::JtR::Formatter.hash_to_jtr(core)
    elsif cracker == 'hashcat'
      job['formatted_hashlist'] << Metasploit::Framework::PasswordCracker::Hashcat::Formatter.hash_to_hashcat(core)
    end
  end

  if job['creds'].length > 0
    return job
  end

  nil
end

#initialize(info = {}) ⇒ Object

Initializes an instance of an auxiliary module that calls out to John the Ripper (jtr)



23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
# File 'lib/msf/core/auxiliary/password_cracker.rb', line 23

def initialize(info = {})
  super

  register_options(
    [
      OptPath.new('CONFIG', [false, 'The path to a John config file to use instead of the default']),
      OptPath.new('CUSTOM_WORDLIST', [false, 'The path to an optional custom wordlist']),
      OptInt.new('ITERATION_TIMEOUT', [false, 'The max-run-time for each iteration of cracking']),
      OptPath.new('CRACKER_PATH', [false, 'The absolute path to the cracker executable']),
      OptInt.new('FORK', [false, 'Forks for John the Ripper to use', 1]),
      OptBool.new('KORELOGIC', [false, 'Apply the KoreLogic rules to John the Ripper Wordlist Mode(slower)', false]),
      OptBool.new('MUTATE', [false, 'Apply common mutations to the Wordlist (SLOW)', false]),
      OptPath.new('POT', [false, 'The path to a John POT file to use instead of the default']),
      OptBool.new('USE_CREDS', [false, 'Use existing credential data saved in the database', true]),
      OptBool.new('USE_DB_INFO', [false, 'Use looted database schema info to seed the wordlist', true]),
      OptBool.new('USE_DEFAULT_WORDLIST', [false, 'Use the default metasploit wordlist', true]),
      OptBool.new('USE_HOSTNAMES', [false, 'Seed the wordlist with hostnames from the workspace', true]),
      OptBool.new('USE_ROOT_WORDS', [false, 'Use the Common Root Words Wordlist', true])
    ], Msf::Auxiliary::PasswordCracker
  )

  register_advanced_options(
    [
      OptBool.new('DeleteTempFiles', [false, 'Delete temporary wordlist and hash files', true]),
      OptBool.new('OptimizeKernel', [false, 'Utilize Optimized Kernels in Hashcat', true]),
      OptBool.new('ShowCommand', [false, 'Print the cracker command being used', true]),
    ], Msf::Auxiliary::PasswordCracker
  )
end

#john_lm_upper_to_ntlm(pwd, hash) ⇒ String?

Parameters:

  • pwd (String)

    Password recovered from cracking an LM hash

  • hash (String)

    NTLM hash for this password

Returns:

  • (String)

    'pwd` converted to the correct case to match the given NTLM hash

  • (nil)

    if no case matches the NT hash. This can happen when 'pwd` came from a john run that only cracked half of the LM hash



59
60
61
62
63
64
65
66
67
68
# File 'lib/msf/core/auxiliary/password_cracker.rb', line 59

def john_lm_upper_to_ntlm(pwd, hash)
  pwd = pwd.upcase
  hash = hash.upcase
  Rex::Text.permute_case(pwd).each do |str|
    if hash == Rex::Proto::NTLM::Crypt.ntlm_hash(str).unpack('H*')[0].upcase
      return str
    end
  end
  nil
end

#new_password_cracker(cracking_application) ⇒ nilClass, Metasploit::Framework::PasswordCracker::Cracker

This method creates a new Metasploit::Framework::PasswordCracker::Cracker and populates some of the attributes based on the module datastore options.

Returns:



75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
# File 'lib/msf/core/auxiliary/password_cracker.rb', line 75

def new_password_cracker(cracking_application)
  fail_with(Msf::Module::Failure::BadConfig, 'Password cracking is not available without an active database connection.') unless framework.db.active
  cracker = Metasploit::Framework::PasswordCracker::Cracker.new(
    config: datastore['CONFIG'],
    cracker_path: datastore['CRACKER_PATH'],
    max_runtime: datastore['ITERATION_TIMEOUT'],
    pot: datastore['POT'],
    optimize: datastore['OptimizeKernel'],
    wordlist: datastore['CUSTOM_WORDLIST']
  )
  cracker.cracker = resolve_cracking_application(cracking_application, cracker)
  begin
    cracker.binary_path
  rescue Metasploit::Framework::PasswordCracker::PasswordCrackerNotFoundError => e
    fail_with(Msf::Module::Failure::BadConfig, e.message)
  end
  # throw this to a local variable since it causes a shell out to pull the version
  cracker_version = cracker.cracker_version
  if cracker.cracker == 'john' && (cracker_version.nil? || !cracker_version.include?('jumbo'))
    fail_with(Msf::Module::Failure::BadConfig, 'John the Ripper JUMBO patch version required.  See https://github.com/magnumripper/JohnTheRipper')
  end
  print_good("#{cracker.cracker} Version Detected: #{cracker_version}")
  cracker
end

#password_cracked?(hash) ⇒ Boolean

This method determines if a given password hash already been cracked in the database

Parameters:

  • hash (String)

    password hash to check against the database

Returns:

  • (Boolean)

    if the password has been cracked in the db



156
157
158
159
160
161
162
163
164
165
# File 'lib/msf/core/auxiliary/password_cracker.rb', line 156

def password_cracked?(hash)
  framework.db.creds({ pass: hash }).each do |test_cred|
    test_cred.public.cores.each do |core|
      if core.origin_type == 'Metasploit::Credential::Origin::CrackedPassword'
        return true
      end
    end
  end
  false
end

#process_cracker_results(results, cred) ⇒ Array

This method takes a results table, and a newly cracked cred, and adds the cred to the table if it isn’t there already. It also creates the cracked credential in the database.

Parameters:

  • results (Hash)

    Hash of the newly cracked cred information, should have hash_type, method, username core_id, and password fields.

Returns:

  • (Array)

    Array of results for printing in a table



220
221
222
223
224
225
226
227
228
229
230
# File 'lib/msf/core/auxiliary/password_cracker.rb', line 220

def process_cracker_results(results, cred)
  return results if cred['core_id'].nil? # make sure we have good data

  # make sure we dont add the same one again
  if results.select { |r| r.first == cred['core_id'] }.empty?
    results << [cred['core_id'], cred['hash_type'], cred['username'], cred['password'], cred['method']]
  end

  create_cracked_credential(username: cred['username'], password: cred['password'], core_id: cred['core_id'])
  results
end

#resolve_cracking_application(cracking_application, cracker) ⇒ Object



100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
# File 'lib/msf/core/auxiliary/password_cracker.rb', line 100

def resolve_cracking_application(cracking_application, cracker)
  return cracking_application unless cracking_application == 'auto'

  if cracker.cracker_path.present?
    basename = ::File.basename(cracker.cracker_path).downcase
    return 'john' if basename.start_with?('john')
    return 'hashcat' if basename.start_with?('hashcat')

    fail_with(
      Msf::Module::Failure::BadConfig,
      "CRACKER_PATH '#{cracker.cracker_path}' does not look like john or hashcat; set ACTION to 'john' or 'hashcat'."
    )
  end

  %w[john hashcat].each do |candidate|
    cracker.cracker = candidate
    begin
      cracker.binary_path
      return candidate
    rescue Metasploit::Framework::PasswordCracker::PasswordCrackerNotFoundError
      next
    end
  end

  fail_with(
    Msf::Module::Failure::BadConfig,
    'No suitable john/hashcat binary was found on the system. Set CRACKER_PATH or ACTION.'
  )
end

#wordlist_file(max_len = 0) ⇒ nilClass, Rex::Quickfile

This method instantiates a Metasploit::Framework::JtR::Wordlist, writes the data out to a file and returns the Rex::Quickfile object.

Parameters:

  • max_len (Integer) (defaults to: 0)

    max length of a word in the wordlist, 0 default for ignored value

Returns:

  • (nilClass)

    if there is no active framework db connection

  • (Rex::Quickfile)

    if it successfully wrote the wordlist to a file



136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
# File 'lib/msf/core/auxiliary/password_cracker.rb', line 136

def wordlist_file(max_len = 0)
  return nil unless framework.db.active

  wordlist = Metasploit::Framework::PasswordCracker::Wordlist.new(
    custom_wordlist: datastore['CUSTOM_WORDLIST'],
    mutate: datastore['MUTATE'],
    use_creds: datastore['USE_CREDS'],
    use_db_info: datastore['USE_DB_INFO'],
    use_default_wordlist: datastore['USE_DEFAULT_WORDLIST'],
    use_hostnames: datastore['USE_HOSTNAMES'],
    use_common_root: datastore['USE_ROOT_WORDS'],
    workspace: myworkspace
  )
  wordlist.to_file(max_len)
end