# -*- coding: binary -*-
require 'open3'
require 'fileutils'
require 'rex/proto/ntlm/crypt'



module Msf

###
#
# This module provides methods for working with John the Ripper
#
###
module Auxiliary::JohnTheRipper
	include Msf::Auxiliary::Report

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

	def initialize(info = {})
		super

		register_options(
			[
				OptPath.new('JOHN_BASE', [false, 'The directory containing John the Ripper (src, run, doc)']),
				OptPath.new('JOHN_PATH', [false, 'The absolute path to the John the Ripper executable']),
				OptPath.new('Wordlist', [false, 'The path to an optional Wordlist']),
				OptBool.new('Munge',[false, 'Munge the Wordlist (Slower)', false])
			], Msf::Auxiliary::JohnTheRipper
		)

		@run_path  = nil
		@john_path = ::File.join(Msf::Config.install_root, "data", "john")

		autodetect_platform
	end

	def autodetect_platform
		cpuinfo_base = ::File.join(Msf::Config.install_root, "data", "cpuinfo")
		return @run_path if @run_path

		case ::RUBY_PLATFORM
		when /mingw|cygwin|mswin/
			data = `"#{cpuinfo_base}/cpuinfo.exe"` rescue nil
			case data
			when /sse2/
				@run_path ||= "run.win32.sse2/john.exe"
			when /mmx/
				@run_path ||= "run.win32.mmx/john.exe"
			else
				@run_path ||= "run.win32.any/john.exe"
			end

		when /x86_64-linux/
			::FileUtils.chmod(0755, "#{cpuinfo_base}/cpuinfo.ia64.bin") rescue nil
			data = `#{cpuinfo_base}/cpuinfo.ia64.bin` rescue nil
			case data
			when /mmx/
				@run_path ||= "run.linux.x64.mmx/john"
			else
				@run_path ||= "run.linux.x86.any/john"
			end

		when /i[\d]86-linux/
			::FileUtils.chmod(0755, "#{cpuinfo_base}/cpuinfo.ia32.bin") rescue nil
			data = `#{cpuinfo_base}/cpuinfo.ia32.bin` rescue nil
			case data
			when /sse2/
				@run_path ||= "run.linux.x86.sse2/john"
			when /mmx/
				@run_path ||= "run.linux.x86.mmx/john"
			else
				@run_path ||= "run.linux.x86.any/john"
			end
		end
		@run_path
	end

	def john_session_id
		@session_id ||= ::Rex::Text.rand_text_alphanumeric(8)
	end

	def john_pot_file
		::File.join( ::Msf::Config.config_directory, "john.pot" )
	end

	def john_cracked_passwords
		ret = {}
		return ret if not ::File.exist?(john_pot_file)
		::File.open(john_pot_file, "rb") do |fd|
			fd.each_line do |line|
				hash,clear = line.sub(/\r?\n$/, '').split(",", 2)
				ret[hash] = clear
			end
		end
		ret
	end

	def john_show_passwords(hfile, format=nil)
		res = {:cracked => 0, :uncracked => 0, :users => {} }

		pot  = john_pot_file
		conf = ::File.join(john_base_path, "confs", "john.conf")

		cmd = [ john_binary_path,  "--show", "--conf=#{conf}", "--pot=#{pot}", hfile]

		if format
			cmd << "--format=" + format
		end

		if RUBY_VERSION =~ /^1\.8\./
			cmd = cmd.join(" ")
		end

		::IO.popen(cmd, "rb") do |fd|
			fd.each_line do |line|
				print_status(line)
				if line =~ /(\d+) password hash(es)* cracked, (\d+) left/m
					res[:cracked]   = $1.to_i
					res[:uncracked] = $2.to_i
				end

				bits = line.split(':')
				next if not bits[2]
				if (format== 'lm' or format == 'nt')
					res[ :users ][ bits[0] ] = bits[1]
				else
					bits.last.chomp!
					res[ :users ][ bits[0] ] = bits.drop(1)
				end

			end
		end
		res
	end

	def john_unshadow(passwd_file,shadow_file)

		retval=""

		if File.exists?(passwd_file)
			unless File.readable?(passwd_file)
				print_error("We do not have permission to read #{passwd_file}")
				return nil
			end
		else
			print_error("File does not exist: #{passwd_file}")
			return nil
		end

		if File.exists?(shadow_file)
			unless File.readable?(shadow_file)
				print_error("We do not have permission to read #{shadow_file}")
				return nil
			end
		else
			print_error("File does not exist: #{shadow_file}")
			return nil
		end


		cmd = [ john_binary_path.gsub(/john$/, "unshadow"), passwd_file , shadow_file ]

		if RUBY_VERSION =~ /^1\.8\./
			cmd = cmd.join(" ")
		end
		::IO.popen(cmd, "rb") do |fd|
			fd.each_line do |line|
				retval << line
			end
		end
		return retval
	end

	def john_wordlist_path
		::File.join(john_base_path, "wordlists", "password.lst")
	end

	def john_binary_path
		path = nil
		if datastore['JOHN_PATH'] and ::File.file?(datastore['JOHN_PATH'])
			path = datastore['JOHN_PATH']
			::FileUtils.chmod(0755, path) rescue nil
		end

		if not @run_path
			if ::RUBY_PLATFORM =~ /mingw|cygwin|mswin/
				::File.join(john_base_path, "john.exe")
			else
				path = ::File.join(john_base_path, "john")
				::FileUtils.chmod(0755, path) rescue nil
			end
		else
			path = ::File.join(john_base_path, @run_path)
			::FileUtils.chmod(0755, path) rescue nil
		end

		if path and ::File.exists?(path)
			return path
		end

		path = Rex::FileUtils.find_full_path("john") ||
			Rex::FileUtils.find_full_path("john.exe")
	end

	def john_base_path
		if datastore['JOHN_BASE'] and ::File.directory?(datastore['JOHN_BASE'])
			return datastore['JOHN_BASE']
		end
		if datastore['JOHN_PATH'] and ::File.file?(datastore['JOHN_PATH'])
			return ::File.dirname( datastore['JOHN_PATH'] )
		end
		@john_path
	end

	def john_expand_word(str)
		res = [str]
		str.split(/\W+/) {|w| res << w }
		res.uniq
	end

	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


	def john_crack(hfile, opts={})

		res = {:cracked => 0, :uncracked => 0, :users => {} }

		# Don't bother making a log file, we'd just have to rm it when we're
		# done anyway.
		cmd = [ john_binary_path,  "--session=" + john_session_id, "--nolog"]

		if opts[:conf]
			cmd << ( "--conf=" + opts[:conf] )
		else
			cmd << ( "--conf=" + ::File.join(john_base_path, "confs", "john.conf") )
		end

		if opts[:pot]
			cmd << ( "--pot=" + opts[:pot] )
		else
			cmd << ( "--pot=" + john_pot_file )
		end

		if opts[:format]
			cmd << ( "--format=" + opts[:format] )
		end

		if opts[:wordlist]
			cmd << ( "--wordlist=" + opts[:wordlist] )
		end

		if opts[:incremental]
			cmd << ( "--incremental=" + opts[:incremental] )
		end

		if opts[:single]
			cmd << ( "--single=" + opts[:single] )
		end

		if opts[:rules]
			cmd << ( "--rules=" + opts[:rules] )
		end

		cmd << hfile

		if RUBY_VERSION =~ /^1\.8\./
			cmd = cmd.join(" ")
		end

		::IO.popen(cmd, "rb") do |fd|
			fd.each_line do |line|
				print_status("Output: #{line.strip}")
			end
		end

		res
	end

	def build_seed

		seed = []
		#Seed the wordlist with Database , Table, and Instance Names

		count = 0
		schemas = myworkspace.notes.where('ntype like ?', '%.schema%')
		unless schemas.nil? or schemas.empty?
			schemas.each do |anote|
				seed << anote.data['DBName']
				count += 1
				anote.data['Tables'].each do |table|
					seed << table['TableName']
					count += 1
					table['Columns'].each do |column|
						seed << column['ColumnName']
						count += 1
					end
				end
			end
		end
		print_status "Seeding wordlist with DB schema info... #{count} words added"
		count = 0

		instances = myworkspace.notes.find(:all, :conditions => ['ntype=?', 'mssql.instancename'])
		unless instances.nil? or instances.empty?
			instances.each do |anote|
				seed << anote.data['InstanceName']
				count += 1
			end
		end
		print_status "Seeding with MSSQL Instance Names....#{count} words added"
		count = 0

		# Seed the wordlist with usernames, passwords, and hostnames

		myworkspace.hosts.find(:all).each do |o|
			if o.name
				seed << john_expand_word( o.name )
				count += 1
			end
		end
		print_status "Seeding with hostnames....#{count} words added"
		count = 0


		myworkspace.creds.each do |o|
			if o.user
				seed << john_expand_word( o.user )
				count +=1
			end
			if (o.pass and o.ptype !~ /hash/)
				seed << john_expand_word( o.pass )
				count += 1
			end
		end
		print_status "Seeding with found credentials....#{count} words added"
		count = 0

		# Grab any known passwords out of the john.pot file
		john_cracked_passwords.values do |v|
			seed << v
			count += 1
		end
		print_status "Seeding with cracked passwords from John....#{count} words added"
		count = 0

		#Grab the default John Wordlist
		john = File.open(john_wordlist_path, "rb")
		john.each_line do |line|
			seed << line.chomp
			count += 1
		end
		print_status "Seeding with default John wordlist...#{count} words added"
		count = 0

		if datastore['Wordlist']
			wordlist= File.open(datastore['Wordlist'], "rb")
			wordlist.each_line do |line|
				seed << line.chomp
				count ==1
			end
			print_status "Seeding from user supplied wordlist...#{count} words added"
		end



		unless seed.empty?
			seed.flatten!
			seed.uniq!
			if datastore['Munge']
				mungedseed=[]
				seed.each do |word|
					munged = word.gsub(/[sS]/, "$").gsub(/[aA]/,"@").gsub(/[oO]/,"0")
					mungedseed << munged
					munged.gsub!(/[eE]/, "3")
					munged.gsub!(/[tT]/, "7")
					mungedseed << munged
				end
				print_status "Adding #{mungedseed.count} words from munging..."
				seed << mungedseed
				seed.flatten!
				seed.uniq!
			end
		end
		print_status "De-duping the wordlist...."

		print_status("Wordlist Seeded with #{seed.length} words")

		return seed

	end
end
end
