121 lines
4.5 KiB
Python
121 lines
4.5 KiB
Python
import sys
|
|
import time
|
|
|
|
from nxc.logger import NXCAdapter
|
|
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:
|
|
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 = NXCAdapter(extra={"protocol": "LDAP", "host": host, "port": port, "hostname": hostname})
|
|
|
|
def connect(self):
|
|
if len(self.ad.dcs()) == 0:
|
|
self.logger.fail("Could not find a domain controller. Consider specifying a domain and/or DNS server.")
|
|
sys.exit(1)
|
|
|
|
if not self.ad.baseDN:
|
|
self.logger.fail("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)
|
|
|
|
|
|
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))
|