# -*- coding: binary -*-
require 'uri'
require 'digest'

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)
    send_winrm_request(Rex::Text.rand_text_alpha(8), timeout)
  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 = send_winrm_request(winrm_open_shell_msg,timeout)
    if resp.nil?
      print_error "Received no reply from server"
      return nil
    end
    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 = send_winrm_request(winrm_cmd_msg(cmd, shell_id),timeout)
    cmd_id = winrm_get_cmd_id(resp)
    resp = send_winrm_request(winrm_cmd_recv_msg(shell_id,cmd_id),timeout)
    streams = winrm_get_cmd_streams(resp)
    resp = send_winrm_request(winrm_terminate_cmd_msg(shell_id,cmd_id),timeout)
    resp = send_winrm_request(winrm_delete_shell_msg(shell_id))
    return streams
  end

  def winrm_run_cmd_hanging(cmd, timeout=20)
    resp = send_winrm_request(winrm_open_shell_msg,timeout)
    if resp.nil?
      print_error "Received no reply from server"
      return nil
    end
    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 = send_winrm_request(winrm_cmd_msg(cmd, shell_id),timeout)
    cmd_id = winrm_get_cmd_id(resp)
    resp = send_winrm_request(winrm_cmd_recv_msg(shell_id,cmd_id),timeout)
    streams = winrm_get_cmd_streams(resp)
    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)
    return nil if response.nil?
    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::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)
    return nil if response.nil?
    xml = response.body
    shell_id = REXML::Document.new(xml).elements["//w:Selector"].text
  end

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

  def winrm_get_cmd_streams(response)
    return nil if response.nil?
    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.decode_base64(node.text)
    end
    return streams
  end

  def generate_uuid
    ::Rex::Proto::DCERPC::UUID.uuid_unpack(Rex::Text.rand_text(16))
  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

  def wmi_namespace
    return datastore['NAMESPACE'] if datastore['NAMESPACE']
    return @namespace_override if @namespace_override
    return "/root/cimv2/"
  end

  def send_winrm_request(data, timeout=20)
    opts = {
      'uri' => datastore['URI'],
      'method' => 'POST',
       'data' => data,
       'username' => datastore['USERNAME'],
      'password' => datastore['PASSWORD'],
      'ctype' => "application/soap+xml;charset=UTF-8"
    }
    send_request_cgi(opts,timeout)
  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#{wmi_namespace}*</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
