mirror of
https://github.com/rapid7/metasploit-framework
synced 2024-09-04 20:18:27 +02:00
Land #4601, @wchen-r7's tool to lookup md5 hashes
This commit is contained in:
commit
2c7777f831
378
spec/tools/md5_lookup_spec.rb
Normal file
378
spec/tools/md5_lookup_spec.rb
Normal file
@ -0,0 +1,378 @@
|
||||
load Metasploit::Framework.root.join('tools/md5_lookup.rb').to_path
|
||||
|
||||
require 'rex/proto/http/response'
|
||||
require 'stringio'
|
||||
|
||||
describe Md5LookupUtility do
|
||||
|
||||
#
|
||||
# Init some data
|
||||
#
|
||||
|
||||
let(:input_data) do
|
||||
'098f6bcd4621d373cade4e832627b4f6'
|
||||
end
|
||||
|
||||
let(:bad_input_data) do
|
||||
''
|
||||
end
|
||||
|
||||
let(:good_result) do
|
||||
'test'
|
||||
end
|
||||
|
||||
let(:empty_result) do
|
||||
''
|
||||
end
|
||||
|
||||
let(:good_json_response) do
|
||||
%Q|{ "status":true, "result":"test", "message":"" }|
|
||||
end
|
||||
|
||||
let(:bad_json_response) do
|
||||
%Q|{ "status":false, "result":"", "message":"not found" }|
|
||||
end
|
||||
|
||||
let(:db_source) do
|
||||
'i337.net'
|
||||
end
|
||||
|
||||
let(:input_file) do
|
||||
'input.txt'
|
||||
end
|
||||
|
||||
let(:output_file) do
|
||||
'output.txt'
|
||||
end
|
||||
|
||||
let(:options) do
|
||||
{
|
||||
:databases => [db_source],
|
||||
:outfile => output_file,
|
||||
:input => input_file
|
||||
}
|
||||
end
|
||||
|
||||
subject do
|
||||
Md5LookupUtility::Md5Lookup.new
|
||||
end
|
||||
|
||||
def set_expected_response(body)
|
||||
res = Rex::Proto::Http::Response.new
|
||||
res.code = 200
|
||||
res.body = body
|
||||
res
|
||||
end
|
||||
|
||||
def set_send_request_cgi(body)
|
||||
allow(subject).to receive(:send_request_cgi) do |opts|
|
||||
set_expected_response(body)
|
||||
end
|
||||
end
|
||||
|
||||
def get_stdout(&block)
|
||||
out = $stdout
|
||||
$stdout = fake = StringIO.new
|
||||
begin
|
||||
yield
|
||||
ensure
|
||||
$stdout = out
|
||||
end
|
||||
fake.string
|
||||
end
|
||||
|
||||
#
|
||||
# Tests start here
|
||||
#
|
||||
|
||||
|
||||
describe Md5LookupUtility::Disclaimer do
|
||||
|
||||
let(:group_name) { 'MD5Lookup' }
|
||||
let(:setting_name) { 'waiver' }
|
||||
let(:data) { true }
|
||||
let(:t_path) { 'filepath' }
|
||||
|
||||
def stub_save
|
||||
ini = Rex::Parser::Ini.new(t_path)
|
||||
allow(ini).to receive(:to_file).with(any_args)
|
||||
allow(Rex::Parser::Ini).to receive(:new).and_return(ini)
|
||||
return ini
|
||||
end
|
||||
|
||||
def stub_load(with_setting=true)
|
||||
if with_setting
|
||||
ini = stub_save
|
||||
disclamer.save_waiver
|
||||
else
|
||||
ini = Rex::Parser::Ini.new(t_path)
|
||||
end
|
||||
|
||||
allow(Rex::Parser::Ini).to receive(:new).and_return(ini)
|
||||
return ini
|
||||
end
|
||||
|
||||
subject(:disclamer) do
|
||||
Md5LookupUtility::Disclaimer.new
|
||||
end
|
||||
|
||||
describe '#ack' do
|
||||
context 'When \'Y\' is entered' do
|
||||
it 'returns true' do
|
||||
agree = "Y\n"
|
||||
allow($stdin).to receive(:gets).and_return(agree)
|
||||
get_stdout { expect(disclamer.ack).to be_truthy }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#save_waiver' do
|
||||
context 'when waiver is true' do
|
||||
it 'saves the wavier setting' do
|
||||
ini = stub_save
|
||||
disclamer.save_waiver
|
||||
expect(ini[group_name]).to eq({setting_name=>true})
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#has_waiver?' do
|
||||
context 'when there is a waiver' do
|
||||
it 'returns true' do
|
||||
ini = stub_load(true)
|
||||
expect(disclamer.send(:has_waiver?)).to be_truthy
|
||||
end
|
||||
end
|
||||
|
||||
context 'when there is no waiver' do
|
||||
it 'returns false' do
|
||||
ini = stub_load(false)
|
||||
expect(disclamer.send(:has_waiver?)).to be_falsey
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#save_setting' do
|
||||
context 'when a setting is given' do
|
||||
it 'saves the setting' do
|
||||
ini = stub_save
|
||||
disclamer.send(:save_setting, setting_name, data)
|
||||
expect(ini[group_name]).to eq({setting_name=>true})
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#load_setting' do
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
describe Md5LookupUtility::Md5Lookup do
|
||||
|
||||
describe '.new' do
|
||||
it 'returns a Md5LookupUtility::Md5Lookup instance' do
|
||||
expect(subject).to be_a(Md5LookupUtility::Md5Lookup)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#lookup' do
|
||||
|
||||
context 'when a hash is found' do
|
||||
it 'returns the cracked result' do
|
||||
set_send_request_cgi(good_json_response)
|
||||
expect(subject.lookup(input_data, db_source)).to eq(good_result)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when a hash is not found' do
|
||||
it 'returns an empty result' do
|
||||
set_send_request_cgi(bad_json_response)
|
||||
expect(subject.lookup(input_data, db_source)).to eq(empty_result)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#get_json_results' do
|
||||
context 'when JSON contains the found result' do
|
||||
it 'returns the cracked result' do
|
||||
res = set_expected_response(good_json_response)
|
||||
expect(subject.send(:get_json_result, res)).to eq(good_result)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when there is no JSON data' do
|
||||
it 'returns an empty result' do
|
||||
res = set_expected_response('')
|
||||
expect(subject.send(:get_json_result, res)).to eq(empty_result)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
describe Md5LookupUtility::Driver do
|
||||
|
||||
let(:expected_result) {
|
||||
{
|
||||
:hash => input_data,
|
||||
:cracked_hash => good_result,
|
||||
:credit => db_source
|
||||
}
|
||||
}
|
||||
|
||||
before(:each) do
|
||||
Md5LookupUtility::OptsConsole.stub(:parse).with(any_args).and_return(options)
|
||||
allow(File).to receive(:open).with(input_file, 'rb').and_yield(StringIO.new(input_data))
|
||||
allow(File).to receive(:new).with(output_file, 'wb').and_return(StringIO.new)
|
||||
end
|
||||
|
||||
subject do
|
||||
Md5LookupUtility::Driver.new
|
||||
end
|
||||
|
||||
describe '.new' do
|
||||
it 'returns a Md5LookupUtility::Driver instance' do
|
||||
expect(subject).to be_a(Md5LookupUtility::Driver)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#run' do
|
||||
context 'when a hash is found' do
|
||||
it 'prints a \'found\' message' do
|
||||
disclaimer = Md5LookupUtility::Disclaimer.new
|
||||
allow(disclaimer).to receive(:has_waiver?).and_return(true)
|
||||
allow(Md5LookupUtility::Disclaimer).to receive(:new).and_return(disclaimer)
|
||||
allow(subject).to receive(:get_hash_results).and_yield(expected_result)
|
||||
output = get_stdout { subject.run }
|
||||
expect(output).to include('Found:')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#save_result' do
|
||||
context 'when a result is given' do
|
||||
it 'writes the result to file' do
|
||||
subject.send(:save_result, expected_result)
|
||||
expect(subject.instance_variable_get(:@output_handle).string).to include(good_result)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#get_hash_results' do
|
||||
context 'when a hash is found' do
|
||||
it 'yields a result' do
|
||||
search_engine = Md5LookupUtility::Md5Lookup.new
|
||||
allow(search_engine).to receive(:lookup).and_return(good_result)
|
||||
allow(Md5LookupUtility::Md5Lookup).to receive(:new).and_return(search_engine)
|
||||
|
||||
expect{ |b| subject.send(:get_hash_results, input_file, [db_source], &b) }.to yield_with_args(expected_result)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#extract_hashes' do
|
||||
context 'when a MD5 file is supplied' do
|
||||
it 'yields the MD5 hash' do
|
||||
expect{ |b| subject.send(:extract_hashes, input_file, &b) }.to yield_with_args(input_data)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when an empty file is supplied' do
|
||||
before do
|
||||
allow(File).to receive(:open).with(input_file, 'rb').and_yield(StringIO.new(''))
|
||||
end
|
||||
|
||||
it 'yields nothing' do
|
||||
expect{ |b| subject.send(:extract_hashes, input_file, &b) }.not_to yield_control
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#is_md5_format?' do
|
||||
context 'when a valid MD5 is given' do
|
||||
it 'returns true' do
|
||||
expect(subject.send(:is_md5_format?,input_data)).to be_truthy
|
||||
end
|
||||
end
|
||||
|
||||
context 'when a non-MD5 value is given' do
|
||||
it 'returns false' do
|
||||
expect(subject.send(:is_md5_format?, bad_input_data)).to be_falsey
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
describe Md5LookupUtility::OptsConsole do
|
||||
let(:valid_argv) { "-i #{input_file} -d all -o #{output_file}".split }
|
||||
|
||||
let(:invalid_argv) { "".split }
|
||||
|
||||
subject do
|
||||
Md5LookupUtility::OptsConsole
|
||||
end
|
||||
|
||||
describe '.parse' do
|
||||
context 'when valid arguments are passed' do
|
||||
let(:opts) { subject.parse(valid_argv) }
|
||||
|
||||
before(:each) do
|
||||
allow(File).to receive(:exists?).and_return(true)
|
||||
end
|
||||
|
||||
it 'returns the input file path' do
|
||||
expect(opts[:input]).to eq(input_file)
|
||||
end
|
||||
|
||||
it 'returns the output file path' do
|
||||
expect(opts[:outfile]).to eq(output_file)
|
||||
end
|
||||
|
||||
it 'returns the databases in an array' do
|
||||
expect(opts[:databases]).to be_a(Array)
|
||||
expect(opts[:databases]).to include(db_source)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the required input file is not set' do
|
||||
before(:each) do
|
||||
allow(File).to receive(:exists?).and_return(false)
|
||||
end
|
||||
|
||||
it 'raises an OptionParser::MissingArgument error' do
|
||||
expect{subject.parse(invalid_argv)}.to raise_error(OptionParser::MissingArgument)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
describe '.extract_db_names' do
|
||||
let(:list) {'i337,invalid'}
|
||||
context 'when database symbols \'i337\' and \'invalid\' are given' do
|
||||
it 'returns i337.net in an array' do
|
||||
db_names = subject.extract_db_names(list)
|
||||
expect(db_names).to be_a(Array)
|
||||
expect(db_names).to include(db_source)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '.get_database_symbols' do
|
||||
it 'returns an array' do
|
||||
expect(subject.get_database_symbols).to be_a(Array)
|
||||
end
|
||||
end
|
||||
|
||||
describe '.get_database_names' do
|
||||
it 'returns an array' do
|
||||
expect(subject.get_database_names).to be_a(Array)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
489
tools/md5_lookup.rb
Executable file
489
tools/md5_lookup.rb
Executable file
@ -0,0 +1,489 @@
|
||||
#!/usr/bin/env ruby
|
||||
|
||||
##
|
||||
# This module requires Metasploit: http://metasploit.com/download
|
||||
# Current source: https://github.com/rapid7/metasploit-framework
|
||||
##
|
||||
|
||||
###
|
||||
#
|
||||
# This script will look up a collection of MD5 hashes (from a file) against the following databases
|
||||
# via md5cracker.org:
|
||||
# authsecu, i337.net, md5.my-addr.com, md5.net, md5crack, md5cracker.org, md5decryption.com,
|
||||
# md5online.net, md5pass, netmd5crack, tmto.
|
||||
# This msf tool script was originally ported from:
|
||||
# https://github.com/hasherezade/metasploit_modules/blob/master/md5_lookup.rb
|
||||
#
|
||||
# To-do:
|
||||
# Maybe as a msf plugin one day and grab hashes directly from the workspace.
|
||||
#
|
||||
# Authors:
|
||||
# * hasherezade (http://hasherezade.net, @hasherezade)
|
||||
# * sinn3r (ported the module as a standalone msf tool)
|
||||
#
|
||||
###
|
||||
|
||||
#
|
||||
# Load our MSF API
|
||||
#
|
||||
|
||||
msfbase = __FILE__
|
||||
while File.symlink?(msfbase)
|
||||
msfbase = File.expand_path(File.readlink(msfbase), File.dirname(msfbase))
|
||||
end
|
||||
$:.unshift(File.expand_path(File.join(File.dirname(msfbase), '..', 'lib')))
|
||||
require 'msfenv'
|
||||
require 'rex'
|
||||
require 'msf/core'
|
||||
require 'optparse'
|
||||
|
||||
|
||||
#
|
||||
# Basic prints we can't live without
|
||||
#
|
||||
|
||||
# Prints with [*] that represents the message is a status
|
||||
#
|
||||
# @param msg [String] The message to print
|
||||
# @return [void]
|
||||
def print_status(msg='')
|
||||
$stdout.puts "[*] #{msg}"
|
||||
end
|
||||
|
||||
# Prints with [-] that represents the message is an error
|
||||
#
|
||||
# @param msg [String] The message to print
|
||||
# @return [void]
|
||||
def print_error(msg='')
|
||||
$stdout.puts "[-] #{msg}"
|
||||
end
|
||||
|
||||
module Md5LookupUtility
|
||||
|
||||
# This class manages the disclaimer
|
||||
class Disclaimer
|
||||
|
||||
# @!attribute config_file
|
||||
# @return [String] The config file path
|
||||
attr_accessor :config_file
|
||||
|
||||
# @!attribute group_name
|
||||
# @return [String] The name of the tool
|
||||
attr_accessor :group_name
|
||||
|
||||
|
||||
def initialize
|
||||
self.config_file = Msf::Config.config_file
|
||||
self.group_name = 'MD5Lookup'
|
||||
end
|
||||
|
||||
# Prompts a disclaimer. The user will not be able to get out unless they acknowledge.
|
||||
#
|
||||
# @return [TrueClass] true if acknowledged.
|
||||
def ack
|
||||
print_status("WARNING: This tool will look up your MD5 hashes by submitting them")
|
||||
print_status("in the clear (HTTP) to third party websites. This can expose")
|
||||
print_status("sensitive data to unknown and untrusted entities.")
|
||||
|
||||
while true
|
||||
$stdout.print "[*] Enter 'Y' to acknowledge: "
|
||||
if $stdin.gets =~ /^y|yes$/i
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# Saves the waiver so the warning won't show again after ack
|
||||
#
|
||||
# @return [void]
|
||||
def save_waiver
|
||||
save_setting('waiver', true)
|
||||
end
|
||||
|
||||
|
||||
# Returns true if we don't have to show the warning again
|
||||
#
|
||||
# @return [Boolean]
|
||||
def has_waiver?
|
||||
load_setting('waiver') == 'true' ? true : false
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
|
||||
# Saves a setting to Metasploit's config file
|
||||
#
|
||||
# @param key_name [String] The name of the setting
|
||||
# @param value [String] The value of the setting
|
||||
# @return [void]
|
||||
def save_setting(key_name, value)
|
||||
ini = Rex::Parser::Ini.new(self.config_file)
|
||||
ini.add_group(self.group_name) if ini[self.group_name].nil?
|
||||
ini[self.group_name][key_name] = value
|
||||
ini.to_file(self.config_file)
|
||||
end
|
||||
|
||||
|
||||
# Returns the value of a specific setting
|
||||
#
|
||||
# @param key_name [String] The name of the setting
|
||||
# @return [String]
|
||||
def load_setting(key_name)
|
||||
ini = Rex::Parser::Ini.new(self.config_file)
|
||||
group = ini[self.group_name]
|
||||
return '' if group.nil?
|
||||
group[key_name].to_s
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
# This class is basically an auxiliary module without relying on msfconsole
|
||||
class Md5Lookup < Msf::Auxiliary
|
||||
|
||||
include Msf::Exploit::Remote::HttpClient
|
||||
|
||||
# @!attribute rhost
|
||||
# @return [String] Should be md5cracker.org
|
||||
attr_accessor :rhost
|
||||
|
||||
# @!attribute rport
|
||||
# @return [Fixnum] The port number to md5cracker.org
|
||||
attr_accessor :rport
|
||||
|
||||
# @!attribute target_uri
|
||||
# @return [String] The URI (API)
|
||||
attr_accessor :target_uri
|
||||
|
||||
# @!attribute ssl
|
||||
# @return [FalseClass] False because doesn't look like md5cracker.org supports HTTPS
|
||||
attr_accessor :ssl
|
||||
|
||||
def initialize(opts={})
|
||||
# The user should not be able to modify these settings, otherwise
|
||||
# the we can't guarantee results.
|
||||
self.rhost = 'md5cracker.org'
|
||||
self.rport = 80
|
||||
self.target_uri = '/api/api.cracker.php'
|
||||
self.ssl = false
|
||||
|
||||
super(
|
||||
'DefaultOptions' =>
|
||||
{
|
||||
'SSL' => self.ssl,
|
||||
'RHOST' => self.rhost,
|
||||
'RPORT' => self.rport
|
||||
}
|
||||
)
|
||||
end
|
||||
|
||||
|
||||
# Returns the found cracked MD5 hash
|
||||
#
|
||||
# @param md5_hash [String] The MD5 hash to lookup
|
||||
# @param db [String] The specific database to check against
|
||||
# @return [String] Found cracked MD5 hash
|
||||
def lookup(md5_hash, db)
|
||||
res = send_request_cgi({
|
||||
'uri' => self.target_uri,
|
||||
'method' => 'GET',
|
||||
'vars_get' => {'database' => db, 'hash' => md5_hash}
|
||||
})
|
||||
get_json_result(res)
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
|
||||
|
||||
# Parses the cracked result from a JSON input
|
||||
# @param res [Rex::Proto::Http::Response] The Rex HTTP response
|
||||
# @return [String] Found cracked MD5 hash
|
||||
def get_json_result(res)
|
||||
result = ''
|
||||
|
||||
# Hmm, no proper response :-(
|
||||
return result unless res && res.code == 200
|
||||
|
||||
begin
|
||||
json = JSON.parse(res.body)
|
||||
result = json['result'] if json['status']
|
||||
rescue JSON::ParserError
|
||||
# No json?
|
||||
end
|
||||
|
||||
result
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
# This class parses the user-supplied options (inputs)
|
||||
class OptsConsole
|
||||
|
||||
# The databases supported by md5cracker.org
|
||||
# The hash keys (symbols) are used as choices for the user, the hash values are the original
|
||||
# database values that md5cracker.org will recognize
|
||||
DATABASES =
|
||||
{
|
||||
:all => nil, # This is shifted before being passed to Md5Lookup
|
||||
:authsecu => 'authsecu',
|
||||
:i337 => 'i337.net',
|
||||
:md5_my_addr => 'md5.my-addr.com',
|
||||
:md5_net => 'md5.net',
|
||||
:md5crack => 'md5crack',
|
||||
:md5cracker => 'md5cracker.org',
|
||||
:md5decryption => 'md5decryption.com',
|
||||
:md5online => 'md5online.net',
|
||||
:md5pass => 'md5pass',
|
||||
:netmd5crack => 'netmd5crack',
|
||||
:tmto => 'tmto'
|
||||
}
|
||||
|
||||
# The default file path to save the results to
|
||||
DEFAULT_OUTFILE = 'md5_results.txt'
|
||||
|
||||
# Returns the normalized user inputs
|
||||
#
|
||||
# @param args [Array] This should be Ruby's ARGV
|
||||
# @raise [OptionParser::MissingArgument] Missing arguments
|
||||
# @return [Hash] The normalized options
|
||||
def self.parse(args)
|
||||
parser, options = get_parsed_options
|
||||
|
||||
# Set the optional datation argument (--database)
|
||||
unless options[:databases]
|
||||
options[:databases] = get_database_names
|
||||
end
|
||||
|
||||
# Set the optional output argument (--out)
|
||||
unless options[:outfile]
|
||||
options[:outfile] = DEFAULT_OUTFILE
|
||||
end
|
||||
|
||||
# Now let's parse it
|
||||
# This may raise OptionParser::InvalidOption
|
||||
parser.parse!(args)
|
||||
|
||||
# Final checks
|
||||
if options.empty?
|
||||
raise OptionParser::MissingArgument, 'No options set, try -h for usage'
|
||||
elsif options[:input].blank?
|
||||
raise OptionParser::MissingArgument, '-i is a required argument'
|
||||
end
|
||||
|
||||
options
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
|
||||
|
||||
# Returns the parsed options from ARGV
|
||||
#
|
||||
# raise [OptionParser::InvalidOption] Invalid option found
|
||||
# @return [OptionParser, Hash] The OptionParser object and an hash containg the options
|
||||
def self.get_parsed_options
|
||||
options = {}
|
||||
parser = OptionParser.new do |opt|
|
||||
opt.banner = "Usage: #{__FILE__} [options]"
|
||||
opt.separator ''
|
||||
opt.separator 'Specific options:'
|
||||
|
||||
opt.on('-i', '--input <file>',
|
||||
'The file that contains all the MD5 hashes (one line per hash)') do |v|
|
||||
if v && !::File.exists?(v)
|
||||
raise OptionParser::InvalidOption, "Invalid input file: #{v}"
|
||||
end
|
||||
|
||||
options[:input] = v
|
||||
end
|
||||
|
||||
opt.on('-d','--databases <names>',
|
||||
"(Optional) Select databases: #{get_database_symbols * ", "} (Default=all)") do |v|
|
||||
options[:databases] = extract_db_names(v)
|
||||
end
|
||||
|
||||
opt.on('-o', '--out <filepath>',
|
||||
"(Optional) Save the results to a file (Default=#{DEFAULT_OUTFILE})") do |v|
|
||||
options[:outfile] = v
|
||||
end
|
||||
|
||||
opt.on_tail('-h', '--help', 'Show this message') do
|
||||
$stdout.puts opt
|
||||
exit
|
||||
end
|
||||
end
|
||||
return parser, options
|
||||
end
|
||||
|
||||
|
||||
# Returns the actual database names based on what the user wants
|
||||
#
|
||||
# @param list [String] A list of user-supplied database names
|
||||
# @return [Array<String>] All the matched database names
|
||||
def self.extract_db_names(list)
|
||||
new_db_list = []
|
||||
|
||||
list_copy = list.split(',')
|
||||
|
||||
if list_copy.include?('all')
|
||||
return get_database_names
|
||||
end
|
||||
|
||||
list_copy.each do |item|
|
||||
item = item.strip.to_sym
|
||||
new_db_list << DATABASES[item] if DATABASES[item]
|
||||
end
|
||||
|
||||
new_db_list
|
||||
end
|
||||
|
||||
|
||||
# Returns a list of all of the supported database symbols
|
||||
#
|
||||
# @return [Array<Symbol>] Database symbols
|
||||
def self.get_database_symbols
|
||||
DATABASES.keys
|
||||
end
|
||||
|
||||
# Returns a list of all the original database values recognized by md5cracker.org
|
||||
#
|
||||
# @return [Array<String>] Original database values
|
||||
def self.get_database_names
|
||||
new_db_list = DATABASES.values
|
||||
new_db_list.shift #Get rid of the 'all' option
|
||||
return new_db_list
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# This class decides how this process works
|
||||
class Driver
|
||||
|
||||
def initialize
|
||||
begin
|
||||
@opts = OptsConsole.parse(ARGV)
|
||||
rescue OptionParser::InvalidOption, OptionParser::MissingArgument => e
|
||||
print_error("#{e.message} (please see -h)")
|
||||
exit
|
||||
end
|
||||
|
||||
@output_handle = nil
|
||||
begin
|
||||
@output_handle = ::File.new(@opts[:outfile], 'wb')
|
||||
rescue
|
||||
# Not end of the world, but if this happens we won't be able to save the results.
|
||||
# The user will just have to copy and paste from the screen.
|
||||
print_error("Unable to create file handle, results will not be saved to #{@opts[:output]}")
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# Main function
|
||||
#
|
||||
# @return [void]
|
||||
def run
|
||||
input = @opts[:input]
|
||||
dbs = @opts[:databases]
|
||||
|
||||
disclamer = Md5LookupUtility::Disclaimer.new
|
||||
|
||||
unless disclamer.has_waiver?
|
||||
disclamer.ack
|
||||
disclamer.save_waiver
|
||||
end
|
||||
|
||||
get_hash_results(input, dbs) do |result|
|
||||
original_hash = result[:hash]
|
||||
cracked_hash = result[:cracked_hash]
|
||||
credit_db = result[:credit]
|
||||
print_status("Found: #{original_hash} = #{cracked_hash} (from #{credit_db})")
|
||||
save_result(result) if @output_handle
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# Cleans up the output file handler if exists
|
||||
#
|
||||
# @return [void]
|
||||
def cleanup
|
||||
@output_handle.close if @output_handle
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
|
||||
|
||||
# Saves the MD5 result to file
|
||||
#
|
||||
# @param result [Hash] The result that contains the MD5 information
|
||||
# @option result :hash [String] The original MD5 hash
|
||||
# @option result :cracked_hash [String] The cracked MD5 hash
|
||||
# @return [void]
|
||||
def save_result(result)
|
||||
@output_handle.puts "#{result[:hash]} = #{result[:cracked_hash]}"
|
||||
end
|
||||
|
||||
# Returns the hash results by actually invoking Md5Lookup
|
||||
#
|
||||
# @param input [String] The path of the input file (MD5 hashes)
|
||||
# @yield [result] Gives a hash as the found result
|
||||
# @return [void]
|
||||
def get_hash_results(input, dbs)
|
||||
search_engine = Md5LookupUtility::Md5Lookup.new
|
||||
extract_hashes(input) do |hash|
|
||||
dbs.each do |db|
|
||||
cracked_hash = search_engine.lookup(hash, db)
|
||||
unless cracked_hash.empty?
|
||||
result = { :hash => hash, :cracked_hash => cracked_hash, :credit => db }
|
||||
yield result
|
||||
end
|
||||
|
||||
# Awright, we already found one cracked, we don't need to keep looking,
|
||||
# Let's move on to the next hash!
|
||||
break unless cracked_hash.empty?
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Extracts all the MD5 hashes one by one
|
||||
#
|
||||
# @param input_file [String] The path of the input file (MD5 hashes)
|
||||
# @yield [hash] The original MD5 hash
|
||||
# @return [void]
|
||||
def extract_hashes(input_file)
|
||||
::File.open(input_file, 'rb') do |f|
|
||||
f.each_line do |hash|
|
||||
next unless is_md5_format?(hash)
|
||||
yield hash.strip # Make sure no newlines
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Checks if the hash format is MD5 or not
|
||||
#
|
||||
# @param md5_hash [String] The MD5 hash (hex)
|
||||
# @return [TrueClass/FlaseClass] True if the format is valid, otherwise false
|
||||
def is_md5_format?(md5_hash)
|
||||
(md5_hash =~ /^[a-f0-9]{32}$/i) ? true : false
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
#
|
||||
# main
|
||||
#
|
||||
if __FILE__ == $PROGRAM_NAME
|
||||
driver = Md5LookupUtility::Driver.new
|
||||
begin
|
||||
driver.run
|
||||
rescue Interrupt
|
||||
$stdout.puts
|
||||
$stdout.puts "Good bye"
|
||||
ensure
|
||||
driver.cleanup # Properly close resources
|
||||
end
|
||||
end
|
Loading…
Reference in New Issue
Block a user