90 lines
4.3 KiB
Python
90 lines
4.3 KiB
Python
import sys, time
|
|
|
|
from cme.logger import CMEAdapter
|
|
from bloodhound.ad.domain import ADDC
|
|
from bloodhound.enumeration.computers import ComputerEnumerator
|
|
from bloodhound.enumeration.memberships import MembershipEnumerator
|
|
from bloodhound.enumeration.domains import DomainEnumerator
|
|
|
|
class BloodHound(object):
|
|
def __init__(self, ad, hostname, host, port):
|
|
self.ad = ad
|
|
self.ldap = None
|
|
self.pdc = None
|
|
self.sessions = []
|
|
self.hostname = hostname
|
|
self.dc = hostname
|
|
self.proto_logger(port, hostname, host)
|
|
|
|
def proto_logger(self, port, hostname, host):
|
|
self.logger = CMEAdapter(extra={
|
|
'protocol': 'LDAP',
|
|
'host': host,
|
|
'port': port,
|
|
'hostname': hostname
|
|
})
|
|
def connect(self):
|
|
if len(self.ad.dcs()) == 0:
|
|
self.logger.error('Could not find a domain controller. Consider specifying a domain and/or DNS server.')
|
|
sys.exit(1)
|
|
|
|
if not self.ad.baseDN:
|
|
self.logger.error('Could not figure out the domain to query. Please specify this manually with -d')
|
|
sys.exit(1)
|
|
|
|
pdc = self.ad.dcs()[0]
|
|
self.logger.debug('Using LDAP server: %s', pdc)
|
|
self.logger.debug('Using base DN: %s', self.ad.baseDN)
|
|
|
|
if len(self.ad.kdcs()) > 0:
|
|
kdc = self.ad.kdcs()[0]
|
|
self.logger.debug('Using kerberos KDC: %s', kdc)
|
|
self.logger.debug('Using kerberos realm: %s', self.ad.realm())
|
|
|
|
# Create a domain controller object
|
|
self.pdc = ADDC(pdc, self.ad)
|
|
# Create an object resolver
|
|
self.ad.create_objectresolver(self.pdc)
|
|
# self.pdc.ldap_connect(self.ad.auth.username, self.ad.auth.password, kdc)
|
|
|
|
|
|
|
|
def run(self, collect, num_workers=10, disable_pooling=False, timestamp="", computerfile="", cachefile=None, exclude_dcs=False):
|
|
start_time = time.time()
|
|
if cachefile:
|
|
self.ad.load_cachefile(cachefile)
|
|
|
|
# Check early if we should enumerate computers as well
|
|
do_computer_enum = any(method in collect for method in ['localadmin', 'session', 'loggedon', 'experimental', 'rdp', 'dcom', 'psremote'])
|
|
|
|
if 'group' in collect or 'objectprops' in collect or 'acl' in collect:
|
|
# Fetch domains for later, computers if needed
|
|
self.pdc.prefetch_info('objectprops' in collect, 'acl' in collect, cache_computers=do_computer_enum)
|
|
# Initialize enumerator
|
|
membership_enum = MembershipEnumerator(self.ad, self.pdc, collect, disable_pooling)
|
|
membership_enum.enumerate_memberships(timestamp=timestamp)
|
|
elif 'container' in collect:
|
|
# Fetch domains for later, computers if needed
|
|
self.pdc.prefetch_info('objectprops' in collect, 'acl' in collect, cache_computers=do_computer_enum)
|
|
# Initialize enumerator
|
|
membership_enum = MembershipEnumerator(self.ad, self.pdc, collect, disable_pooling)
|
|
membership_enum.do_container_collection(timestamp=timestamp)
|
|
elif do_computer_enum:
|
|
# We need to know which computers to query regardless
|
|
# We also need the domains to have a mapping from NETBIOS -> FQDN for local admins
|
|
self.pdc.prefetch_info('objectprops' in collect, 'acl' in collect, cache_computers=True)
|
|
elif 'trusts' in collect:
|
|
# Prefetch domains
|
|
self.pdc.get_domains('acl' in collect)
|
|
if 'trusts' in collect or 'acl' in collect or 'objectprops' in collect:
|
|
trusts_enum = DomainEnumerator(self.ad, self.pdc)
|
|
trusts_enum.dump_domain(collect,timestamp=timestamp)
|
|
if do_computer_enum:
|
|
# If we don't have a GC server, don't use it for deconflictation
|
|
have_gc = len(self.ad.gcs()) > 0
|
|
computer_enum = ComputerEnumerator(self.ad, self.pdc, collect, do_gc_lookup=have_gc, computerfile=computerfile, exclude_dcs=exclude_dcs)
|
|
computer_enum.enumerate_computers(self.ad.computers, num_workers=num_workers, timestamp=timestamp)
|
|
end_time = time.time()
|
|
minutes, seconds = divmod(int(end_time-start_time),60)
|
|
self.logger.highlight('Done in %02dM %02dS' % (minutes, seconds))
|