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

class MetasploitModule < Msf::Post
  include Msf::Post::Windows::Accounts

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'Windows Gather Directory Permissions Enumeration',
        'Description' => %q{
          This module enumerates directories and lists the permissions set
          on found directories. Please note: if the PATH option isn't specified,
          then the module will start enumerate whatever is in the target machine's
          %PATH% variable.
        },
        'License' => MSF_LICENSE,
        'Platform' => ['win'],
        'SessionTypes' => ['meterpreter'],
        'Author' => [
          'Kx499',
          'Ben Campbell',
          'sinn3r'
        ],
        'Compat' => {
          'Meterpreter' => {
            'Commands' => %w[
              stdapi_fs_stat
            ]
          }
        }
      )
    )

    register_options(
      [
        OptString.new('PATH', [ false, 'Directory to begin search from', '']),
        OptEnum.new('FILTER', [ false, 'Filter to limit results by', 'NA', [ 'NA', 'R', 'W', 'RW' ]]),
        OptInt.new('DEPTH', [ true, 'Depth to drill down into subdirs, O = no limit', 0]),
      ]
    )
  end

  def enum_subdirs(perm_filter, dpath, maxdepth, token)
    filter = datastore['FILTER']
    filter = nil if datastore['FILTER'] == 'NA'

    begin
      dirs = session.fs.dir.foreach(dpath)
    rescue Rex::Post::Meterpreter::RequestError
      # Sometimes we cannot see the dir
      dirs = []
    end

    if (maxdepth >= 1) || (maxdepth < 0)
      dirs.each do |d|
        next if d =~ /^(\.|\.\.)$/

        realpath = dpath + '\\' + d
        next unless session.fs.file.stat(realpath).directory?

        perm = check_dir_perms(realpath, token)
        if perm_filter && perm && perm.include?(perm_filter)
          print_status(perm + "\t" + realpath)
        end
        enum_subdirs(perm_filter, realpath, maxdepth - 1, token)
      end
    end
  end

  def get_paths
    p = datastore['PATH']
    return [p] if !p.nil? && !p.empty?

    begin
      p = cmd_exec('cmd.exe', '/c echo %PATH%')
    rescue Rex::Post::Meterpreter::RequestError => e
      vprint_error(e.message)
      return []
    end
    print_status("Option 'PATH' isn't specified. Using system %PATH%")
    if p.include?(';')
      return p.split(';')
    else
      return [p]
    end
  end

  def get_token
    print_status('Getting impersonation token...')
    begin
      t = get_imperstoken
    rescue ::Exception => e
      # Failure due to timeout, access denied, etc.
      t = nil
      vprint_error("Error #{e.message} while using get_imperstoken()")
      vprint_error(e.backtrace)
    end
    return t
  end

  def enum_perms(perm_filter, token, depth, paths)
    paths.each do |path|
      next if path.empty?

      path = path.strip

      print_status("Checking directory permissions from: #{path}")

      perm = check_dir_perms(path, token)
      next if perm.nil?

      # Show the permission of the parent directory
      if perm_filter && perm.include?(perm_filter)
        print_status(perm + "\t" + path)
      end

      # call recursive function to loop through and check all sub directories
      enum_subdirs(perm_filter, path, depth, token)
    end
  end

  def run
    perm_filter = datastore['FILTER']
    perm_filter = nil if datastore['FILTER'] == 'NA'

    paths = get_paths
    if paths.empty?
      print_error('Unable to get the path')
      return
    end

    depth = -1
    if datastore['DEPTH'] > 0
      depth = datastore['DEPTH']
    end

    t = get_token

    if t
      print_status("Got token: #{t}...")
      enum_perms(perm_filter, t, depth, paths)
    else
      print_error('Getting impersonation token failed')
    end
  end
end
