rapid7/metasploit-framework

View on GitHub
lib/msf/core/exploit/remote/smb/client.rb

Summary

Maintainability
F
5 days
Test Coverage
# -*- coding: binary -*-
require 'rex/encoder/ndr'
require 'recog'

module Msf
  module Exploit::Remote::SMB
    # This mixin provides utility methods for interacting with a SMB/CIFS 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 SMB
    # service can be accessed at a time using this class.
    module Client

      include Msf::Exploit::Remote::Tcp
      include Msf::Exploit::Remote::NTLM::Client

      # These constants are unused here, but may be used in some code that
      # includes this. Local definitions should be preferred.
      SIMPLE = Rex::Proto::SMB::SimpleClient
      XCEPT  = Rex::Proto::SMB::Exceptions
      CONST  = Rex::Proto::SMB::Constants

      # 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

      def initialize(info = {})
        super

        register_evasion_options(
        [
          OptBool.new('SMB::pipe_evasion',     [ true, 'Enable segmented read/writes for SMB Pipes', false]),
          OptInt.new('SMB::pipe_write_min_size', [ true, 'Minimum buffer size for pipe writes',  1]),
          OptInt.new('SMB::pipe_write_max_size', [ true, 'Maximum buffer size for pipe writes', 1024]),
          OptInt.new('SMB::pipe_read_min_size',  [ true, 'Minimum buffer size for pipe reads',  1]),
          OptInt.new('SMB::pipe_read_max_size',  [ true, 'Maximum buffer size for pipe reads', 1024]),
          OptInt.new('SMB::pad_data_level',  [ true, 'Place extra padding between headers and data (level 0-3)', 0]),
          OptInt.new('SMB::pad_file_level',  [ true, 'Obscure path names used in open/create (level 0-3)', 0]),
          OptInt.new('SMB::obscure_trans_pipe_level',  [ true, 'Obscure PIPE string in TransNamedPipe (level 0-3)', 0]),

        ], Msf::Exploit::Remote::SMB::Client)

        register_advanced_options(
        [
          OptBool.new('SMBDirect', [ false, 'The target port is a raw SMB service (not NetBIOS)', true ]),
          OptString.new('SMBUser', [ false, 'The username to authenticate as', ''], fallbacks: ['USERNAME']),
          OptString.new('SMBPass', [ false, 'The password for the specified username', ''], fallbacks: ['PASSWORD']),
          OptString.new('SMBDomain',  [ false, 'The Windows domain to use for authentication', '.'], fallbacks: ['DOMAIN']),
          OptString.new('SMBName', [ true, 'The NetBIOS hostname (required for port 139 connections)', '*SMBSERVER']),
          OptBool.new('SMB::VerifySignature', [ true, "Enforces client-side verification of server response signatures", false]),
          OptInt.new('SMB::ChunkSize', [ true, 'The chunk size for SMB segments, bigger values will increase speed but break NT 4.0 and SMB signing', 500]),
          #
          # Control the identified operating system of the client
          #
          OptString.new('SMB::Native_OS', [ true, 'The Native OS to send during authentication', 'Windows 2000 2195']),
          OptString.new('SMB::Native_LM', [ true, 'The Native LM to send during authentication', 'Windows 2000 5.0']),
          OptString.new(
            'SMB::ProtocolVersion',
            [
              true,
              'One or a list of coma-separated SMB protocol versions to '\
              'negotiate (e.g. "1" or "1,2" or "2,3,1")', '1,2,3'
            ],
            regex: '^[123](?:,[123])*$'
          ),
          OptBool.new(
            'SMB::AlwaysEncrypt',
            [
              true,
              'Enforces encryption even if the server does not require it (SMB3.x only). '\
              'Note that when it is set to false, the SMB client will still '\
              'encrypt the communication if the server requires it',
              true
            ]
          )
        ], Msf::Exploit::Remote::SMB::Client)

        register_options(
        [
          Opt::RHOST,
          OptPort.new('RPORT', [ true, 'The SMB service port', 445])
        ], Msf::Exploit::Remote::SMB::Client)

        register_autofilter_ports([ 139, 445])
        register_autofilter_services(%W{ netbios-ssn microsoft-ds })
      end

      # Override {Exploit::Remote::Tcp#connect} to setup an SMB connection
      # and configure evasion options
      #
      # Also populates {#simple}.
      #
      # @param (see Exploit::Remote::Tcp#connect)
      # @return (see Exploit::Remote::Tcp#connect)
      def connect(global=true, versions: [], backend: nil)
        if versions.nil? || versions.empty?
          versions = datastore['SMB::ProtocolVersion'].split(',').map(&:strip).reject(&:blank?).map(&:to_i)
          # if the user explicitly set the protocol version to 1, still use ruby_smb
          backend ||= :ruby_smb if versions == [1]
        end

        disconnect() if global

        s = super(global, {'SSL' => false})
        self.sock = s if global

        # Disable direct SMB when SMBDirect has not been set
        # and the destination port is configured as 139
        direct = smb_direct
        if(datastore.default?('SMBDirect') and rport.to_i == 139)
          direct = false
        end

        c = Rex::Proto::SMB::SimpleClient.new(s, direct, versions, always_encrypt: datastore['SMB::AlwaysEncrypt'], backend: backend)

        # setup pipe evasion foo
        if datastore['SMB::pipe_evasion']
          # XXX - insert code to change the instance of the read/write functions to do segmentation
        end

        if (datastore['SMB::pad_data_level'])
          c.client.evasion_opts['pad_data'] = datastore['SMB::pad_data_level']
        end

        if (datastore['SMB::pad_file_level'])
          c.client.evasion_opts['pad_file'] = datastore['SMB::pad_file_level']
        end

        if (datastore['SMB::obscure_trans_pipe_level'])
          c.client.evasion_opts['obscure_trans_pipe'] = datastore['SMB::obscure_trans_pipe_level']
        end

        self.simple = c if global
        c
      end

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

      # Establishes an SMB session over the default socket and connects to
      # the IPC$ share.
      #
      # You should call {#connect} before calling this
      #
      # @param simple_client [Rex::Proto::SMB::SimpleClient] Optional SimpleClient instance to use
      # @return [void]
      def smb_login(simple_client = self.simple)
        # Override the default RubySMB capabilities with Kerberos authentication
        if datastore['SMB::Auth'] == Msf::Exploit::Remote::AuthOption::KERBEROS
          fail_with(Msf::Exploit::Failure::BadConfig, 'The Smb::Rhostname option is required when using Kerberos authentication.') if datastore['Smb::Rhostname'].blank?
          fail_with(Msf::Exploit::Failure::BadConfig, 'The SMBDomain option is required when using Kerberos authentication.') if datastore['SMBDomain'].blank?
          offered_etypes = Msf::Exploit::Remote::AuthOption.as_default_offered_etypes(datastore['Smb::KrbOfferedEncryptionTypes'])
          fail_with(Msf::Exploit::Failure::BadConfig, 'At least one encryption type is required when using Kerberos authentication.') if offered_etypes.empty?

          kerberos_authenticator = Msf::Exploit::Remote::Kerberos::ServiceAuthenticator::SMB.new(
            host: datastore['DomainControllerRhost'].blank? ? nil : datastore['DomainControllerRhost'],
            hostname: datastore['Smb::Rhostname'],
            proxies: datastore['Proxies'],
            realm: datastore['SMBDomain'],
            username: datastore['SMBUser'],
            password: datastore['SMBPass'],
            framework: framework,
            framework_module: self,
            cache_file: datastore['Smb::Krb5Ccname'].blank? ? nil : datastore['Smb::Krb5Ccname'],
            ticket_storage: kerberos_ticket_storage,
            offered_etypes: offered_etypes
          )

          simple_client.client.extend(Msf::Exploit::Remote::SMB::Client::KerberosAuthentication)
          simple_client.client.kerberos_authenticator = kerberos_authenticator
        end

        simple_client.login(
          datastore['SMBName'],
          datastore['SMBUser'],
          datastore['SMBPass'],
          datastore['SMBDomain'],
          datastore['SMB::VerifySignature'],
          datastore['NTLM::UseNTLMv2'],
          datastore['NTLM::UseNTLM2_session'],
          datastore['NTLM::SendLM'],
          datastore['NTLM::UseLMKey'],
          datastore['NTLM::SendNTLM'],
          datastore['SMB::Native_OS'],
          datastore['SMB::Native_LM'],
          {:use_spn => datastore['NTLM::SendSPN'], :name =>  self.rhost}
        )
        # XXX: Any reason to connect to the IPC$ share in this method?
        simple_client.connect("\\\\#{datastore['RHOST']}\\IPC$")
      end

      # This method returns the native operating system of the peer
      def smb_peer_os
        unless self.simple.negotiated_smb_version == 1
          print_warning("peer_native_os is only available with SMB1 (current version: SMB#{self.simple.negotiated_smb_version})")
        end
        self.simple.client.peer_native_os
      end

      # This method returns the native lanman version of the peer
      def smb_peer_lm
        unless self.simple.negotiated_smb_version == 1
          print_warning("peer_native_lm is only available with SMB1 (current version: SMB#{self.simple.negotiated_smb_version})")
        end
        self.simple.client.peer_native_lm
      end

      # This method opens a handle to an IPC pipe
      def smb_create(pipe)
        self.simple.create_pipe(pipe)
      end

      #the default chunk size of 48000 for OpenFile is not compatible when signing is enabled (and with some nt4 implementations)
      #cause it looks like MS windows refuse to sign big packet and send STATUS_ACCESS_DENIED
      #fd.chunk_size = 500 is better
      def smb_open(path, perm, read: true, write: false)
        self.simple.open(path, perm, datastore['SMB::ChunkSize'], read: read, write: write)
      end

      def smb_hostname
        datastore['SMBName'] || '*SMBSERVER'
      end

      def smb_direct
        datastore['SMBDirect']
      end

      def domain
        datastore['SMBDomain']
      end

      def smbhost
        if domain == "."
          "#{rhost}:#{rport}"
        else
          "#{rhost}:#{rport}|#{domain}"
        end
      end

      # If the username contains a / slash, then
      # split it as a domain/username. NOTE: this
      # is predicated on forward slashes, and not
      # Microsoft's backwards slash convention.
      def domain_username_split(user)
        return user if(user.nil? || user.empty?)
        if !user[/\//] # Only /, not \!
          return [nil,user]
        else
          return user.split("/",2)
        end
      end

      def splitname(uname)
        if datastore["PRESERVE_DOMAINS"]
          d,u = domain_username_split(uname)
          return u
        else
          return uname
        end
      end

      # Whether a remote file exists
      #
      # @param file [String] Path to a file to remove, relative to the
      #   most-recently connected share
      # @raise [Rex::Proto::SMB::Exceptions::ErrorCode]
      def smb_file_exist?(file)
        begin
          fd = simple.open(file, 'o')
        rescue RubySMB::Error::UnexpectedStatusCode => e
          found = false
        rescue Rex::Proto::SMB::Exceptions::ErrorCode => e
          # If attempting to open the file results in a "*_NOT_FOUND" error,
          # then we can be sure the file is not there.
          #
          # Copy-pasted from smb/exceptions.rb to avoid the gymnastics
          # required to pull them out of a giant inverted hash
          #
          # 0xC0000034 => "STATUS_OBJECT_NAME_NOT_FOUND",
          # 0xC000003A => "STATUS_OBJECT_PATH_NOT_FOUND",
          # 0xC0000225 => "STATUS_NOT_FOUND",
          error_is_not_found = [ 0xC0000034, 0xC000003A, 0xC0000225 ].include?(e.error_code)
          # If the server returns some other error, then there was a
          # permissions problem or some other difficulty that we can't
          # really account for and hope the caller can deal with it.
          raise e unless error_is_not_found
          found = !error_is_not_found
        else
          # There was no exception, so we know the file is openable
          fd.close
          found = true
        end

        found
      end

      # Remove remote file
      #
      # @param file (see #smb_file_exist?)
      # @return [void]
      def smb_file_rm(file)
        fd = smb_open(file, 'ro')
        fd.delete
      end


      #
      # Fingerprinting methods
      #


      # Calls the EnumPrinters() function of the spooler service
      def smb_enumprinters(flags, name, level, blen)
        stub =
          NDR.long(flags) +
          (name ? NDR.uwstring(name) : NDR.long(0)) +
          NDR.long(level) +
          NDR.long(rand(0xffffffff)+1)+
          NDR.long(blen) +
          "\x00" * blen +
          NDR.long(blen)

        handle = dcerpc_handle(
          '12345678-1234-abcd-ef00-0123456789ab', '1.0',
          'ncacn_np', ["\\SPOOLSS"]
        )

        begin
          dcerpc_bind(handle)
          dcerpc.call(0x00, stub)
          return dcerpc.last_response.stub_data
        rescue ::Interrupt
          raise $!
        rescue ::Exception => e
          return nil
        end
      end

      # This method dumps the print provider strings from the spooler
      def smb_enumprintproviders
        resp = smb_enumprinters(8, nil, 1, 0)
        return nil if not resp
        rptr, tmp, blen = resp.unpack("V*")

        resp = smb_enumprinters(8, nil, 1, blen)
        return nil if not resp

        bcnt,pcnt,stat = resp[-12, 12].unpack("VVV")
        return nil if stat != 0
        return nil if pcnt == 0
        return nil if bcnt > blen
        return nil if pcnt < 3

        #
        # The correct way, which leads to invalid offsets :-(
        #
        #providers = []
        #
        #0.upto(pcnt-1) do |i|
        # flags,desc_o,name_o,comm_o = resp[8 + (i*16), 16].unpack("VVVV")
        #
        # #desc = read_unicode(resp,8+desc_o).gsub("\x00", '')
        # #name = read_unicode(resp,8+name_o).gsub("\x00", '')
        # #comm = read_unicode(resp,8+comm_o).gsub("\x00", '')
        # #providers << [flags,desc,name,comm]
        #end
        #
        #providers

        return resp

      end

      # This method performs an extensive set of fingerprinting operations
      def smb_fingerprint
        fprint = {}

        # Connect to the server if needed
        if not self.simple
          # native_lm/native_os is only available with SMB1
          vprint_status('Force SMB1 since SMB fingerprint needs native_lm/native_os information')
          connect(versions: [1])
          # The login method can throw any number of exceptions, we don't
          # care since we still get the native_lm/native_os.
          begin
            smb_login()
          rescue ::Rex::Proto::SMB::Exceptions::NoReply,
                 ::Rex::Proto::SMB::Exceptions::ErrorCode,
                 ::Rex::Proto::SMB::Exceptions::LoginError => e
            dlog("Error with SMB fingerprint: #{e.message}")
          end
        end

        fprint['native_os'] = smb_peer_os()
        fprint['native_lm'] = smb_peer_lm()

        # Leverage Recog for SMB native OS fingerprinting
        fp_match = Recog::Nizer.match('smb.native_os', fprint['native_os']) || { }

        os = fp_match['os.product'] || 'Unknown'
        sp = fp_match['os.version'] || ''

        # Metasploit prefers 'Windows 2003' vs 'Windows Server 2003'
        if os =~ /^Windows Server/
          os = os.sub(/^Windows Server/, 'Windows')
        end

        if fp_match['os.edition']
          fprint['edition'] = fp_match['os.edition']
        end

        if fp_match['os.build']
          fprint['build'] = fp_match['os.build']
        end

        if sp == ''
          sp = smb_fingerprint_windows_sp(os)
        end

        lang = smb_fingerprint_windows_lang

        fprint['os']   = os
        fprint['sp']   = sp
        fprint['lang'] = lang

        fprint
      end

      #
      # Determine the service pack level of a Windows system via SMB probes
      #
      def smb_fingerprint_windows_sp(os)
        sp = ''

        if (os == 'Windows XP')
          # SRVSVC was blocked in SP2
          begin
            smb_create("\\SRVSVC")
            sp = 'Service Pack 0 / 1'
          rescue ::Rex::Proto::SMB::Exceptions::ErrorCode => e
            if (e.error_code == 0xc0000022)
              sp = 'Service Pack 2+'
            end
          end
        end

        if (os == 'Windows 2000' and sp.length == 0)
          # LLSRPC was blocked in a post-SP4 update
          begin
            smb_create("\\LLSRPC")
            sp = 'Service Pack 0 - 4'
          rescue ::Rex::Proto::SMB::Exceptions::ErrorCode => e
            if (e.error_code == 0xc0000022)
              sp = 'Service Pack 4 with MS05-010+'
            end
          end
        end

        #
        # Perform granular XP SP checks if LSARPC is exposed
        #
        if (os == 'Windows XP')

          #
          # Service Pack 2 added a range(0,64000) to opnum 0x22 in SRVSVC
          # Credit to spoonm for first use of unbounded [out] buffers
          #
          handle = dcerpc_handle(
            '4b324fc8-1670-01d3-1278-5a47bf6ee188', '3.0',
            'ncacn_np', ["\\BROWSER"]
          )

          begin
            dcerpc_bind(handle)

            stub =
              NDR.uwstring(Rex::Text.rand_text_alpha(rand(10)+1)) +
              NDR.wstring(Rex::Text.rand_text_alpha(rand(10)+1))  +
              NDR.long(64001) +
              NDR.long(0) +
              NDR.long(0)

            dcerpc.call(0x22, stub)
            sp = "Service Pack 0 / 1"

          rescue ::Interrupt
            raise $!
          rescue ::Rex::Proto::SMB::Exceptions::ErrorCode
          rescue ::Rex::Proto::SMB::Exceptions::ReadPacket
          rescue ::Rex::Proto::DCERPC::Exceptions::Fault
            sp = "Service Pack 2+"
          rescue ::Exception
          end


          #
          # Service Pack 3 fixed information leaks via [unique][out] pointers
          # Call SRVSVC::NetRemoteTOD() to return [out] [ref] [unique]
          # Credit:
          #   Pointer leak is well known, but Immunity also covered in a paper
          #   Silent fix of pointer leak in SP3 and detection method by Rhys Kidd
          #
          handle = dcerpc_handle(
            '4b324fc8-1670-01d3-1278-5a47bf6ee188', '3.0',
            'ncacn_np', ["\\BROWSER"]
          )

          begin
            dcerpc_bind(handle)

            stub = NDR.uwstring(Rex::Text.rand_text_alpha(rand(8)+1))
            resp = dcerpc.call(0x1c, stub)

            if(resp and resp[0,4] == "\x00\x00\x02\x00")
              sp = "Service Pack 3"
            else
              if(resp and sp =~ /Service Pack 2\+/)
                sp = "Service Pack 2"
              end
            end

          rescue ::Interrupt
            raise $!
          rescue ::Rex::Proto::SMB::Exceptions::ErrorCode
          rescue ::Rex::Proto::SMB::Exceptions::ReadPacket
          rescue ::Exception
          end
        end

        sp
      end


      #
      # Determine the native language pack of a Windows system via SMB probes
      #
      def smb_fingerprint_windows_lang

        #
        # Remote language detection via Print Providers
        # Credit: http://immunityinc.com/downloads/Remote_Language_Detection_in_Immunity_CANVAS.odt
        #

        lang = 'Unknown'

        sigs =
        {
          'English' =>
            [
              Rex::Text.to_unicode('Windows NT Remote Printers'),
              Rex::Text.to_unicode('LanMan Print Services')
            ],
          'Spanish' =>
            [
              Rex::Text.to_unicode('Impresoras remotas Windows NT'),
              Rex::Text.to_unicode('Impresoras remotas de Windows NT')
            ],
          'Italian' =>
            [
              Rex::Text.to_unicode('Stampanti remote di Windows NT'),
              Rex::Text.to_unicode('Servizi di stampa LanMan')
            ],
          'French' =>
            [
              Rex::Text.to_unicode('Imprimantes distantes NT'),
              Rex::Text.to_unicode('Imprimantes distantes pour Windows NT'),
              Rex::Text.to_unicode("Services d'impression LanMan")
            ],
          'German' =>
            [
              Rex::Text.to_unicode('Remotedrucker')
            ],
          'Portuguese - Brazilian' =>
            [
              Rex::Text.to_unicode('Impr. remotas Windows NT'),
              Rex::Text.to_unicode('Impressoras remotas do Windows NT')
            ],
          'Portuguese' =>
            [
              Rex::Text.to_unicode('Imp. remotas do Windows NT')
            ],
          'Hungarian' =>
            [
              Rex::Text.to_unicode("\x54\xe1\x76\x6f\x6c\x69\x20\x6e\x79\x6f\x6d\x74\x61\x74\xf3\x6b")
            ],
          'Finnish' =>
            [
              Rex::Text.to_unicode("\x45\x74\xe4\x74\x75\x6c\x6f\x73\x74\x69\x6d\x65\x74")
            ],
          'Dutch' =>
            [
              Rex::Text.to_unicode('Externe printers voor NT')
            ],
          'Danish' =>
            [
              Rex::Text.to_unicode('Fjernprintere')
            ],
          'Swedish' =>
            [
              Rex::Text.to_unicode("\x46\x6a\xe4\x72\x72\x73\x6b\x72\x69\x76\x61\x72\x65")
            ],
          'Polish' =>
            [
              Rex::Text.to_unicode('Zdalne drukarki')
            ],
          'Czech'   =>
            [
              Rex::Text.to_unicode("\x56\x7a\x64\xe1\x6c\x65\x6e\xe9\x20\x74\x69\x73\x6b\xe1\x72\x6e\x79")
            ],
          'Turkish' =>
            [
              "\x59\x00\x61\x00\x7a\x00\x31\x01\x63\x00\x31\x01\x6c\x00\x61\x00\x72\x00"
            ],
          'Japanese' =>
            [
              "\xea\x30\xe2\x30\xfc\x30\xc8\x30\x20\x00\xd7\x30\xea\x30\xf3\x30\xbf\x30"
            ],
          'Chinese - Traditional' =>
            [
              "\xdc\x8f\x0b\x7a\x53\x62\x70\x53\x3a\x67"
            ],
          'Chinese - Traditional / Taiwan' =>
            [
              "\x60\x90\xef\x7a\x70\x53\x68\x88\x5f\x6a",
            ],
          'Korean' =>
            [
              "\xd0\xc6\xa9\xac\x20\x00\x04\xd5\xb0\xb9\x30\xd1",
            ],
          'Russian' =>
            [
              "\x1f\x04\x40\x04\x38\x04\x3d\x04\x42\x04\x35\x04\x40\x04\x4b\x04\x20\x00\x43\x04\x34\x04\x30\x04\x3b\x04\x35\x04\x3d\x04\x3d\x04\x3e\x04\x33\x04\x3e\x04\x20\x00\x34\x04\x3e\x04\x41\x04\x42\x04\x43\x04\x3f\x04\x30\x04",
            ],

        }

        begin
          prov = smb_enumprintproviders()
          if(prov)
            sigs.each_key do |k|
              sigs[k].each do |s|
                if(prov.index(s))
                  lang = k
                  break
                end
                break if lang != 'Unknown'
              end
              break if lang != 'Unknown'
            end

            if(lang == 'Unknown')

              @fpcache ||= {}
              mhash = ::Digest::MD5.hexdigest(prov[4,prov.length-4])

              if(not @fpcache[mhash])

                buff = "\n"
                buff << "*** NEW FINGERPRINT: PLEASE SEND TO [ msfdev[at]metasploit.com ]\n"
                buff << " VERS: $Revision$\n"
                buff << " HOST: #{rhost}\n"
                buff << "   OS: #{os}\n"
                buff << "   SP: #{sp}\n"

                prov.unpack("H*")[0].scan(/.{64}|.*/).each do |line|
                  next if line.length == 0
                  buff << "   FP: #{line}\n"
                end

                prov.split(/\x00\x00+/n).each do |line|
                  line.gsub!("\x00",'')
                  line.strip!
                  next if line.length < 6

                  buff <<  "  TXT: #{line}\n"
                end

                buff << "*** END FINGERPRINT\n"

                print_line(buff)

                @fpcache[mhash] = true
              end

            end
          end
        rescue ::Interrupt
          raise $!
        rescue ::Rex::Proto::SMB::Exceptions::ErrorCode
        end
        lang
      end

      # Map an integer share type to a human friendly descriptor
      def smb_lookup_share_type(val)
        [ 'DISK', 'PRINTER', 'DEVICE', 'IPC', 'SPECIAL', 'TEMPORARY' ][val]
      end

      # Retrieve detailed information about a specific share using any available method
      def smb_netsharegetinfo(share)
       smb_srvsvc_netsharegetinfo(share)
      end

      # Retrieve detailed share dinformation via the NetShareGetInfo function in the Server Service
      def smb_srvsvc_netsharegetinfo(share)
        shares = []
        simple.connect("\\\\#{rhost}\\IPC$")
        handle = dcerpc_handle('4b324fc8-1670-01d3-1278-5a47bf6ee188', '3.0', 'ncacn_np', ["\\srvsvc"])
        begin
          dcerpc_bind(handle)
        rescue Rex::Proto::SMB::Exceptions::ErrorCode, RubySMB::Error::RubySMBError => e
          vprint_error(e.message)
          return []
        end

        stubdata =
          NDR.uwstring("\\\\#{rhost}") +
          NDR.wstring(share) +
          NDR.long(2)

        response = dcerpc.call(0x10, stubdata)

        if ! response
          raise RuntimeError, "Invalid DCERPC response: <empty>"
        end

        head = response.slice!(0, 40)
        if head.length != 40
          raise RuntimeError, "Invalid DCERPC response: not enough data"
        end

        share_info = {
          share_type: head[12, 4].unpack('V').first,
          permissions: head[20, 4].unpack('V').first,
          max_users: head[24, 4].unpack('V').first,
        }

        idx = 0

        [:share, :comment, :path, :password].each do |field|
          field_info = response[idx, 12].unpack("V*")
          break if field_info.length == 0
          idx += 12

          field_text = response[idx, field_info.first * 2]
          share_info[ field ] = field_text.gsub("\x00", '')
          idx += (field_info.first * 2)
          idx += (idx % 4)
        end

        share_info
      end

      # Retrieve a list of all shares using any available method
      def smb_netshareenumall
        begin
          return smb_srvsvc_netshareenumall
        rescue Rex::Proto::SMB::Exceptions::ErrorCode, RubySMB::Error::RubySMBError => e
          vprint_error("Warning: NetShareEnumAll failed via Server Service: #{e}")
          return [] if self.simple.client.is_a?(RubySMB::Client)
          vprint_error("Falling back to LANMAN")
          return smb_lanman_netshareenumall
        end
      end

      # Retrieve a list of shares via the NetShareEnumAll function in the Server Service
      def smb_srvsvc_netshareenumall
        shares = []
        simple.connect("\\\\#{rhost}\\IPC$")
        handle = dcerpc_handle('4b324fc8-1670-01d3-1278-5a47bf6ee188', '3.0', 'ncacn_np', ["\\srvsvc"])
        begin
          dcerpc_bind(handle)
        rescue Rex::Proto::SMB::Exceptions::ErrorCode, RubySMB::Error::RubySMBError => e
          vprint_error(e.message)
          return []
        end

        stubdata =
          NDR.uwstring("\\\\#{rhost}") +
          NDR.long(1)  #level

        ref_id = stubdata[0,4].unpack("V")[0]
        ctr = [1, ref_id + 4 , 0, 0].pack("VVVV")

        stubdata << ctr
        stubdata << NDR.align(ctr)
        stubdata << ["FFFFFFFF"].pack("H*")
        stubdata << [ref_id + 8, 0].pack("VV")
        response = dcerpc.call(0x0f, stubdata)
        res = response.dup
        win_error = res.slice!(-4, 4).unpack("V")[0]

        if win_error != 0
          raise RuntimeError, "Invalid DCERPC response: win_error = #{win_error}"
        end

        # Remove unused data
        res.slice!(0,12) # level, CTR header, Reference ID of CTR
        share_count = res.slice!(0, 4).unpack("V")[0]
        res.slice!(0,4) # Reference ID of CTR1
        share_max_count = res.slice!(0, 4).unpack("V")[0]

        if share_max_count != share_count
          raise RuntimeError, "Invalid DCERPC response: count != count max (#{share_count}/#{share_max_count})"
        end

        # ReferenceID / Type / ReferenceID of Comment
        types = res.slice!(0, share_count * 12).scan(/.{12}/n).map{|a| a[4,2].unpack("v")[0]}

        share_count.times do |t|
          length, offset, max_length = res.slice!(0, 12).unpack("VVV")
          if offset != 0
            raise RuntimeError, "Invalid DCERPC response: offset != 0 (#{offset})"
          end

          if length != max_length
            raise RuntimeError, "Invalid DCERPC response: length !=max_length (#{length}/#{max_length})"
          end
          name = res.slice!(0, 2 * length).gsub('\x00','')
          res.slice!(0,2) if length % 2 == 1 # pad

          comment_length, comment_offset, comment_max_length = res.slice!(0, 12).unpack("VVV")

          if comment_offset != 0
            raise RuntimeError, "Invalid DCERPC response: comment_offset != 0 (#{comment_offset})"
          end

          if comment_length != comment_max_length
            raise RuntimeError, "Invalid DCERPC response: comment_length != comment_max_length (#{comment_length}/#{comment_max_length})"
          end

          comment = res.slice!(0, 2 * comment_length)

          res.slice!(0,2) if comment_length % 2 == 1 # pad

          name    = Rex::Text.to_ascii(name).gsub("\x00", "")
          s_type  = smb_lookup_share_type(types[t])
          comment = Rex::Text.to_ascii(comment).gsub("\x00", "")

          shares << [ name, s_type, comment ]
        end

        shares
      end

      # Retrieve a list of shares via the NetShareEnumAll function in the LANMAN service
      # This method can only return shares with names 12 bytes or less
      def smb_lanman_netshareenumall
        shares = []
        begin
          # XXX: #trans is not supported by RubySMB
          res = self.simple.client.trans(
            "\\PIPE\\LANMAN",
            (
              [0x00].pack('v') +
              "WrLeh\x00"   +
              "B13BWz\x00"  +
              [0x01, 65406].pack("vv")
            ))
        rescue ::Rex::Proto::SMB::Exceptions::ErrorCode => e
          vprint_error("Could not enumerate shares via LANMAN")
          return []
        end
        if res.nil?
          vprint_error("Could not enumerate shares via LANMAN")
          return []
        end

        lerror, lconv, lentries, lcount = res['Payload'].to_s[
          res['Payload'].v['ParamOffset'],
          res['Payload'].v['ParamCount']
        ].unpack("v4")

        data = res['Payload'].to_s[
          res['Payload'].v['DataOffset'],
          res['Payload'].v['DataCount']
        ]

        0.upto(lentries - 1) do |i|
          sname,tmp = data[(i * 20) +  0, 14].split("\x00")
          stype     = data[(i * 20) + 14, 2].unpack('v')[0]
          scoff     = data[(i * 20) + 16, 2].unpack('v')[0]
          scoff -= lconv if lconv != 0
          scomm,tmp = data[scoff, data.length - scoff].split("\x00")
          shares << [ sname, smb_lookup_share_type(stype), scomm]
        end

        shares
      end

      # @return [Rex::Proto::SMB::SimpleClient]
      attr_accessor :simple
    end
  end
end