# -*- coding: binary -*-
require 'rex/encoder/ndr'
module Msf

###
#
# This mixin provides utility methods for interacting with a DCERPC service on
# a remote machine.  These methods may generally be useful in the context of
# exploitation.  This mixin extends the Tcp exploit mixin. Only one DCERPC
# service can be accessed at a time using this class.
#
###
module Exploit::Remote::DCERPC

  # Alias over the Rex DCERPC protocol modules
  DCERPCPacket   = Rex::Proto::DCERPC::Packet
  DCERPCClient   = Rex::Proto::DCERPC::Client
  DCERPCResponse = Rex::Proto::DCERPC::Response
  DCERPCUUID     = Rex::Proto::DCERPC::UUID
  NDR            = Rex::Encoder::NDR


  # Support TCP-based RPC services
  include Exploit::Remote::Tcp

  # Helper methods for specific services
  include Exploit::Remote::DCERPC_EPM
  include Exploit::Remote::DCERPC_MGMT
  include Exploit::Remote::DCERPC_LSA

  def initialize(info = {})
    super

    register_evasion_options(
      [
        OptInt.new('DCERPC::max_frag_size',   [ true, 'Set the DCERPC packet fragmentation size', 4096]),
        OptBool.new('DCERPC::fake_bind_multi', [ false, 'Use multi-context bind calls', true ]),
        OptInt.new('DCERPC::fake_bind_multi_prepend',  [ false, 'Set the number of UUIDs to prepend before the target', 0]),
        OptInt.new('DCERPC::fake_bind_multi_append',   [ false, 'Set the number of UUIDs to append the target', 0]),
        OptEnum.new('DCERPC::smb_pipeio', [ false, 'Use a different delivery method for accessing named pipes', 'rw', ['rw', 'trans']] )

      ], Msf::Exploit::Remote::DCERPC)

    register_options(
      [
        Opt::RHOST,
        Opt::RPORT(135),
      ], Msf::Exploit::Remote::DCERPC)

    register_advanced_options(
      [
        OptInt.new('DCERPC::ReadTimeout',   [ true, 'The number of seconds to wait for DCERPC responses', 10] )
      ], Msf::Exploit::Remote::DCERPC)

  end

  def dcerpc_handle(uuid, version, protocol, opts)
    dcerpc_handle_target(uuid, version, protocol, opts, rhost)
  end

  def dcerpc_handle_target(uuid, version, protocol, opts, target)
    self.handle = Rex::Proto::DCERPC::Handle.new([uuid, version], protocol, target, opts)
  end

  def dcerpc_bind(h)
    opts = { 'Msf' => framework, 'MsfExploit' => self }

    if datastore['DCERPC::max_frag_size']
      opts['frag_size'] = datastore['DCERPC::max_frag_size']
    end

    if datastore['DCERPC::fake_bind_multi']
      opts['fake_multi_bind'] = 1

      if datastore['DCERPC::fake_bind_multi_prepend']
        opts['fake_multi_bind_prepend'] = datastore['DCERPC::fake_bind_multi_prepend']
      end

      if datastore['DCERPC::fake_bind_multi_append']
        opts['fake_multi_bind_append'] = datastore['DCERPC::fake_bind_multi_append']
      end
    end

    opts['connect_timeout'] = (datastore['ConnectTimeout'] || 10).to_i

    opts['read_timeout']    = (datastore['DCERPC::ReadTimeout'] || 10).to_i


    # Configure the SMB evasion options

    if (datastore['SMBUser'])
      opts['smb_user'] = datastore['SMBUser']
    end

    if (datastore['SMBPass'])
      opts['smb_pass'] = datastore['SMBPass']
    end

    if (datastore['DCERPC::smb_pipeio'])
      opts['smb_pipeio'] = datastore['DCERPC::smb_pipeio']
    end

    if (datastore['SMB::pipe_write_min_size'])
      opts['pipe_write_min_size'] = datastore['SMB::pipe_write_min_size']
    end

    if (datastore['SMB::pipe_write_max_size'])
      opts['pipe_write_max_size'] = datastore['SMB::pipe_write_max_size']
    end

    if (datastore['SMB::pipe_read_min_size'])
      opts['pipe_read_min_size'] = datastore['SMB::pipe_read_min_size']
    end

    if (datastore['SMB::pipe_read_max_size'])
      opts['pipe_read_max_size'] = datastore['SMB::pipe_read_max_size']
    end

    if (self.respond_to?('simple') and self.simple)
      opts['smb_client'] = self.simple
    end

    # Create the DCERPC client
    self.dcerpc = Rex::Proto::DCERPC::Client.new(h, self.sock, opts)

    if (self.handle.protocol == 'ncacn_np' and not self.simple)
      self.simple = self.dcerpc.smb  # expose the simple client if we have access to it
    end
  end

  def dcerpc_call(function, stub = '', timeout=nil, do_recv=true)
    otimeout = dcerpc.options['read_timeout']

    begin
      dcerpc.options['read_timeout'] = timeout if timeout
      dcerpc.call(function, stub, do_recv)
    rescue ::Rex::Proto::SMB::Exceptions::NoReply, Rex::Proto::DCERPC::Exceptions::NoResponse
      print_status("The DCERPC service did not reply to our request")
      return
    ensure
      dcerpc.options['read_timeout'] = otimeout
    end
  end

  # https://github.com/CoreSecurity/impacket/blob/master/examples/getArch.py
  # https://msdn.microsoft.com/en-us/library/cc243948.aspx#Appendix_A_53
  def dcerpc_getarch
    ret = nil

    connect_timeout = (datastore['ConnectTimeout'] || 10).to_i
    read_timeout    = (datastore['DCERPC::ReadTimeout'] || 10).to_i

    pkt = Rex::Proto::DCERPC::Packet.make_bind(
      # Abstract Syntax: EPMv4 V3.0
      'e1af8308-5d1f-11c9-91a4-08002b14a0fa', '3.0',
      # Transfer Syntax[1]: 64bit NDR V1
      '71710533-beba-4937-8319-b5dbef9ccc36', '1.0'
    ).first

    begin
      nsock = Rex::Socket::Tcp.create(
        'PeerHost'     => rhost,
        'PeerPort'     => 135,
        'Proxies'      => proxies,
        'Timeout'      => connect_timeout,
        'Context'      => {
          'Msf'        => framework,
          'MsfExploit' => self
        }
      )
    rescue Rex::ConnectionError => e
      vprint_error(e.to_s)
      return nil
    end

    nsock.put(pkt)

    begin
      res = nsock.get_once(60, read_timeout)
    rescue EOFError
      vprint_error('DCE/RPC socket returned EOFError')
      return nil
    end

    disconnect(nsock)

    begin
      resp = Rex::Proto::DCERPC::Response.new(res)
    rescue Rex::Proto::DCERPC::Exceptions::InvalidPacket => e
      vprint_error(e.to_s)
      return nil
    end

    # Ack result: Acceptance (0)
    if resp.ack_result.first == 0
      ret = ARCH_X64
    end
    # Ack result: Provider rejection (2)
    # Ack reason: Proposed transfer syntaxes not supported (2)
    if resp.ack_result.first == 2 && resp.ack_reason.first == 2
      ret = ARCH_X86
    end

    ret
  end

  # Convert a standard ASCII string to 16-bit Unicode
  def unicode(str)
    Rex::Text.to_unicode(str)
  end

  # Useful accessors for tracking DCERPC state
  attr_accessor	:handle, :dcerpc

end

end
