Windows Gather Deleted Files Enumeration and Recovering - Metasploit


This page contains detailed information about how to use the post/windows/gather/forensics/recovery_files metasploit module. For list of all metasploit modules, visit the Metasploit Module Library.

Module Overview


Name: Windows Gather Deleted Files Enumeration and Recovering
Module: post/windows/gather/forensics/recovery_files
Source code: modules/post/windows/gather/forensics/recovery_files.rb
Disclosure date: -
Last modification time: 2021-10-06 13:43:31 +0000
Supported architecture(s): -
Supported platform(s): Windows
Target service / protocol: -
Target network port(s): -
List of CVEs: -

This module lists and attempts to recover deleted files from NTFS file systems. Use the FILES option to guide recovery. Leave this option empty to enumerate deleted files in the DRIVE. Set FILES to an extension (e.g., "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 account file enumeration. Recovery may take a long time; use the TIMEOUT option to abort enumeration or recovery by extension after a specified period (in seconds).

Module Ranking and Traits


Module Ranking:

  • normal: The exploit is otherwise reliable, but depends on a specific version and can't (or doesn't) reliably autodetect. More information about ranking can be found here.

Basic Usage


There are two ways to execute this post module.

From the Meterpreter prompt

The first is by using the "run" command at the Meterpreter prompt. It allows you to run the post module against that specific session:

meterpreter > run post/windows/gather/forensics/recovery_files

From the msf prompt

The second is by using the "use" command at the msf prompt. You will have to figure out which session ID to set manually. To list all session IDs, you can use the "sessions" command.

msf > use post/windows/gather/forensics/recovery_files
msf post(recovery_files) > show options
    ... show and set options ...
msf post(recovery_files) > set SESSION session-id
msf post(recovery_files) > exploit

If you wish to run the post against all sessions from framework, here is how:

1 - Create the following resource script:


framework.sessions.each_pair do |sid, session|
  run_single("use post/windows/gather/forensics/recovery_files")
  run_single("set SESSION #{sid}")
  run_single("run")
end

2 - At the msf prompt, execute the above resource script:

msf > resource path-to-resource-script

Required Options


  • SESSION: The session to run this module on.

Go back to menu.

Msfconsole Usage


Here is how the windows/gather/forensics/recovery_files post exploitation module looks in the msfconsole:

msf6 > use post/windows/gather/forensics/recovery_files

msf6 post(windows/gather/forensics/recovery_files) > show info

       Name: Windows Gather Deleted Files Enumeration and Recovering
     Module: post/windows/gather/forensics/recovery_files
   Platform: Windows
       Arch: 
       Rank: Normal

Provided by:
  Borja Merino <[email protected]>

Compatible session types:
  Meterpreter

Basic options:
  Name     Current Setting  Required  Description
  ----     ---------------  --------  -----------
  DRIVE    C:               yes       Drive you want to recover files from.
  FILES                     no        ID or extensions of the files to recover in a comma separated way. Let empty to enumerate deleted files.
  SESSION                   yes       The session to run this module on.
  TIMEOUT  3600             yes       Search timeout. If 0 the module will go through the entire $MFT.

