# -*- coding: binary -*- require 'rex/proto/smb' require 'rex/proto/ntlm' require 'rex/proto/dcerpc' require 'rex/encoder/ndr' module Msf require 'msf/core/exploit/smb_server' ### # # This mixin provides utility methods for interacting with a SMB/CIFS service on # a remote machine. These methods may generally be useful in the context of # exploitation. This mixin extends the Tcp exploit mixin. Only one SMB # service can be accessed at a time using this class. # ### module Exploit::Remote::SMB include Exploit::Remote::Tcp include Exploit::Remote::NTLM::Client SIMPLE = Rex::Proto::SMB::SimpleClient XCEPT = Rex::Proto::SMB::Exceptions CONST = Rex::Proto::SMB::Constants # Alias over the Rex DCERPC protocol modules DCERPCPacket = Rex::Proto::DCERPC::Packet DCERPCClient = Rex::Proto::DCERPC::Client DCERPCResponse = Rex::Proto::DCERPC::Response DCERPCUUID = Rex::Proto::DCERPC::UUID NDR = Rex::Encoder::NDR def initialize(info = {}) super register_evasion_options( [ OptBool.new('SMB::pipe_evasion', [ true, 'Enable segmented read/writes for SMB Pipes', false]), OptInt.new('SMB::pipe_write_min_size', [ true, 'Minimum buffer size for pipe writes', 1]), OptInt.new('SMB::pipe_write_max_size', [ true, 'Maximum buffer size for pipe writes', 1024]), OptInt.new('SMB::pipe_read_min_size', [ true, 'Minimum buffer size for pipe reads', 1]), OptInt.new('SMB::pipe_read_max_size', [ true, 'Maximum buffer size for pipe reads', 1024]), OptInt.new('SMB::pad_data_level', [ true, 'Place extra padding between headers and data (level 0-3)', 0]), OptInt.new('SMB::pad_file_level', [ true, 'Obscure path names used in open/create (level 0-3)', 0]), OptInt.new('SMB::obscure_trans_pipe_level', [ true, 'Obscure PIPE string in TransNamedPipe (level 0-3)', 0]), ], Msf::Exploit::Remote::SMB) register_advanced_options( [ OptBool.new('SMBDirect', [ true, 'The target port is a raw SMB service (not NetBIOS)', true ]), OptString.new('SMBUser', [ false, 'The username to authenticate as', '']), OptString.new('SMBPass', [ false, 'The password for the specified username', '']), OptString.new('SMBDomain', [ false, 'The Windows domain to use for authentication', '.']), OptString.new('SMBName', [ true, 'The NetBIOS hostname (required for port 139 connections)', '*SMBSERVER']), OptBool.new('SMB::VerifySignature', [ true, "Enforces client-side verification of server response signatures", false]), OptInt.new('SMB::ChunkSize', [ true, 'The chunk size for SMB segments, bigger values will increase speed but break NT 4.0 and SMB signing', 500]), # # Control the identified operating system of the client # OptString.new('SMB::Native_OS', [ true, 'The Native OS to send during authentication', 'Windows 2000 2195']), OptString.new('SMB::Native_LM', [ true, 'The Native LM to send during authentication', 'Windows 2000 5.0']), ], Msf::Exploit::Remote::SMB) register_options( [ Opt::RHOST, OptInt.new('RPORT', [ true, 'Set the SMB service port', 445]) ], Msf::Exploit::Remote::SMB) register_autofilter_ports([ 139, 445]) register_autofilter_services(%W{ netbios-ssn microsoft-ds }) end # Override {Exploit::Remote::Tcp#connect} to setup an SMB connection # and configure evasion options # # Also populates {#simple}. # # @param (see Exploit::Remote::Tcp#connect) # @return (see Exploit::Remote::Tcp#connect) def connect(global=true) disconnect() if global s = super(global) self.sock = s if global # Disable direct SMB when SMBDirect has not been set # and the destination port is configured as 139 direct = smb_direct if(datastore.default?('SMBDirect') and rport.to_i == 139) direct = false end c = SIMPLE.new(s, direct) # setup pipe evasion foo if datastore['SMB::pipe_evasion'] # XXX - insert code to change the instance of the read/write functions to do segmentation end if (datastore['SMB::pad_data_level']) c.client.evasion_opts['pad_data'] = datastore['SMB::pad_data_level'] end if (datastore['SMB::pad_file_level']) c.client.evasion_opts['pad_file'] = datastore['SMB::pad_file_level'] end if (datastore['SMB::obscure_trans_pipe_level']) c.client.evasion_opts['obscure_trans_pipe'] = datastore['SMB::obscure_trans_pipe_level'] end self.simple = c if global c end # Convert a standard ASCII string to 16-bit Unicode def unicode(str) Rex::Text.to_unicode(str) end # Establishes an SMB session over the default socket and connects to # the IPC$ share. # # You should call {#connect} before calling this # # @return [void] def smb_login simple.login( datastore['SMBName'], datastore['SMBUser'], datastore['SMBPass'], datastore['SMBDomain'], datastore['SMB::VerifySignature'], datastore['NTLM::UseNTLMv2'], datastore['NTLM::UseNTLM2_session'], datastore['NTLM::SendLM'], datastore['NTLM::UseLMKey'], datastore['NTLM::SendNTLM'], datastore['SMB::Native_OS'], datastore['SMB::Native_LM'], {:use_spn => datastore['NTLM::SendSPN'], :name => self.rhost} ) simple.connect("\\\\#{datastore['RHOST']}\\IPC$") end # This method returns the native operating system of the peer def smb_peer_os self.simple.client.peer_native_os end # This method returns the native lanman version of the peer def smb_peer_lm self.simple.client.peer_native_lm end # This method opens a handle to an IPC pipe def smb_create(pipe) self.simple.create_pipe(pipe) end #the default chunk size of 48000 for OpenFile is not compatible when signing is enabled (and with some nt4 implementations) #cause it looks like MS windows refuse to sign big packet and send STATUS_ACCESS_DENIED #fd.chunk_size = 500 is better def smb_open(path, perm) self.simple.open(path, perm, datastore['SMB::ChunkSize']) end def smb_hostname datastore['SMBName'] || '*SMBSERVER' end def smb_direct datastore['SMBDirect'] end def domain datastore['SMBDomain'] end def smbhost if domain == "." "#{rhost}:#{rport}" else "#{rhost}:#{rport}|#{domain}" end end # If the username contains a / slash, then # split it as a domain/username. NOTE: this # is predicated on forward slashes, and not # Microsoft's backwards slash convention. def domain_username_split(user) return user if(user.nil? || user.empty?) if !user[/\//] # Only /, not \! return [nil,user] else return user.split("/",2) end end def splitname(uname) if datastore["PRESERVE_DOMAINS"] d,u = domain_username_split(uname) return u else return uname end end # Whether a remote file exists # # @param file [String] Path to a file to remove, relative to the # most-recently connected share # @raise [Rex::Proto::SMB::Exceptions::ErrorCode] def smb_file_exist?(file) begin fd = simple.open(file, 'ro') rescue XCEPT::ErrorCode => e # If attempting to open the file results in a "*_NOT_FOUND" error, # then we can be sure the file is not there. # # Copy-pasted from smb/exceptions.rb to avoid the gymnastics # required to pull them out of a giant inverted hash # # 0xC0000034 => "STATUS_OBJECT_NAME_NOT_FOUND", # 0xC000003A => "STATUS_OBJECT_PATH_NOT_FOUND", # 0xC0000225 => "STATUS_NOT_FOUND", error_is_not_found = [ 0xC0000034, 0xC000003A, 0xC0000225 ].include?(e.error_code) # If the server returns some other error, then there was a # permissions problem or some other difficulty that we can't # really account for and hope the caller can deal with it. raise e unless error_is_not_found found = !error_is_not_found else # There was no exception, so we know the file is openable fd.close found = true end found end # Remove remote file # # @param file (see #smb_file_exist?) # @return [void] def smb_file_rm(file) fd = smb_open(file, 'ro') fd.delete end # # Fingerprinting methods # # Calls the EnumPrinters() function of the spooler service def smb_enumprinters(flags, name, level, blen) stub = NDR.long(flags) + (name ? NDR.uwstring(name) : NDR.long(0)) + NDR.long(level) + NDR.long(rand(0xffffffff)+1)+ NDR.long(blen) + "\x00" * blen + NDR.long(blen) handle = dcerpc_handle( '12345678-1234-abcd-ef00-0123456789ab', '1.0', 'ncacn_np', ["\\SPOOLSS"] ) begin dcerpc_bind(handle) dcerpc.call(0x00, stub) return dcerpc.last_response.stub_data rescue ::Interrupt raise $! rescue ::Exception => e return nil end end # This method dumps the print provider strings from the spooler def smb_enumprintproviders resp = smb_enumprinters(8, nil, 1, 0) return nil if not resp rptr, tmp, blen = resp.unpack("V*") resp = smb_enumprinters(8, nil, 1, blen) return nil if not resp bcnt,pcnt,stat = resp[-12, 12].unpack("VVV") return nil if stat != 0 return nil if pcnt == 0 return nil if bcnt > blen return nil if pcnt < 3 # # The correct way, which leads to invalid offsets :-( # #providers = [] # #0.upto(pcnt-1) do |i| # flags,desc_o,name_o,comm_o = resp[8 + (i*16), 16].unpack("VVVV") # # #desc = read_unicode(resp,8+desc_o).gsub("\x00", '') # #name = read_unicode(resp,8+name_o).gsub("\x00", '') # #comm = read_unicode(resp,8+comm_o).gsub("\x00", '') # #providers << [flags,desc,name,comm] #end # #providers return resp end # This method performs an extensive set of fingerprinting operations def smb_fingerprint fprint = {} # Connect to the server if needed if not self.simple connect() smb_login() end fprint['native_os'] = smb_peer_os() fprint['native_lm'] = smb_peer_lm() # Leverage Recog for SMB native OS fingerprinting fp_match = Recog::Nizer.match('smb.native_os', fprint['native_os']) || { } os = fp_match['os.product'] || 'Unknown' sp = fp_match['os.version'] || '' # Metasploit prefers 'Windows 2003' vs 'Windows Server 2003' if os =~ /^Windows Server/ os = os.sub(/^Windows Server/, 'Windows') end if fp_match['os.edition'] fprint['edition'] = fp_match['os.edition'] end if fp_match['os.build'] fprint['build'] = fp_match['os.build'] end if sp == '' sp = smb_fingerprint_windows_sp(os) end lang = smb_fingerprint_windows_lang fprint['os'] = os fprint['sp'] = sp fprint['lang'] = lang fprint end # # Determine the service pack level of a Windows system via SMB probes # def smb_fingerprint_windows_sp(os) sp = '' if (os == 'Windows XP') # SRVSVC was blocked in SP2 begin smb_create("\\SRVSVC") sp = 'Service Pack 0 / 1' rescue ::Rex::Proto::SMB::Exceptions::ErrorCode => e if (e.error_code == 0xc0000022) sp = 'Service Pack 2+' end end end if (os == 'Windows 2000' and sp.length == 0) # LLSRPC was blocked in a post-SP4 update begin smb_create("\\LLSRPC") sp = 'Service Pack 0 - 4' rescue ::Rex::Proto::SMB::Exceptions::ErrorCode => e if (e.error_code == 0xc0000022) sp = 'Service Pack 4 with MS05-010+' end end end # # Perform granular XP SP checks if LSARPC is exposed # if (os == 'Windows XP') # # Service Pack 2 added a range(0,64000) to opnum 0x22 in SRVSVC # Credit to spoonm for first use of unbounded [out] buffers # handle = dcerpc_handle( '4b324fc8-1670-01d3-1278-5a47bf6ee188', '3.0', 'ncacn_np', ["\\BROWSER"] ) begin dcerpc_bind(handle) stub = NDR.uwstring(Rex::Text.rand_text_alpha(rand(10)+1)) + NDR.wstring(Rex::Text.rand_text_alpha(rand(10)+1)) + NDR.long(64001) + NDR.long(0) + NDR.long(0) dcerpc.call(0x22, stub) sp = "Service Pack 0 / 1" rescue ::Interrupt raise $! rescue ::Rex::Proto::SMB::Exceptions::ErrorCode rescue ::Rex::Proto::SMB::Exceptions::ReadPacket rescue ::Rex::Proto::DCERPC::Exceptions::Fault sp = "Service Pack 2+" rescue ::Exception end # # Service Pack 3 fixed information leaks via [unique][out] pointers # Call SRVSVC::NetRemoteTOD() to return [out] [ref] [unique] # Credit: # Pointer leak is well known, but Immunity also covered in a paper # Silent fix of pointer leak in SP3 and detection method by Rhys Kidd # handle = dcerpc_handle( '4b324fc8-1670-01d3-1278-5a47bf6ee188', '3.0', 'ncacn_np', ["\\BROWSER"] ) begin dcerpc_bind(handle) stub = NDR.uwstring(Rex::Text.rand_text_alpha(rand(8)+1)) resp = dcerpc.call(0x1c, stub) if(resp and resp[0,4] == "\x00\x00\x02\x00") sp = "Service Pack 3" else if(resp and sp =~ /Service Pack 2\+/) sp = "Service Pack 2" end end rescue ::Interrupt raise $! rescue ::Rex::Proto::SMB::Exceptions::ErrorCode rescue ::Rex::Proto::SMB::Exceptions::ReadPacket rescue ::Exception end end sp end # # Determine the native language pack of a Windows system via SMB probes # def smb_fingerprint_windows_lang # # Remote language detection via Print Providers # Credit: http://immunityinc.com/downloads/Remote_Language_Detection_in_Immunity_CANVAS.odt # lang = 'Unknown' sigs = { 'English' => [ Rex::Text.to_unicode('Windows NT Remote Printers'), Rex::Text.to_unicode('LanMan Print Services') ], 'Spanish' => [ Rex::Text.to_unicode('Impresoras remotas Windows NT'), Rex::Text.to_unicode('Impresoras remotas de Windows NT') ], 'Italian' => [ Rex::Text.to_unicode('Stampanti remote di Windows NT'), Rex::Text.to_unicode('Servizi di stampa LanMan') ], 'French' => [ Rex::Text.to_unicode('Imprimantes distantes NT'), Rex::Text.to_unicode('Imprimantes distantes pour Windows NT'), Rex::Text.to_unicode("Services d'impression LanMan") ], 'German' => [ Rex::Text.to_unicode('Remotedrucker') ], 'Portuguese - Brazilian' => [ Rex::Text.to_unicode('Impr. remotas Windows NT'), Rex::Text.to_unicode('Impressoras remotas do Windows NT') ], 'Portuguese' => [ Rex::Text.to_unicode('Imp. remotas do Windows NT') ], 'Hungarian' => [ Rex::Text.to_unicode("\x54\xe1\x76\x6f\x6c\x69\x20\x6e\x79\x6f\x6d\x74\x61\x74\xf3\x6b") ], 'Finnish' => [ Rex::Text.to_unicode("\x45\x74\xe4\x74\x75\x6c\x6f\x73\x74\x69\x6d\x65\x74") ], 'Dutch' => [ Rex::Text.to_unicode('Externe printers voor NT') ], 'Danish' => [ Rex::Text.to_unicode('Fjernprintere') ], 'Swedish' => [ Rex::Text.to_unicode("\x46\x6a\xe4\x72\x72\x73\x6b\x72\x69\x76\x61\x72\x65") ], 'Polish' => [ Rex::Text.to_unicode('Zdalne drukarki') ], 'Czech' => [ Rex::Text.to_unicode("\x56\x7a\x64\xe1\x6c\x65\x6e\xe9\x20\x74\x69\x73\x6b\xe1\x72\x6e\x79") ], 'Turkish' => [ "\x59\x00\x61\x00\x7a\x00\x31\x01\x63\x00\x31\x01\x6c\x00\x61\x00\x72\x00" ], 'Japanese' => [ "\xea\x30\xe2\x30\xfc\x30\xc8\x30\x20\x00\xd7\x30\xea\x30\xf3\x30\xbf\x30" ], 'Chinese - Traditional' => [ "\xdc\x8f\x0b\x7a\x53\x62\x70\x53\x3a\x67" ], 'Chinese - Traditional / Taiwan' => [ "\x60\x90\xef\x7a\x70\x53\x68\x88\x5f\x6a", ], 'Korean' => [ "\xd0\xc6\xa9\xac\x20\x00\x04\xd5\xb0\xb9\x30\xd1", ], 'Russian' => [ "\x1f\x04\x40\x04\x38\x04\x3d\x04\x42\x04\x35\x04\x40\x04\x4b\x04\x20\x00\x43\x04\x34\x04\x30\x04\x3b\x04\x35\x04\x3d\x04\x3d\x04\x3e\x04\x33\x04\x3e\x04\x20\x00\x34\x04\x3e\x04\x41\x04\x42\x04\x43\x04\x3f\x04\x30\x04", ], } begin prov = smb_enumprintproviders() if(prov) sigs.each_key do |k| sigs[k].each do |s| if(prov.index(s)) lang = k break end break if lang != 'Unknown' end break if lang != 'Unknown' end if(lang == 'Unknown') @fpcache ||= {} mhash = ::Digest::MD5.hexdigest(prov[4,prov.length-4]) if(not @fpcache[mhash]) buff = "\n" buff << "*** NEW FINGERPRINT: PLEASE SEND TO [ msfdev[at]metasploit.com ]\n" buff << " VERS: $Revision$\n" buff << " HOST: #{rhost}\n" buff << " OS: #{os}\n" buff << " SP: #{sp}\n" prov.unpack("H*")[0].scan(/.{64}|.*/).each do |line| next if line.length == 0 buff << " FP: #{line}\n" end prov.split(/\x00\x00+/n).each do |line| line.gsub!("\x00",'') line.strip! next if line.length < 6 buff << " TXT: #{line}\n" end buff << "*** END FINGERPRINT\n" print_line(buff) @fpcache[mhash] = true end end end rescue ::Interrupt raise $! rescue ::Rex::Proto::SMB::Exceptions::ErrorCode end lang end # @return [Rex::Proto::SMB::SimpleClient] attr_accessor :simple end end