Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Path: blob/master/tools/password/md5_lookup.rb
Views: 11768
#!/usr/bin/env ruby12##3# This module requires Metasploit: https://metasploit.com/download4# Current source: https://github.com/rapid7/metasploit-framework5##67#8# This script will look up a collection of MD5 hashes (from a file) against the following databases9# via md5cracker.org:10# authsecu, i337.net, md5.my-addr.com, md5.net, md5crack, md5cracker.org, md5decryption.com,11# md5online.net, md5pass, netmd5crack, tmto.12# This msf tool script was originally ported from:13# https://github.com/hasherezade/metasploit_modules/blob/master/md5_lookup.rb14#15# To-do:16# Maybe as a msf plugin one day and grab hashes directly from the workspace.17#18# Authors:19# * hasherezade (http://hasherezade.net, @hasherezade)20# * sinn3r (ported the module as a standalone msf tool)21#2223#24# Load our MSF API25#2627msfbase = __FILE__28while File.symlink?(msfbase)29msfbase = File.expand_path(File.readlink(msfbase), File.dirname(msfbase))30end31$:.unshift(File.expand_path(File.join(File.dirname(msfbase), '..', '..', 'lib')))32require 'msfenv'33require 'rex'34require 'optparse'3536#37# Basic prints we can't live without38#3940# Prints with [*] that represents the message is a status41#42# @param msg [String] The message to print43# @return [void]44def print_status(msg='')45$stdout.puts "[*] #{msg}"46end4748# Prints with [-] that represents the message is an error49#50# @param msg [String] The message to print51# @return [void]52def print_error(msg='')53$stdout.puts "[-] #{msg}"54end5556module Md5LookupUtility5758# This class manages the disclaimer59class Disclaimer6061# @!attribute config_file62# @return [String] The config file path63attr_accessor :config_file6465# @!attribute group_name66# @return [String] The name of the tool67attr_accessor :group_name6869def initialize70self.config_file = Msf::Config.config_file71self.group_name = 'MD5Lookup'72end7374# Prompts a disclaimer. The user will not be able to get out unless they acknowledge.75#76# @return [TrueClass] true if acknowledged.77def ack78print_status("WARNING: This tool will look up your MD5 hashes by submitting them")79print_status("in the clear (HTTP) to third party websites. This can expose")80print_status("sensitive data to unknown and untrusted entities.")8182while true83$stdout.print "[*] Enter 'Y' to acknowledge: "84if $stdin.gets =~ /^y|yes$/i85return true86end87end88end8990# Saves the waiver so the warning won't show again after ack91#92# @return [void]93def save_waiver94save_setting('waiver', true)95end9697# Returns true if we don't have to show the warning again98#99# @return [Boolean]100def has_waiver?101load_setting('waiver') == 'true' ? true : false102end103104private105106# Saves a setting to Metasploit's config file107#108# @param key_name [String] The name of the setting109# @param value [String] The value of the setting110# @return [void]111def save_setting(key_name, value)112ini = Rex::Parser::Ini.new(self.config_file)113ini.add_group(self.group_name) if ini[self.group_name].nil?114ini[self.group_name][key_name] = value115ini.to_file(self.config_file)116end117118# Returns the value of a specific setting119#120# @param key_name [String] The name of the setting121# @return [String]122def load_setting(key_name)123ini = Rex::Parser::Ini.new(self.config_file)124group = ini[self.group_name]125return '' if group.nil?126group[key_name].to_s127end128129end130131# This class is basically an auxiliary module without relying on msfconsole132class Md5Lookup < Msf::Auxiliary133134include Msf::Exploit::Remote::HttpClient135136# @!attribute rhost137# @return [String] Should be md5cracker.org138attr_accessor :rhost139140# @!attribute rport141# @return [Integer] The port number to md5cracker.org142attr_accessor :rport143144# @!attribute target_uri145# @return [String] The URI (API)146attr_accessor :target_uri147148# @!attribute ssl149# @return [FalseClass] False because doesn't look like md5cracker.org supports HTTPS150attr_accessor :ssl151152def initialize(opts={})153# The user should not be able to modify these settings, otherwise154# the we can't guarantee results.155self.rhost = 'md5cracker.org'156self.rport = 80157self.target_uri = '/api/api.cracker.php'158self.ssl = false159160super(161'DefaultOptions' =>162{163'SSL' => self.ssl,164'RHOST' => self.rhost,165'RPORT' => self.rport166}167)168end169170# Returns the found cracked MD5 hash171#172# @param md5_hash [String] The MD5 hash to lookup173# @param db [String] The specific database to check against174# @return [String] Found cracked MD5 hash175def lookup(md5_hash, db)176res = send_request_cgi({177'uri' => self.target_uri,178'method' => 'GET',179'vars_get' => {'database' => db, 'hash' => md5_hash}180})181get_json_result(res)182end183184private185186# Parses the cracked result from a JSON input187# @param res [Rex::Proto::Http::Response] The Rex HTTP response188# @return [String] Found cracked MD5 hash189def get_json_result(res)190result = ''191192# Hmm, no proper response :-(193return result unless res && res.code == 200194195begin196json = JSON.parse(res.body)197result = json['result'] if json['status']198rescue JSON::ParserError199# No json?200end201202result203end204205end206207# This class parses the user-supplied options (inputs)208class OptsConsole209210# The databases supported by md5cracker.org211# The hash keys (symbols) are used as choices for the user, the hash values are the original212# database values that md5cracker.org will recognize213DATABASES =214{215:all => nil, # This is shifted before being passed to Md5Lookup216:authsecu => 'authsecu',217:i337 => 'i337.net',218:md5_my_addr => 'md5.my-addr.com',219:md5_net => 'md5.net',220:md5crack => 'md5crack',221:md5cracker => 'md5cracker.org',222:md5decryption => 'md5decryption.com',223:md5online => 'md5online.net',224:md5pass => 'md5pass',225:netmd5crack => 'netmd5crack',226:tmto => 'tmto'227}228229# The default file path to save the results to230DEFAULT_OUTFILE = 'md5_results.txt'231232# Returns the normalized user inputs233#234# @param args [Array] This should be Ruby's ARGV235# @raise [OptionParser::MissingArgument] Missing arguments236# @return [Hash] The normalized options237def self.parse(args)238parser, options = get_parsed_options239240# Set the optional datation argument (--database)241unless options[:databases]242options[:databases] = get_database_names243end244245# Set the optional output argument (--out)246unless options[:outfile]247options[:outfile] = DEFAULT_OUTFILE248end249250# Now let's parse it251# This may raise OptionParser::InvalidOption252parser.parse!(args)253254# Final checks255if options.empty?256raise OptionParser::MissingArgument, 'No options set, try -h for usage'257elsif options[:input].blank?258raise OptionParser::MissingArgument, '-i is a required argument'259end260261options262end263264private265266# Returns the parsed options from ARGV267#268# raise [OptionParser::InvalidOption] Invalid option found269# @return [OptionParser, Hash] The OptionParser object and an hash containing the options270def self.get_parsed_options271options = {}272parser = OptionParser.new do |opt|273opt.banner = "Usage: #{__FILE__} [options]"274opt.separator ''275opt.separator 'Specific options:'276277opt.on('-i', '--input <file>',278'The file that contains all the MD5 hashes (one line per hash)') do |v|279if v && !::File.exist?(v)280raise OptionParser::InvalidOption, "Invalid input file: #{v}"281end282283options[:input] = v284end285286opt.on('-d','--databases <names>',287"(Optional) Select databases: #{get_database_symbols * ", "} (Default=all)") do |v|288options[:databases] = extract_db_names(v)289end290291opt.on('-o', '--out <filepath>',292"(Optional) Save the results to a file (Default=#{DEFAULT_OUTFILE})") do |v|293options[:outfile] = v294end295296opt.on_tail('-h', '--help', 'Show this message') do297$stdout.puts opt298exit299end300end301return parser, options302end303304# Returns the actual database names based on what the user wants305#306# @param list [String] A list of user-supplied database names307# @return [Array<String>] All the matched database names308def self.extract_db_names(list)309new_db_list = []310311list_copy = list.split(',')312313if list_copy.include?('all')314return get_database_names315end316317list_copy.each do |item|318item = item.strip.to_sym319new_db_list << DATABASES[item] if DATABASES[item]320end321322new_db_list323end324325# Returns a list of all of the supported database symbols326#327# @return [Array<Symbol>] Database symbols328def self.get_database_symbols329DATABASES.keys330end331332# Returns a list of all the original database values recognized by md5cracker.org333#334# @return [Array<String>] Original database values335def self.get_database_names336new_db_list = DATABASES.values337new_db_list.shift #Get rid of the 'all' option338return new_db_list339end340end341342# This class decides how this process works343class Driver344345def initialize346begin347@opts = OptsConsole.parse(ARGV)348rescue OptionParser::InvalidOption, OptionParser::MissingArgument => e349print_error("#{e.message} (please see -h)")350exit351end352353@output_handle = nil354begin355@output_handle = ::File.new(@opts[:outfile], 'wb')356rescue357# Not end of the world, but if this happens we won't be able to save the results.358# The user will just have to copy and paste from the screen.359print_error("Unable to create file handle, results will not be saved to #{@opts[:output]}")360end361end362363# Main function364#365# @return [void]366def run367input = @opts[:input]368dbs = @opts[:databases]369370disclaimer = Md5LookupUtility::Disclaimer.new371372unless disclaimer.has_waiver?373disclaimer.ack374disclaimer.save_waiver375end376377get_hash_results(input, dbs) do |result|378original_hash = result[:hash]379cracked_hash = result[:cracked_hash]380credit_db = result[:credit]381print_status("Found: #{original_hash} = #{cracked_hash} (from #{credit_db})")382save_result(result) if @output_handle383end384end385386# Cleans up the output file handler if exists387#388# @return [void]389def cleanup390@output_handle.close if @output_handle391end392393private394395# Saves the MD5 result to file396#397# @param result [Hash] The result that contains the MD5 information398# @option result :hash [String] The original MD5 hash399# @option result :cracked_hash [String] The cracked MD5 hash400# @return [void]401def save_result(result)402@output_handle.puts "#{result[:hash]} = #{result[:cracked_hash]}"403end404405# Returns the hash results by actually invoking Md5Lookup406#407# @param input [String] The path of the input file (MD5 hashes)408# @yield [result] Gives a hash as the found result409# @return [void]410def get_hash_results(input, dbs)411search_engine = Md5LookupUtility::Md5Lookup.new412extract_hashes(input) do |hash|413dbs.each do |db|414cracked_hash = search_engine.lookup(hash, db)415unless cracked_hash.empty?416result = { :hash => hash, :cracked_hash => cracked_hash, :credit => db }417yield result418end419420# Awright, we already found one cracked, we don't need to keep looking,421# Let's move on to the next hash!422break unless cracked_hash.empty?423end424end425end426427# Extracts all the MD5 hashes one by one428#429# @param input_file [String] The path of the input file (MD5 hashes)430# @yield [hash] The original MD5 hash431# @return [void]432def extract_hashes(input_file)433::File.open(input_file, 'rb') do |f|434f.each_line do |hash|435next unless is_md5_format?(hash)436yield hash.strip # Make sure no newlines437end438end439end440441# Checks if the hash format is MD5 or not442#443# @param md5_hash [String] The MD5 hash (hex)444# @return [TrueClass/FalseClass] True if the format is valid, otherwise false445def is_md5_format?(md5_hash)446(md5_hash =~ /^[a-f0-9]{32}$/i) ? true : false447end448end449450end451452#453# main454#455if __FILE__ == $PROGRAM_NAME456driver = Md5LookupUtility::Driver.new457begin458driver.run459rescue Interrupt460$stdout.puts461$stdout.puts "Good bye"462ensure463driver.cleanup # Properly close resources464end465end466467468