Description:
  This module lists and attempts to recover deleted files from NTFS 
  file systems. Use the FILES option to guide recovery. Leave this 
  option empty to enumerate deleted files in the DRIVE. Set FILES to 
  an extension (e.g., "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 account file 
  enumeration. Recovery may take a long time; use the TIMEOUT option 
  to abort enumeration or recovery by extension after a specified 
  period (in seconds).

References:
  http://www.youtube.com/watch?v=9yzCf360ujY&hd=1

Module Options


This is a complete list of options available in the windows/gather/forensics/recovery_files post exploitation module:

msf6 post(windows/gather/forensics/recovery_files) > show options

Module options (post/windows/gather/forensics/recovery_files):

   Name     Current Setting  Required  Description
   ----     ---------------  --------  -----------
   DRIVE    C:               yes       Drive you want to recover files from.
   FILES                     no        ID or extensions of the files to recover in a comma separated way. Let empty to enumerate deleted files.
   SESSION                   yes       The session to run this module on.
   TIMEOUT  3600             yes       Search timeout. If 0 the module will go through the entire $MFT.

Advanced Options


Here is a complete list of advanced options supported by the windows/gather/forensics/recovery_files post exploitation module:

msf6 post(windows/gather/forensics/recovery_files) > show advanced

Module advanced options (post/windows/gather/forensics/recovery_files):

   Name       Current Setting  Required  Description
   ----       ---------------  --------  -----------
   VERBOSE    false            no        Enable detailed status messages
   WORKSPACE                   no        Specify the workspace for this module

Post Actions


This is a list of all post exploitation actions which the windows/gather/forensics/recovery_files module can do:

msf6 post(windows/gather/forensics/recovery_files) > show actions

Post actions:

   Name  Description
   ----  -----------

Evasion Options


Here is the full list of possible evasion options supported by the windows/gather/forensics/recovery_files post exploitation module in order to evade defenses (e.g. Antivirus, EDR, Firewall, NIDS etc.):

msf6 post(windows/gather/forensics/recovery_files) > show evasion

Module evasion options:

   Name  Current Setting  Required  Description
   ----  ---------------  --------  -----------

Go back to menu.

Error Messages


This module may fail with the following error messages:

Check for the possible causes from the code snippets below found in the module source code. This can often times help in identifying the root cause of the problem.

Module not valid for Windows 2000


Here is a relevant code snippet related to the "Module not valid for Windows 2000" error message:

40:	
41:	  def run
42:	    winver = sysinfo["OS"]
43:	
44:	    if winver =~ /2000/i
45:	      print_error("Module not valid for Windows 2000")
46:	      return
47:	    end
48:	
49:	    drive = datastore['DRIVE']
50:	    fs = file_system(drive)

The file system is not NTFS


Here is a relevant code snippet related to the "The file system is not NTFS" error message:

48:	
49:	    drive = datastore['DRIVE']
50:	    fs = file_system(drive)
51:	
52:	    if fs !~ /ntfs/i
53:	      print_error("The file system is not NTFS")
54:	      return
55:	    end
56:	
57:	    if not is_admin?
58:	      print_error("You don't have enough privileges. Try getsystem.")

You don't have enough privileges. Try getsystem.


Here is a relevant code snippet related to the "You don't have enough privileges. Try getsystem." error message:

53:	      print_error("The file system is not NTFS")
54:	      return
55:	    end
56:	
57:	    if not is_admin?
58:	      print_error("You don't have enough privileges. Try getsystem.")
59:	      return
60:	    end
61:	
62:	    print_status("System Info - OS: #{winver}, Drive: #{drive}")
63:	    type = datastore['FILES']

GetLastError


Here is a relevant code snippet related to the "GetLastError" error message:

63:	    type = datastore['FILES']
64:	    files = type.split(',')
65:	    # To extract files from its IDs
66:	    if datastore['FILES'] != "" and is_numeric(files[0])
67:	      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)
68:	      if r['GetLastError'] == 0
69:	        recover_file(files, r['return'])
70:	        client.railgun.kernel32.CloseHandle(r['return'])
71:	      else
72:	        print_error("Error opening #{drive} GetLastError=#{r['GetLastError']}")
73:	      end

Error opening <DRIVE> GetLastError=<GETLASTERROR>


Here is a relevant code snippet related to the "Error opening <DRIVE> GetLastError=<GETLASTERROR>" error message:

67:	      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)
68:	      if r['GetLastError'] == 0
69:	        recover_file(files, r['return'])
70:	        client.railgun.kernel32.CloseHandle(r['return'])
71:	      else
72:	        print_error("Error opening #{drive} GetLastError=#{r['GetLastError']}")
73:	      end
74:	    # To show deleted files (FILE="") or extract the type of file specified by extension
75:	    else
76:	      handle = get_mft_info(drive)
77:	      if handle != nil

Timed out after <TO> seconds. Skipping...


Here is a relevant code snippet related to the "Timed out after <TO> seconds. Skipping..." error message:

81:	        begin
82:	          ::Timeout.timeout(to) do
83:	            deleted_files(data_runs[1..-1], handle, files)
84:	          end
85:	        rescue ::Timeout::Error
86:	          print_error("Timed out after #{to} seconds. Skipping...")
87:	        end
88:	      end
89:	    end
90:	  end
91:	

There were problems to recover the file: <NAME>


Here is a relevant code snippet related to the "There were problems to recover the file: <NAME>" error message:

109:	      name = get_name(attributes)
110:	      print_status("File to download: #{name}")
111:	      vprint_status("Getting Data Runs ...")
112:	      data = get_data_runs(attributes)
113:	      if data == nil or name == nil
114:	        print_error("There were problems to recover the file: #{name}")
115:	        next
116:	      end
117:	
118:	      # If file is resident
119:	      if data[0] == 0

The file is not resident. Saving <NAME> ... (<SIZE> bytes)


Here is a relevant code snippet related to the "The file is not resident. Saving <NAME> ... (<SIZE> bytes)" error message:

