##
# This module requires Metasploit: http://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##


require 'msf/core'
require 'recog'

class MetasploitModule < Msf::Auxiliary


  # Exploit mixins should be called first
  include Msf::Exploit::Remote::DCERPC
  include Msf::Exploit::Remote::SMB::Client
  include Msf::Exploit::Remote::SMB::Client::Authenticated

  # Scanner mixin should be near last
  include Msf::Auxiliary::Scanner
  include Msf::Auxiliary::Report

  # Aliases for common classes
  SIMPLE = Rex::Proto::SMB::SimpleClient
  XCEPT  = Rex::Proto::SMB::Exceptions
  CONST  = Rex::Proto::SMB::Constants


  def initialize
    super(
      'Name'        => 'SMB Version Detection',
      'Description' => 'Display version information about each system',
      'Author'      => 'hdm',
      'License'     => MSF_LICENSE
    )

    deregister_options('RPORT')
    deregister_options('SMBDIRECT')
    @smb_port = 445
  end

  def rport
    @smb_port || datastore['RPORT']
  end

  def smb_direct
    (@smb_port == 445)
  end

  # Fingerprint a single host
  #
  def run_host(ip)
    smb_ports = [445, 139]
    smb_ports.each do |pnum|
      @smb_port = pnum
      self.simple = nil

    begin
      res = smb_fingerprint()

      #
      # Create the note hash for smb.fingerprint
      #
      conf = {
         :native_os => res['native_os'],
         :native_lm => res['native_lm']
      }

      if res['os'] and res['os'] != 'Unknown'

        #
        # Create the note hash for fingerprint.match
        #
        match_conf = { }

        #
        # Create a descriptive string for service.info
        #
        desc = res['os'].dup

        if res['edition'].to_s.length > 0
          desc << " #{res['edition']}"
          conf[:os_edition] = res['edition']
          match_conf['os.edition'] = res['edition']
        end

        if res['sp'].to_s.length > 0
          desc << " #{res['sp'].downcase.gsub('service pack ', 'SP')}"
          conf[:os_sp] = res['sp']
          match_conf['os.version'] = res['sp']
        end

        if res['build'].to_s.length > 0
          desc << " (build:#{res['build']})"
          conf[:os_build] = res['build']
          match_conf['os.build'] = res['build']
        end

        if res['lang'].to_s.length > 0 and res['lang'] != 'Unknown'
          desc << " (language:#{res['lang']})"
          conf[:os_lang] = res['lang']
          match_conf['os.language'] = conf[:os_lang]
        end

        if simple.client.default_name
          desc << " (name:#{simple.client.default_name})"
          conf[:SMBName] = simple.client.default_name
          match_conf['host.name'] = conf[:SMBName]
        end

        if simple.client.default_domain
          if simple.client.default_domain.encoding.name == "UTF-8"
            desc << " (domain:#{simple.client.default_domain})"
          else
            # Workgroup names are in ANSI, but may contain invalid characters
            # Go through each char and convert/check
            temp_workgroup = simple.client.default_domain.dup
            desc << " (workgroup:"
            temp_workgroup.each_char do |i|
              begin
                desc << i.encode("UTF-8")
              rescue Encoding::UndefinedConversionError => e
                desc << '?'
                print_error("Found incompatible (non-ANSI) character in Workgroup name. Replaced with '?'")
              end
            end
            desc << " )"
          end
          conf[:SMBDomain] = simple.client.default_domain
          match_conf['host.domain'] = conf[:SMBDomain]
        end

        print_status("Host is running #{desc}")

        # Report the service with a friendly banner
        report_service(
          :host  => ip,
          :port  => rport,
          :proto => 'tcp',
          :name  => 'smb',
          :info  => desc
        )

        # Report a fingerprint.match hash for name, domain, and language
        # Ignore OS fields, as those are handled via smb.fingerprint
        report_note(
          :host  => ip,
          :port  => rport,
          :proto => 'tcp',
          :ntype => 'fingerprint.match',
          :data  => match_conf
        )
      else
        desc = "#{res['native_os']} (#{res['native_lm']})"
        report_service(:host => ip, :port => rport, :name => 'smb', :info => desc)
        print_status("Host could not be identified: #{desc}")
      end

      # Report a smb.fingerprint hash of attributes for OS fingerprinting
      report_note(
        :host  => ip,
        :port  => rport,
        :proto => 'tcp',
        :ntype => 'smb.fingerprint',
        :data  => conf
      )

      disconnect

      break

    rescue ::Rex::Proto::SMB::Exceptions::NoReply => e
      next
    rescue ::Rex::Proto::SMB::Exceptions::ErrorCode  => e
      next
    rescue ::Rex::Proto::SMB::Exceptions::LoginError => e
      # Vista has 139 open but doesnt like *SMBSERVER
      if(e.to_s =~ /server refused our NetBIOS/)
        next
      end

      return
    rescue ::Timeout::Error
    rescue ::Rex::ConnectionError
      next

    rescue ::Exception => e
      print_error("#{rhost}: #{e.class} #{e}")
    ensure
      disconnect
    end
    end
  end

end
