metasploit-framework/modules/auxiliary/scanner/smb/smb_lookupsid.rb

290 lines
7.0 KiB
Ruby
Raw Normal View History

##
# This module requires Metasploit: http//metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
require 'msf/core'
class Metasploit3 < Msf::Auxiliary
2013-08-30 21:28:54 +00:00
# Exploit mixins should be called first
include Msf::Exploit::Remote::SMB
include Msf::Exploit::Remote::SMB::Authenticated
include Msf::Exploit::Remote::DCERPC
# Scanner mixin should be near last
include Msf::Auxiliary::Report
include Msf::Auxiliary::Scanner
def initialize
super(
'Name' => 'SMB Local User Enumeration (LookupSid)',
'Description' => 'Determine what users exist via brute force SID lookups.
This module can enumerate both local and domain accounts by setting
ACTION to either LOCAL or DOMAIN',
'Author' => 'hdm',
'License' => MSF_LICENSE,
'DefaultOptions' =>
{
'DCERPC::fake_bind_multi' => false
},
'Actions' =>
[
['LOCAL', { 'Description' => 'Enumerate local accounts' } ],
['DOMAIN', { 'Description' => 'Enumerate domain accounts' } ]
],
'DefaultAction' => 'LOCAL'
)
register_options(
[
OptInt.new('MaxRID', [ false, "Maximum RID to check", 4000 ])
],
self.class
)
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_parse_sid(data)
fields = data.unpack('VvvvvVVVVV')
domain = data[32, fields[3]]
domain.gsub!("\x00", '')
if(fields[6] == 0)
return [nil, domain]
end
while(fields[3] % 4 != 0)
fields[3] += 1
end
buff = data[32 + fields[3], data.length].unpack('VCCvNVVVVV')
sid = buff[4..8].map{|x| x.to_s }.join("-")
return [sid, domain]
end
def smb_pack_sid(str)
[1,5,0].pack('CCv') + str.split('-').map{|x| x.to_i}.pack('NVVVV')
end
def smb_parse_sid_lookup(data)
fields = data.unpack('VVVVVvvVVVVV')
if(fields[0] == 0)
return nil
end
domain = data[44, fields[5]]
domain.gsub!("\x00", '')
while(fields[5] % 4 != 0)
fields[5] += 1
end
ginfo = data[44 + fields[5], data.length].unpack('VCCvNVVVV')
uinfo = data[72 + fields[5], data.length].unpack('VVVVvvVVVVV')
if(uinfo[3] == 8)
return [8, nil]
end
name = data[112 + fields[5], uinfo[4]]
name.gsub!("\x00", '')
[ uinfo[3], name ]
end
@@lsa_uuid = '12345778-1234-abcd-ef00-0123456789ab'
@@lsa_vers = '0.0'
@@lsa_pipes = %W{ LSARPC NETLOGON SAMR 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]
lsa_pipe = nil
lsa_handle = nil
begin
# find the lsarpc pipe
lsa_pipe = smb_find_dcerpc_pipe(@@lsa_uuid, @@lsa_vers, @@lsa_pipes)
break if not lsa_pipe
# OpenPolicy2()
stub =
NDR.uwstring(ip) +
NDR.long(24) +
NDR.long(0) +
NDR.long(0) +
NDR.long(0) +
NDR.long(0) +
NDR.long(rand(0x10000000)) +
NDR.long(12) +
[
2, # Impersonation
1, # Context
0 # Effective
].pack('vCC') +
NDR.long(0x02000000)
dcerpc.call(44, stub)
resp = dcerpc.last_response ? dcerpc.last_response.stub_data : nil
if ! (resp and resp.length == 24)
print_error("#{ip} Invalid response from the OpenPolicy request")
disconnect
return
end
phandle = resp[0,20]
perror = resp[20,4].unpack("V")[0]
# Recent versions of Windows restrict this by default
if(perror == 0xc0000022)
disconnect
return
end
if(perror != 0)
print_error("#{ip} Received error #{"0x%.8x" % perror} from the OpenPolicy2 request")
disconnect
return
end
# QueryInfoPolicy(Local)
stub = phandle + NDR.long(5)
dcerpc.call(7, stub)
resp = dcerpc.last_response ? dcerpc.last_response.stub_data : nil
host_sid, host_name = smb_parse_sid(resp)
# QueryInfoPolicy(Domain)
stub = phandle + NDR.long(3)
dcerpc.call(7, stub)
resp = dcerpc.last_response ? dcerpc.last_response.stub_data : nil
domain_sid, domain_name = smb_parse_sid(resp)
# Store SID, local domain name, joined domain name
print_status("#{ip} PIPE(#{lsa_pipe}) LOCAL(#{host_name} - #{host_sid}) DOMAIN(#{domain_name} - #{domain_sid})")
domain = {
:name => host_name,
:txt_sid => host_sid,
:users => {},
:groups => {}
}
target_sid = host_sid if action.name =~ /LOCAL/i
target_sid = domain_sid if action.name =~ /DOMAIN/i
# Brute force through a common RID range
500.upto(datastore['MaxRID'].to_i) do |rid|
stub =
phandle +
NDR.long(1) +
NDR.long(rand(0x10000000)) +
NDR.long(1) +
NDR.long(rand(0x10000000)) +
NDR.long(5) +
smb_pack_sid(target_sid) +
NDR.long(rid) +
NDR.long(0) +
NDR.long(0) +
NDR.long(1) +
NDR.long(0)
dcerpc.call(15, stub)
resp = dcerpc.last_response ? dcerpc.last_response.stub_data : nil
# Skip the "not mapped" error message
if(resp and resp[-4,4].unpack("V")[0] == 0xc0000073)
next
end
# Stop if we are seeing access denied
if(resp and resp[-4,4].unpack("V")[0] == 0xc0000022)
break
end
utype,uname = smb_parse_sid_lookup(resp)
case utype
when 1
print_status("#{ip} USER=#{uname} RID=#{rid}")
domain[:users][rid] = uname
when 2
domain[:groups][rid] = uname
print_status("#{ip} GROUP=#{uname} RID=#{rid}")
else
print_status("#{ip} TYPE=#{utype} NAME=#{uname} rid=#{rid}")
end
end
# Store the domain information
report_note(
:host => ip,
:proto => 'tcp',
:port => datastore['RPORT'],
:type => 'smb.domain.lookupsid',
:data => domain
)
print_status("#{ip} #{domain[:name].upcase} [#{domain[:users].keys.map{|k| domain[:users][k]}.join(", ")} ]")
# cleanup
disconnect
return
rescue ::Timeout::Error
rescue ::Interrupt
raise $!
rescue ::Rex::ConnectionError
rescue ::Rex::Proto::SMB::Exceptions::LoginError
next
rescue ::Exception => e
print_line("Error: #{ip} #{e.class} #{e}")
end
end
end
end