124:	      # If file no resident
125:	      else
126:	        # 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
127:	        # that make up the file (save_file function).
128:	        size = get_size(rf['lpBuffer'][56..-1])
129:	        print_status("The file is not resident. Saving #{name} ... (#{size} bytes)")
130:	        base = 0
131:	        # Go through each of the data runs to save the file
132:	        file_data = ""
133:	        1.upto(data.count - 1) { |i|
134:	          datarun = get_datarun_location(data[i])

(No last datarun) Save 1 chunk of <VALUE>, there are <SIZE> left


Here is a relevant code snippet related to the "(No last datarun) Save 1 chunk of <VALUE>, there are <SIZE> left" error message:

178:	        vprint_status("(Last datarun) Save 1 chunk of #{size}")
179:	      else
180:	        rf = client.railgun.kernel32.ReadFile(handle, bytes_per_cluster * rest, bytes_per_cluster * rest, 4, nil)
181:	        file_data << rf['lpBuffer']
182:	        size = size - bytes_per_cluster * rest
183:	        vprint_status("(No last datarun) Save 1 chunk of #{rest * bytes_per_cluster}, there are #{size} left")
184:	      end
185:	    end
186:	    return size
187:	  end
188:	

GetLastError


Here is a relevant code snippet related to the "GetLastError" error message:

285:	
286:	  # Gets the NTFS information and return a pointer to the beginning of the MFT
287:	  def get_mft_info(drive)
288:	    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)
289:	
290:	    if r['GetLastError'] != 0
291:	      print_error("Error opening #{drive} GetLastError=#{r['GetLastError']}")
292:	      print_error("Try to get SYSTEM Privilege") if r['GetLastError'] == 5
293:	      return nil
294:	    else
295:	      ra = file_system_features(r['return'])

Error opening <DRIVE> GetLastError=<GETLASTERROR>


Here is a relevant code snippet related to the "Error opening <DRIVE> GetLastError=<GETLASTERROR>" error message:

286:	  # Gets the NTFS information and return a pointer to the beginning of the MFT
287:	  def get_mft_info(drive)
288:	    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)
289:	
290:	    if r['GetLastError'] != 0
291:	      print_error("Error opening #{drive} GetLastError=#{r['GetLastError']}")
292:	      print_error("Try to get SYSTEM Privilege") if r['GetLastError'] == 5
293:	      return nil
294:	    else
295:	      ra = file_system_features(r['return'])
296:	      bytes_per_cluster = ra['lpOutBuffer'][44, 4].unpack("V*")[0]

Try to get SYSTEM Privilege


Here is a relevant code snippet related to the "Try to get SYSTEM Privilege" error message:

287:	  def get_mft_info(drive)
288:	    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)
289:	
290:	    if r['GetLastError'] != 0
291:	      print_error("Error opening #{drive} GetLastError=#{r['GetLastError']}")
292:	      print_error("Try to get SYSTEM Privilege") if r['GetLastError'] == 5
293:	      return nil
294:	    else
295:	      ra = file_system_features(r['return'])
296:	      bytes_per_cluster = ra['lpOutBuffer'][44, 4].unpack("V*")[0]
297:	      mft_logical_offset = ra['lpOutBuffer'][64, 8].unpack("V*")[0]

File compressed/encrypted/sparse. Ignore this file if you get errors


Here is a relevant code snippet related to the "File compressed/encrypted/sparse. Ignore this file if you get errors" error message:

327:	  def get_data_runs(data)
328:	    # We reach de DATA attribute
329:	    data_runs = get_attribute(data, "\x80\x00\x00\x00")
330:	    return nil if data_runs == nil
331:	
332:	    print_status("File compressed/encrypted/sparse. Ignore this file if you get errors") if ["\x01\x00", "\x00\x40", "\x00\x80"].include? data_runs[12, 2]
333:	    # Check if the file is resident or not
334:	    resident = data_runs[8, 1]
335:	    if resident == "\x00"
336:	      inf = [0]
337:	      inf << get_resident(data_runs)

Attribute not found


Here is a relevant code snippet related to the "Attribute not found" error message:

376:	        return nil if (size_att > 1024) or header == "\xff\xff\xff\xff"
377:	
378:	        str = str[size_att..-1]
379:	      end
380:	    end
381:	    print_status("Attribute not found")
382:	    return nil
383:	  end
384:	
385:	  # Get the type of file system
386:	  def file_system(drive)

Go back to menu.


References


See Also


Check also the following modules related to this module:

Authors


  • Borja Merino <bmerinofe[at]gmail.com>

Version


This page has been produced using Metasploit Framework version 6.1.24-dev. For more modules, visit the Metasploit Module Library.

Go back to menu.