# -*- coding: binary -*-
module Msf

###
#
# This module provides service-specific methods for the DCERPC exploit mixin
#
###
module Exploit::Remote::DCERPC_EPM

  # Contact the endpoint mapper of the target host and find the transport
  def dcerpc_endpoint_find_tcp(host, uuid, vers, transport)
    res = dcerpc_endpoint_list()
    return nil if not res

    res.each do |ent|
      if (ent[:uuid] == uuid and ent[:vers] == vers and ent[:prot] == 'tcp')
        return ent[:port]
      end
    end

    nil
  end

  # Contact the endpoint mapper of the target host and find the transport
  def dcerpc_endpoint_find_udp(host, uuid, vers, transport)
    res = dcerpc_endpoint_list()
    return nil if not res

    res.each do |ent|
      if (ent[:uuid] == uuid and ent[:vers] == vers and ent[:prot] == 'udp')
        return ent[:port]
      end
    end

    nil
  end

  # Contact the endpoint mapper and create a hash of all endpoints
  def dcerpc_endpoint_list
    res = []

    print_status("Connecting to the endpoint mapper service...")
    begin
      eps   = nil
      dport = datastore['RPORT'] || 135

      begin
        eps = Rex::Socket::Tcp.create(
          'PeerHost'  => rhost,
          'PeerPort'  => dport,
          'Proxies'   => proxies,
          'Context'   =>
          {
            'Msf'        => framework,
            'MsfExploit' => self,
          }
        )
      rescue ::Exception
      end

      if (not eps)
        print_status("Could not connect to the endpoint mapper service")
        return nil
      end

      eph = dcerpc_handle('e1af8308-5d1f-11c9-91a4-08002b14a0fa', '3.0', 'ncacn_ip_tcp', [dport])
      opt = { 'Msf' => framework, 'MsfExploit' => self }
      dce = Rex::Proto::DCERPC::Client.new(eph, eps, opt)

      hnd = nil

      while(true)

        # Placeholders
        info =
        {
          :type => nil,
          :port => nil,
          :host => nil,
          :pipe => nil,
          :prot => nil,
          :uuid => nil,
          :vers => nil,
          :note => nil
        }

        data = nil

        if(not hnd)
          # NULL handle to start with
          data = [0, 0, 0, 0, 0, 0, 0, 0, 0, 1].pack("V*")
        else
          # Break the loop early if we get another NULL handle
          break if hnd == [0, 0, 0, 0, 1].pack("V*")

          # Use the existing handle if we already have one
          data = [0, 0, 0, 0, 0].pack("V*") + hnd
        end

        ret = dce.call(2, data)

        if (
          dce.last_response == nil or
          dce.last_response.stub_data == nil or
          dce.last_response.stub_data.length < 40 or
          dce.last_response.stub_data[36,4] == "\xd6\xa0\xc9\x16"
        )
          # break from the parsing loop
          break
        end

        # Record the response data
        buf = dce.last_response.stub_data

        # Record the handle if needed
        hnd = buf[4, 20] if not hnd

        # Parse the response data
        nlen = buf[60, 4].unpack('V')[0]
        if (nlen > 1)
          info[:note] = buf[64, nlen - 1]
        end

        # Align the stub offset
        soff = nlen + 72
        while (soff % 4 != 0)
          soff += 1
        end

        # Determine number of records
        rcnt = buf[soff, 2].unpack('v')[0]
        soff += 2

        # Parse the data from the stack
        1.upto(rcnt) do |i|
          rlen = buf[soff, 2].unpack('v')[0]
          soff += 2

          if (i == 1)
            info[:uuid] = Rex::Proto::DCERPC::UUID.uuid_unpack(buf[soff+1, 16])
            info[:vers] = buf[soff+17,2].unpack('CC').map{|s| s.to_s}.join(".")
          end

          if (i > 3)
            info[:type] = buf[soff, 1].unpack("C*")[0]
          end

          soff += rlen

          xlen = buf[soff, 2].unpack('v')[0]
          soff += 2

          case info[:type]
          when nil

          # TCP
          when 7
            info[:prot] = 'tcp'
            info[:port] = buf[soff, 2].unpack('n')[0]

          # UDP
          when 8
            info[:prot] = 'udp'
            info[:port] = buf[soff, 2].unpack('n')[0]

          # ADDR
          when 9
            info[:host] = buf[soff, 4].unpack('C4').join('.')

          # PIPE
          when 15
            info[:prot] = 'pipe'
            info[:pipe] = buf[soff, xlen].unpack("a*")[0]

          # LRPC
          when 16
            info[:prot] = 'lrpc'
            info[:pipe] = buf[soff, xlen].unpack("a*")[0]

          # NETBIOS
          when 17,24
            info[:host] = buf[soff, xlen].unpack("a*")[0]

          # HTTP
          when 31
            info[:prot] = 'http'
            info[:port] = buf[soff, 2].unpack('n')[0]

          # DYNAMIC?
          when 22
            # not parsed
          else
            print_status("EPM unknown type: #{info[:type]} #{buf[soff, xlen].unpack("H*")[0]}")
          end

          soff += xlen
        end

        info[:pipe].gsub!("\x00", '') if info[:pipe]
        info[:host].gsub!("\x00", '') if info[:host]

        res << info

        # Handle a buggy response from a Likewise server that can result in a loop otherwise
        break if hnd == [0, 0, 0, 0, 0, 0, 0, 0, 0, 1].pack("V*")
      end

    rescue ::Interrupt
      raise $!

    rescue ::Exception => e
      print_status("Could not obtain the endpoint list: #{e}")
      res = nil
    end

    res
  end

end
end

