def process_computer(self, hostname, samname, objectsid, entry, results_q): """ Processes a single computer, pushes the results of the computer to the given queue. """ logging.debug('Querying computer: %s', hostname) c = ADComputer(hostname=hostname, samname=samname, ad=self.addomain, objectsid=objectsid) c.primarygroup = self.get_primary_membership(entry) if c.try_connect() == True: try: if 'session' in self.collect: sessions = c.rpc_get_sessions() else: sessions = [] if 'localadmin' in self.collect: unresolved = c.rpc_get_group_members(544, c.admins) c.rpc_resolve_sids(unresolved, c.admins) if 'rdp' in self.collect: unresolved = c.rpc_get_group_members(555, c.rdp) c.rpc_resolve_sids(unresolved, c.rdp) if 'dcom' in self.collect: unresolved = c.rpc_get_group_members(562, c.dcom) c.rpc_resolve_sids(unresolved, c.dcom) if 'loggedon' in self.collect: loggedon = c.rpc_get_loggedon() else: loggedon = [] if 'experimental' in self.collect: services = c.rpc_get_services() tasks = c.rpc_get_schtasks() else: services = [] tasks = [] c.rpc_close() # c.rpc_get_domain_trusts() results_q.put( ('computer', c.get_bloodhound_data(entry, self.collect))) if sessions is None: sessions = [] # Process found sessions for ses in sessions: # For every session, resolve the SAM name in the GC if needed domain = self.addomain.domain if self.addomain.num_domains > 1 and self.do_gc_lookup: try: users = self.addomain.samcache.get(samname) except KeyError: # Look up the SAM name in the GC users = self.addomain.objectresolver.gc_sam_lookup( ses['user']) if users is None: # Unknown user continue self.addomain.samcache.put(samname, users) else: users = [((u'%s@%s' % (ses['user'], domain)).upper(), 2)] # Resolve the IP to obtain the host the session is from try: target = self.addomain.dnscache.get(ses['source']) except KeyError: target = ADUtils.ip2host(ses['source'], self.addomain.dnsresolver, self.addomain.dns_tcp) # Even if the result is the IP (aka could not resolve PTR) we still cache # it since this result is unlikely to change during this run self.addomain.dnscache.put_single( ses['source'], target) if ':' in target: # IPv6 address, not very useful continue if '.' not in target: logging.debug( 'Resolved target does not look like an IP or domain. Assuming hostname: %s', target) target = '%s.%s' % (target, domain) # Put the result on the results queue. for user in users: results_q.put(('session', { 'UserName': user[0].upper(), 'ComputerName': target.upper(), 'Weight': user[1] })) if loggedon is None: loggedon = [] # Put the logged on users on the queue too for user in loggedon: results_q.put(('session', { 'UserName': ('%s@%s' % user).upper(), 'ComputerName': hostname.upper(), 'Weight': 1 })) # Process Tasks for taskuser in tasks: try: user = self.addomain.sidcache.get(taskuser) except KeyError: # Resolve SID in GC userentry = self.addomain.objectresolver.resolve_sid( taskuser) # Resolve it to an entry and store in the cache user = ADUtils.resolve_ad_entry(userentry) self.addomain.sidcache.put(taskuser, user) logging.debug('Resolved TASK SID to username: %s', user['principal']) # Use sessions for now results_q.put(('session', { 'UserName': user['principal'].upper(), 'ComputerName': hostname.upper(), 'Weight': 2 })) # Process Services for serviceuser in services: # Todo: use own cache try: user = self.addomain.sidcache.get(serviceuser) except KeyError: # Resolve UPN in GC userentry = self.addomain.objectresolver.resolve_upn( serviceuser) # Resolve it to an entry and store in the cache user = ADUtils.resolve_ad_entry(userentry) self.addomain.sidcache.put(serviceuser, user) logging.debug('Resolved Service UPN to username: %s', user['principal']) # Use sessions for now results_q.put(('session', { 'UserName': user['principal'].upper(), 'ComputerName': hostname.upper(), 'Weight': 2 })) except DCERPCException: logging.warning('Querying computer failed: %s' % hostname) except Exception as e: logging.error( 'Unhandled exception in computer %s processing: %s', hostname, str(e)) logging.info(traceback.format_exc()) else: # Write the info we have to the file regardless try: results_q.put( ('computer', c.get_bloodhound_data(entry, self.collect))) except Exception as e: logging.error( 'Unhandled exception in computer %s processing: %s', hostname, str(e)) logging.info(traceback.format_exc())
def enumerate_computers_dconly(self, timestamp=""): ''' Enumerate computer objects. This function is only used if no collection was requested that required connecting to computers anyway. ''' filename = timestamp + 'computers.json' acl = 'acl' in self.collect entries = self.addc.ad.computers.values() logging.debug('Writing computers ACL to file: %s', filename) # Use a separate queue for processing the results self.result_q = queue.Queue() results_worker = threading.Thread( target=OutputWorker.membership_write_worker, args=(self.result_q, 'computers', filename)) results_worker.daemon = True results_worker.start() if acl and not self.disable_pooling: self.aclenumerator.init_pool() # This loops over the cached entries for entry in entries: if not 'attributes' in entry: continue if 'dNSHostName' not in entry['attributes']: continue hostname = entry['attributes']['dNSHostName'] if not hostname: continue samname = entry['attributes']['sAMAccountName'] cobject = ADComputer(hostname=hostname, samname=samname, ad=self.addomain, addc=self.addc, objectsid=entry['attributes']['objectSid']) cobject.primarygroup = MembershipEnumerator.get_primary_membership( entry) computer = cobject.get_bloodhound_data(entry, self.collect, skip_acl=True) # If we are enumerating ACLs, we break out of the loop here # this is because parsing ACLs is computationally heavy and therefor is done in subprocesses if acl: if self.disable_pooling: # Debug mode, don't run this pooled since it hides exceptions self.process_acldata( parse_binary_acl( computer, 'computer', ADUtils.get_entry_property(entry, 'nTSecurityDescriptor', raw=True), self.addc.objecttype_guid_map)) else: # Process ACLs in separate processes, then call the processing function to resolve entries and write them to file self.aclenumerator.pool.apply_async( parse_binary_acl, args=(computer, 'computer', ADUtils.get_entry_property( entry, 'nTSecurityDescriptor', raw=True), self.addc.objecttype_guid_map), callback=self.process_acldata) else: # Write it to the queue -> write to file in separate thread # this is solely for consistency with acl parsing, the performance improvement is probably minimal self.result_q.put(computer) # If we are parsing ACLs, close the parsing pool first # then close the result queue and join it if acl and not self.disable_pooling: self.aclenumerator.pool.close() self.aclenumerator.pool.join() self.result_q.put(None) else: self.result_q.put(None) self.result_q.join() logging.debug('Finished writing computers')
def process_computer(self, hostname, samname, objectsid, entry, results_q): """ Processes a single computer, pushes the results of the computer to the given queue. """ logging.debug('Querying computer: %s', hostname) c = ADComputer(hostname=hostname, samname=samname, ad=self.addomain, addc=self.addc, objectsid=objectsid) c.primarygroup = self.get_primary_membership(entry) if c.try_connect() == True: try: if 'session' in self.collect: sessions = c.rpc_get_sessions() else: sessions = [] if 'localadmin' in self.collect: unresolved = c.rpc_get_group_members(544, c.admins) c.rpc_resolve_sids(unresolved, c.admins) if 'rdp' in self.collect: unresolved = c.rpc_get_group_members(555, c.rdp) c.rpc_resolve_sids(unresolved, c.rdp) if 'dcom' in self.collect: unresolved = c.rpc_get_group_members(562, c.dcom) c.rpc_resolve_sids(unresolved, c.dcom) if 'psremote' in self.collect: unresolved = c.rpc_get_group_members(580, c.psremote) c.rpc_resolve_sids(unresolved, c.psremote) if 'loggedon' in self.collect: loggedon = c.rpc_get_loggedon() else: loggedon = [] if 'experimental' in self.collect: services = c.rpc_get_services() tasks = c.rpc_get_schtasks() else: services = [] tasks = [] c.rpc_close() # c.rpc_get_domain_trusts() if sessions is None: sessions = [] # Should we use the GC? use_gc = self.addomain.num_domains > 1 and self.do_gc_lookup # Process found sessions for ses in sessions: # For every session, resolve the SAM name in the GC if needed domain = self.addomain.domain try: users = self.addomain.samcache.get(samname) except KeyError: # Look up the SAM name in the GC entries = self.addomain.objectresolver.resolve_samname( ses['user'], use_gc=use_gc) if entries is not None: users = [ user['attributes']['objectSid'] for user in entries ] if entries is None or users == []: logging.warning( 'Failed to resolve SAM name %s in current forest', samname) continue self.addomain.samcache.put(samname, users) # Resolve the IP to obtain the host the session is from try: target = self.addomain.dnscache.get(ses['source']) except KeyError: # TODO: also use discovery based on port 445 connections similar to sharphound target = ADUtils.ip2host(ses['source'], self.addomain.dnsresolver, self.addomain.dns_tcp) # Even if the result is the IP (aka could not resolve PTR) we still cache # it since this result is unlikely to change during this run self.addomain.dnscache.put_single( ses['source'], target) if ':' in target: # IPv6 address, not very useful continue if '.' not in target: logging.debug( 'Resolved target does not look like an IP or domain. Assuming hostname: %s', target) target = '%s.%s' % (target, domain) # Resolve target hostname try: hostsid = self.addomain.computersidcache.get( target.lower()) except KeyError: logging.warning( 'Could not resolve hostname to SID: %s', target) continue # Put the result on the results queue. for user in users: c.sessions.append({ 'ComputerId': hostsid, 'UserId': user }) if loggedon is None: loggedon = [] # Put the logged on users on the queue too for user, userdomain in loggedon: # Construct fake UPN to cache this user fupn = '%s@%s' % (user.upper(), userdomain.upper()) try: users = self.addomain.samcache.get(fupn) except KeyError: entries = self.addomain.objectresolver.resolve_samname( user, use_gc=use_gc) if entries is not None: if len(entries) > 1: for resolved_user in entries: edn = ADUtils.get_entry_property( resolved_user, 'distinguishedName') edom = ADUtils.ldap2domain(edn).lower() if edom == userdomain.lower(): users = [ resolved_user['attributes'] ['objectSid'] ] break logging.debug( 'Skipping resolved user %s since domain does not match (%s != %s)', edn, edom, userdomain.lower()) else: users = [ resolved_user['attributes']['objectSid'] for resolved_user in entries ] if entries is None or users == []: logging.warning( 'Failed to resolve SAM name %s in current forest', samname) continue self.addomain.samcache.put(fupn, users) for resultuser in users: c.sessions.append({ 'ComputerId': objectsid, 'UserId': resultuser }) # Process Tasks for taskuser in tasks: c.sessions.append({ 'ComputerId': objectsid, 'UserId': taskuser }) # Process Services for serviceuser in services: try: user = self.addomain.sidcache.get(serviceuser) except KeyError: # Resolve UPN in GC userentry = self.addomain.objectresolver.resolve_upn( serviceuser) # Resolve it to an entry and store in the cache self.addomain.sidcache.put( serviceuser, userentry['attributes']['objectSid']) user = userentry['attributes']['objectSid'] logging.debug('Resolved Service UPN to SID: %s', user['objectsid']) c.sessions.append({ 'ComputerId': objectsid, 'UserId': user }) results_q.put( ('computer', c.get_bloodhound_data(entry, self.collect))) except DCERPCException: logging.debug(traceback.format_exc()) logging.warning('Querying computer failed: %s', hostname) except Exception as e: logging.error( 'Unhandled exception in computer %s processing: %s', hostname, str(e)) logging.info(traceback.format_exc()) else: # Write the info we have to the file regardless try: results_q.put( ('computer', c.get_bloodhound_data(entry, self.collect))) except Exception as e: logging.error( 'Unhandled exception in computer %s processing: %s', hostname, str(e)) logging.info(traceback.format_exc())