# -*- coding: binary -*-
require 'uri'
require 'digest'
require 'rex/proto/ntlm/crypt'
require 'rex/proto/ntlm/constants'
require 'rex/proto/ntlm/utils'
require 'rex/proto/ntlm/exceptions'

module Msf
module Exploit::Remote::WinRM
	include Exploit::Remote::NTLM::Client
	include Exploit::Remote::HttpClient
	#
	# Constants
	#
	NTLM_CRYPT ||= Rex::Proto::NTLM::Crypt
	NTLM_CONST ||= Rex::Proto::NTLM::Constants
	NTLM_UTILS ||= Rex::Proto::NTLM::Utils
	NTLM_XCEPT ||= Rex::Proto::NTLM::Exceptions
	def initialize(info = {})
		super
		register_options(
			[
				Opt::RPORT(5985),
				OptString.new('DOMAIN', [ true, 'The domain to use for Windows authentification', 'WORKSTATION']),
				OptString.new('URI', [ true, "The URI of the WinRM service", "/wsman" ]),
				OptString.new('USERNAME', [ false, 'A specific username to authenticate as' ]),
				OptString.new('PASSWORD', [ false, 'A specific password to authenticate with' ]),
			], self.class
		)

		register_autofilter_ports([ 80,443,5985,5986 ])
		register_autofilter_services(%W{ winrm })
	end

	def winrm_poke(timeout = 20)
		opts = {
			'uri' => datastore['URI'],
			'data' => Rex::Text.rand_text_alpha(8)
		}
		c = connect(opts)
		to = opts[:timeout] || timeout
		ctype = "application/soap+xml;charset=UTF-8"
		resp, c = send_request_cgi(opts.merge({
			'uri' => opts['uri'],
			'method' => 'POST',
			'ctype'     => ctype,
			'data'        => opts['data']
		}), to)
		return resp
	end

	def parse_auth_methods(resp)
		return [] unless resp and resp.code == 401
		methods = []
		methods << "Negotiate" if resp.headers['WWW-Authenticate'].include? "Negotiate"
		methods << "Kerberos" if resp.headers['WWW-Authenticate'].include? "Kerberos"
		methods << "Basic" if resp.headers['WWW-Authenticate'].include? "Basic"
		return methods
	end

	def winrm_run_cmd(cmd, timeout=20)
		resp,c = send_request_ntlm(winrm_open_shell_msg,timeout)
		if resp.code == 401
			print_error "Login failure! Recheck supplied credentials."
			return resp .code
		end
		unless resp.code == 200
			print_error "Got unexpected response: \n #{resp.to_s}"
			retval == resp.code || 0
			return retval
		end
		shell_id = winrm_get_shell_id(resp)
		resp,c = send_request_ntlm(winrm_cmd_msg(cmd, shell_id),timeout)
		cmd_id = winrm_get_cmd_id(resp)
		resp,c = send_request_ntlm(winrm_cmd_recv_msg(shell_id,cmd_id),timeout)
		streams = winrm_get_cmd_streams(resp)
		resp,c = send_request_ntlm(winrm_terminate_cmd_msg(shell_id,cmd_id),timeout)
		resp,c = send_request_ntlm(winrm_delete_shell_msg(shell_id))
		return streams
	end

	def winrm_wql_msg(wql)
		action = winrm_uri_action("wql")
		contents = winrm_header(action) + winrm_wql_body(wql)
		msg = winrm_envelope(contents)
		return msg
	end

	def winrm_open_shell_msg
		action = winrm_uri_action("create_shell")
		options = winrm_option_set([['WINRS_NOPROFILE', 'FALSE'], ['WINRS_CODEPAGE', '437']])
		header_data = action + options
		contents = winrm_header(header_data) + winrm_open_shell_body
		msg = winrm_envelope(contents)
		return msg
	end

	def winrm_cmd_msg(cmd,shell_id)
		action = winrm_uri_action("send_cmd")
		options = winrm_option_set([['WINRS_CONSOLEMODE_STDIN', 'TRUE'], ['WINRS_SKIP_CMD_SHELL', 'FALSE']])
		selectors = winrm_selector_set([['ShellId', shell_id]])
		header_data = action + options + selectors
		contents = winrm_header(header_data) + winrm_cmd_body(cmd)
		msg = winrm_envelope(contents)
		return msg
	end

	def winrm_cmd_recv_msg(shell_id,cmd_id)
		action = winrm_uri_action("recv_cmd")
		selectors = winrm_selector_set([['ShellId', shell_id]])
		header_data = action +  selectors
		contents = winrm_header(header_data) + winrm_cmd_recv_body(cmd_id)
		msg = winrm_envelope(contents)
		return msg
	end

	def winrm_terminate_cmd_msg(shell_id,cmd_id)
		action = winrm_uri_action("signal_shell")
		selectors = winrm_selector_set([['ShellId', shell_id]])
		header_data = action +  selectors
		contents = winrm_header(header_data) + winrm_terminate_cmd_body(cmd_id)
		msg = winrm_envelope(contents)
		return msg
	end

	def winrm_delete_shell_msg(shell_id)
		action = winrm_uri_action("delete_shell")
		selectors = winrm_selector_set([['ShellId', shell_id]])
		header_data = action +  selectors
		contents = winrm_header(header_data) + winrm_empty_body
		msg = winrm_envelope(contents)
		return msg
	end

	def parse_wql_response(response)
		xml = response.body
		columns = []
		rows =[]
		rxml = REXML::Document.new(xml).root
		items = rxml.elements["///w:Items"]
		items.elements.to_a("///w:XmlFragment").each do |node|
			row_data = []
			node.elements.to_a.each do |sub_node|
				columns << sub_node.name
				row_data << sub_node.text
			end
			rows << row_data
		end
		columns.uniq!
		response_data = Rex::Ui::Text::Table.new(
			'Header'    => "#{datastore['WQL']} (#{rhost})",
			'Indent'    => 1,
			'Columns'   => columns
		)
		rows.each do |row|
			response_data << row
		end
		return response_data
	end

	def winrm_get_shell_id(response)
		xml = response.body
		shell_id = REXML::Document.new(xml).elements["//w:Selector"].text
	end

	def winrm_get_cmd_id(response)
		xml = response.body
		cmd_id = REXML::Document.new(xml).elements["//rsp:CommandId"].text
	end

	def winrm_get_cmd_streams(response)
		streams = {
			'stdout' => '',
			'stderr'   => '',
		}
		xml = response.body
		rxml = REXML::Document.new(xml).root
		rxml.elements.to_a("//rsp:Stream").each do |node|
			next if node.text.nil?
			streams[node.attributes['Name']] << Rex::Text.base64_decode(node.text)
		end
		return streams
	end

	def generate_uuid
		::Rex::Proto::DCERPC::UUID.uuid_unpack(Rex::Text.rand_text(16))
	end

	def send_request_ntlm(data, timeout = 20)
		opts = {
			'uri' => datastore['URI'],
			'data' => data,
			'username' => datastore['USERNAME'],
			'password' => datastore['PASSWORD']
		}
		ntlm_options = {
				:signing    => false,
				:usentlm2_session   => datastore['NTLM::UseNTLM2_session'],
				:use_ntlmv2     => datastore['NTLM::UseNTLMv2'],
				:send_lm    => datastore['NTLM::SendLM'],
				:send_ntlm    => datastore['NTLM::SendNTLM']
				}
		ntlmssp_flags = NTLM_UTILS.make_ntlm_flags(ntlm_options)
		workstation_name =  Rex::Text.rand_text_alpha(rand(8)+1)
		domain_name = datastore['DOMAIN']
		ntlm_message_1 = "NEGOTIATE " + Rex::Text::encode_base64(NTLM_UTILS::make_ntlmssp_blob_init( domain_name,
			workstation_name,
			ntlmssp_flags))
		to = opts[:timeout] || timeout
		begin
			c = connect(opts)
			ctype = "application/soap+xml;charset=UTF-8"
			# First request to get the challenge
			r = c.request_cgi(opts.merge({
				'uri' => opts['uri'],
				'method' => 'POST',
				'ctype'     => ctype,
				'headers' => { 'Authorization' => ntlm_message_1},
				'data'        => opts['data']
				}))
			resp = c.send_recv(r, to)
			unless resp.kind_of? Rex::Proto::Http::Response
				return [nil,nil]
			end
			return [nil,nil] if resp.code == 404
			return [nil,nil] unless resp.code == 401 && resp.headers['WWW-Authenticate']
			# Get the challenge and craft the response
			ntlm_challenge = resp.headers['WWW-Authenticate'].match(/NEGOTIATE ([A-Z0-9\x2b\x2f=]+)/i)[1]
			return [nil,nil] unless ntlm_challenge

			#old and simplier method but not compatible with windows 7/2008r2
			#ntlm_message_2 = Rex::Proto::NTLM::Message.decode64(ntlm_challenge)
			#ntlm_message_3 = ntlm_message_2.response( {:user => opts['username'],:password => opts['password']}, {:ntlmv2 => true})
			ntlm_message_2 = Rex::Text::decode_base64(ntlm_challenge)
			blob_data = NTLM_UTILS.parse_ntlm_type_2_blob(ntlm_message_2)
			challenge_key = blob_data[:challenge_key]
			server_ntlmssp_flags = blob_data[:server_ntlmssp_flags] #else should raise an error
			#netbios name
			default_name =  blob_data[:default_name] || ''
			#netbios domain
			default_domain = blob_data[:default_domain] || ''
			#dns name
			dns_host_name =  blob_data[:dns_host_name] || ''
			#dns domain
			dns_domain_name =  blob_data[:dns_domain_name] || ''
			#Client time
			chall_MsvAvTimestamp = blob_data[:chall_MsvAvTimestamp] || ''
			spnopt = {:use_spn => datastore['NTLM::SendSPN'], :name =>  self.rhost}
			resp_lm,
			resp_ntlm,
			client_challenge,
			ntlm_cli_challenge = NTLM_UTILS.create_lm_ntlm_responses(opts['username'], opts['password'], challenge_key,
				domain_name, default_name, default_domain,
				dns_host_name, dns_domain_name, chall_MsvAvTimestamp,
				spnopt, ntlm_options)
			ntlm_message_3 = NTLM_UTILS.make_ntlmssp_blob_auth(domain_name, workstation_name, opts['username'],
				resp_lm, resp_ntlm, '', ntlmssp_flags)
			ntlm_message_3 = Rex::Text::encode_base64(ntlm_message_3)
			# Send the response
			r = c.request_cgi(opts.merge({
				'uri' => opts['uri'],
				'method' => 'POST',
				'ctype'     => ctype,
				'headers' => { 'Authorization' => "NEGOTIATE #{ntlm_message_3}"},
				'data'        => opts['data']
				}))
			resp = c.send_recv(r, to, true)
			unless resp.kind_of? Rex::Proto::Http::Response
				return [nil,nil]
			end
			return [nil,nil] if resp.code == 404
			return [resp,c]
		rescue ::Errno::EPIPE, ::Timeout::Error
		end
	end

	def accepts_ntlm_auth
		parse_auth_methods(winrm_poke).include? "Negotiate"
	end

	def target_url
		proto = "http"
		if rport == 5986 or datastore['SSL']
			proto = "https"
		end
		if datastore['VHOST']
			return  "#{proto}://#{datastore ['VHOST']}:#{rport}#{@uri.to_s}"
		else
			return "#{proto}://#{rhost}:#{rport}#{@uri.to_s}"
		end
	end

	private

	def winrm_option_set(options)
		xml = "<w:OptionSet>"
		options.each do |option_pair|
			xml << winrm_option(*option_pair)
		end
		xml << "</w:OptionSet>"
		return xml
	end

	def winrm_option(name,value)
		%Q{<w:Option Name="#{name}">#{value}</w:Option>}
	end

	def winrm_selector_set(selectors)
		xml = "<w:SelectorSet>"
		selectors.each do |selector_pair|
			xml << winrm_selector(*selector_pair)
		end
		xml << "</w:SelectorSet>"
		return xml
	end

	def winrm_selector(name,value)
		%Q{<w:Selector Name="#{name}">#{value}</w:Selector>}
	end

	def winrm_wql_body(wql)
		%Q{
				<env:Body>
				<n:Enumerate>
					<w:OptimizeEnumeration xsi:nil="true"/>
					<w:MaxElements>32000</w:MaxElements>
					<w:Filter Dialect="http://schemas.microsoft.com/wbem/wsman/1/WQL">#{wql}</w:Filter>
				</n:Enumerate>
			</env:Body>
		}
	end

	def winrm_open_shell_body
		%q{<env:Body>
			<rsp:Shell>
		<rsp:InputStreams>stdin</rsp:InputStreams>
		<rsp:OutputStreams>stdout stderr</rsp:OutputStreams>
		</rsp:Shell>
		</env:Body>}
	end

	def winrm_cmd_body(cmd)
		%Q{ <env:Body>
			<rsp:CommandLine>
				<rsp:Command>&quot;#{cmd}&quot;</rsp:Command>
			</rsp:CommandLine>
		</env:Body>}
	end

	def winrm_cmd_recv_body(cmd_id)
		%Q{<env:Body>
			<rsp:Receive>
				<rsp:DesiredStream CommandId="#{cmd_id}">stdout stderr</rsp:DesiredStream>
			</rsp:Receive>
		</env:Body>}
	end

	def winrm_terminate_cmd_body(cmd_id)
		%Q{ <env:Body>
			<rsp:Signal CommandId="#{cmd_id}">
				<rsp:Code>http://schemas.microsoft.com/wbem/wsman/1/windows/shell/signal/terminate</rsp:Code>
			</rsp:Signal>
		</env:Body>}
	end

	def winrm_empty_body
		%q{<env:Body/>}
	end

	def  winrm_envelope(data)
		%Q{
			<?xml version="1.0" encoding="UTF-8"?>
			<env:Envelope xmlns:a="http://schemas.xmlsoap.org/ws/2004/08/addressing" xmlns:b="http://schemas.dmtf.org/wbem/wsman/1/cimbinding.xsd"
			xmlns:cfg="http://schemas.microsoft.com/wbem/wsman/1/config" xmlns:env="http://www.w3.org/2003/05/soap-envelope"
			xmlns:n="http://schemas.xmlsoap.org/ws/2004/09/enumeration" xmlns:p="http://schemas.microsoft.com/wbem/wsman/1/wsman.xsd"
			xmlns:rsp="http://schemas.microsoft.com/wbem/wsman/1/windows/shell" xmlns:w="http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd"
			xmlns:x="http://schemas.xmlsoap.org/ws/2004/09/transfer" xmlns:xsd="http://www.w3.org/2001/XMLSchema"
			xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> #{data}</env:Envelope>
		}
	end

	def winrm_header(data)
		%Q{
			<env:Header>
			<a:To>#{target_url}</a:To>
			<a:ReplyTo>
				<a:Address mustUnderstand="true">http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</a:Address>
			</a:ReplyTo>
			<w:MaxEnvelopeSize mustUnderstand="true">153600</w:MaxEnvelopeSize>
			<a:MessageID>uuid:#{generate_uuid}</a:MessageID>
			<w:Locale mustUnderstand="false" xml:lang="en-US"/>
			<p:DataLocale mustUnderstand="false" xml:lang="en-US"/>
			<w:OperationTimeout>PT60S</w:OperationTimeout>
			#{data}
		</env:Header>
		}
	end

	def winrm_uri_action(type)
		case type
		when "wql"
			return %q{<w:ResourceURI mustUnderstand="true">http://schemas.microsoft.com/wbem/wsman/1/wmi/root/cimv2/*</w:ResourceURI>
			<a:Action mustUnderstand="true">http://schemas.xmlsoap.org/ws/2004/09/enumeration/Enumerate</a:Action>}
		when "create_shell"
			return %q{<w:ResourceURI mustUnderstand="true">http://schemas.microsoft.com/wbem/wsman/1/windows/shell/cmd</w:ResourceURI>
			<a:Action mustUnderstand="true">http://schemas.xmlsoap.org/ws/2004/09/transfer/Create</a:Action>}
		when "send_cmd"
			return %q{<w:ResourceURI mustUnderstand="true">http://schemas.microsoft.com/wbem/wsman/1/windows/shell/cmd</w:ResourceURI>
			<a:Action mustUnderstand="true">http://schemas.microsoft.com/wbem/wsman/1/windows/shell/Command</a:Action>}
		when "recv_cmd"
			return %q{<w:ResourceURI mustUnderstand="true">http://schemas.microsoft.com/wbem/wsman/1/windows/shell/cmd</w:ResourceURI>
			<a:Action mustUnderstand="true">http://schemas.microsoft.com/wbem/wsman/1/windows/shell/Receive</a:Action>}
		when "signal_shell"
			return %q{<w:ResourceURI mustUnderstand="true">http://schemas.microsoft.com/wbem/wsman/1/windows/shell/cmd</w:ResourceURI>
			<a:Action mustUnderstand="true">http://schemas.microsoft.com/wbem/wsman/1/windows/shell/Signal</a:Action>}
		when "delete_shell"
			return %q{<w:ResourceURI mustUnderstand="true">http://schemas.microsoft.com/wbem/wsman/1/windows/shell/cmd</w:ResourceURI>
			<a:Action mustUnderstand="true">http://schemas.xmlsoap.org/ws/2004/09/transfer/Delete</a:Action>}
		end
	end

end
end
