## # $Id$ ## ## # This file is part of the Metasploit Framework and may be subject to # redistribution and commercial restrictions. Please see the Metasploit # Framework web site for more information on licensing and terms of use. # http://metasploit.com/framework/ ## require 'msf/core' class Metasploit3 < Msf::Auxiliary # Exploit mixins should be called first include Msf::Exploit::Remote::SMB include Msf::Exploit::Remote::DCERPC # Scanner mixin should be near last include Msf::Auxiliary::Scanner def initialize super( 'Name' => 'SMB User Enumeration (SAM EnumUsers)', 'Version' => '$Revision$', 'Description' => 'Determine what local users exist via the SAM RPC service', 'Author' => 'hdm', 'License' => MSF_LICENSE, 'DefaultOptions' => { 'DCERPC::fake_bind_multi' => false } ) deregister_options('RPORT', 'RHOST') end # Locate an available SMB PIPE for the specified service def smb_find_dcerpc_pipe(uuid, vers, pipes) found_pipe = nil found_handle = nil pipes.each do |pipe_name| connected = false begin connect smb_login connected = true handle = dcerpc_handle( uuid, vers, 'ncacn_np', ["\\#{pipe_name}"] ) dcerpc_bind(handle) return pipe_name rescue ::Interrupt => e raise e rescue ::Exception => e raise e if not connected end disconnect end nil end def smb_pack_sid(str) [1,5,0].pack('CCv') + str.split('-').map{|x| x.to_i}.pack('NVVVV') end def smb_parse_sam_domains(data) ret = [] idx = 0 cnt = data[8, 4].unpack("V")[0] return ret if cnt == 0 idx += 20 idx += 12 * cnt 1.upto(cnt) do v = data[idx,data.length].unpack('V*') l = v[2] * 2 while(l % 4 != 0) l += 1 end idx += 12 ret << data[idx, v[2] * 2].gsub("\x00", '') idx += l end ret end def smb_parse_sam_users(data) ret = {} rid = [] idx = 0 cnt = data[8, 4].unpack("V")[0] return ret if cnt == 0 idx += 20 1.upto(cnt) do v = data[idx,12].unpack('V3') rid << v[0] idx += 12 end 1.upto(cnt) do v = data[idx,32].unpack('V*') l = v[2] * 2 while(l % 4 != 0) l += 1 end uid = rid.shift idx += 12 ret[uid] = data[idx, v[2] * 2].gsub("\x00", '') idx += l end ret end @@sam_uuid = '12345778-1234-abcd-ef00-0123456789ac' @@sam_vers = '1.0' @@sam_pipes = %W{ SAMR LSARPC NETLOGON BROWSER SRVSVC } # Fingerprint a single host def run_host(ip) [[139, false], [445, true]].each do |info| datastore['RPORT'] = info[0] datastore['SMBDirect'] = info[1] sam_pipe = nil sam_handle = nil begin # Find the SAM pipe sam_pipe = smb_find_dcerpc_pipe(@@sam_uuid, @@sam_vers, @@sam_pipes) break if not sam_pipe # Connect5 stub = NDR.uwstring("\\\\" + ip) + NDR.long(0x30) + NDR.long(1) + NDR.long(1) + NDR.long(3) + NDR.long(0) dcerpc.call(64, stub) resp = dcerpc.last_response ? dcerpc.last_response.stub_data : nil if ! (resp and resp.length == 40) print_error("#{ip} Invalid response from the Connect5 request") disconnect return end phandle = resp[16,20] perror = resp[36,4].unpack("V")[0] if(perror == 0xc0000022) disconnect return end if(perror != 0) print_error("#{ip} Received error #{"0x%.8x" % perror} from the OpenPolicy2 request") disconnect return end # EnumDomains stub = phandle + NDR.long(0) + NDR.long(8192) dcerpc.call(6, stub) resp = dcerpc.last_response ? dcerpc.last_response.stub_data : nil domlist = smb_parse_sam_domains(resp) domains = {} # LookupDomain domlist.each do |domain| next if domain == 'Builtin' # Round up the name to match NDR.uwstring() behavior dlen = (domain.length + 1) * 2 stub = phandle + [dlen, dlen].pack("vv") + NDR.uwstring(domain) dcerpc.call(5, stub) resp = dcerpc.last_response ? dcerpc.last_response.stub_data : nil raw_sid = resp[12, 20] txt_sid = raw_sid.unpack("NVVVV").join("-") domains[domain] = { :sid_raw => raw_sid, :sid_txt => txt_sid } end # OpenDomain, QueryDomainInfo, CloseDomain domains.each_key do |domain| # Open stub = phandle + NDR.long(0x00000304) + NDR.long(4) + [1,4,0].pack('CvC') + domains[domain][:sid_raw] dcerpc.call(7, stub) resp = dcerpc.last_response ? dcerpc.last_response.stub_data : nil dhandle = resp[0,20] # Password information stub = dhandle + [0x01].pack('v') dcerpc.call(8, stub) resp = dcerpc.last_response ? dcerpc.last_response.stub_data : nil if(resp and resp[-4,4].unpack('V')[0] == 0) mlen,hlen = resp[8,4].unpack('vv') domains[domain][:pass_min] = mlen domains[domain][:pass_min_history] = hlen end # Server Role stub = dhandle + [0x07].pack('v') dcerpc.call(8, stub) if(resp and resp[-4,4].unpack('V')[0] == 0) resp = dcerpc.last_response ? dcerpc.last_response.stub_data : nil domains[domain][:server_role] = resp[8,2].unpack('v')[0] end # Lockout Threshold stub = dhandle + [12].pack('v') dcerpc.call(8, stub) resp = dcerpc.last_response ? dcerpc.last_response.stub_data : nil if(resp and resp[-4,4].unpack('V')[0] == 0) lduration = resp[8,8] lwindow = resp[16,8] lthresh = resp[24, 2].unpack('v')[0] domains[domain][:lockout_threshold] = lthresh domains[domain][:lockout_duration] = Rex::Proto::SMB::Utils.time_smb_to_unix(*(lduration.unpack('V2'))) domains[domain][:lockout_window] = Rex::Proto::SMB::Utils.time_smb_to_unix(*(lwindow.unpack('V2'))) end # Users stub = dhandle + NDR.long(0) + NDR.long(0x10) + NDR.long(1024*1024) dcerpc.call(13, stub) resp = dcerpc.last_response ? dcerpc.last_response.stub_data : nil if(resp and resp[-4,4].unpack('V')[0] == 0) domains[domain][:users] = smb_parse_sam_users(resp) end # Close Domain dcerpc.call(1, dhandle) end # Close Policy dcerpc.call(1, phandle) domains.each_key do |domain| users = domains[domain][:users] extra = "" if (domains[domain][:lockout_threshold]) extra = "( " extra << "LockoutTries=#{domains[domain][:lockout_threshold]} " extra << "PasswordMin=#{domains[domain][:pass_min]} " extra << ")" end print_status("#{ip} #{domain.upcase} [ #{users.keys.map{|k| users[k]}.join(", ")} ] #{extra}") end # cleanup disconnect return rescue ::Interrupt raise $! rescue ::Rex::ConnectionError rescue ::Rex::Proto::SMB::Exceptions::LoginError next rescue ::Exception => e print_line("Error: #{ip} #{e.class} #{e} #{e.backtrace}") end end end end