Skip to content

Commit 84b59d6

Browse files
committed
Merge branch 'main' into neff-add-certipy-find
2 parents 913605b + 0278b81 commit 84b59d6

148 files changed

Lines changed: 1840 additions & 680 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

nxc/cli.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,13 +76,13 @@ def gen_cli_args():
7676
mgroup = module_parser.add_argument_group("Modules", "Options for nxc modules")
7777
mgroup.add_argument("-M", "--module", choices=get_module_names(), action="append", metavar="MODULE", help="module to use")
7878
mgroup.add_argument("-o", metavar="MODULE_OPTION", nargs="+", default=[], dest="module_options", help="module options")
79-
mgroup.add_argument("-L", "--list-modules", action="store_true", help="list available modules")
79+
mgroup.add_argument("-L", "--list-modules", nargs="?", type=str, const="", help="list available modules")
8080
mgroup.add_argument("--options", dest="show_module_options", action="store_true", help="display module options")
8181

8282
subparsers = parser.add_subparsers(title="Available Protocols", dest="protocol")
8383

8484
std_parser = argparse.ArgumentParser(add_help=False, parents=[generic_parser, output_parser, dns_parser], formatter_class=DisplayDefaultsNotNone)
85-
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)")
85+
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)")
8686
credential_group = std_parser.add_argument_group("Authentication", "Options for authenticating")
8787
credential_group.add_argument("-u", "--username", metavar="USERNAME", dest="username", nargs="+", default=[], help="username(s) or file(s) containing usernames")
8888
credential_group.add_argument("-p", "--password", metavar="PASSWORD", dest="password", nargs="+", default=[], help="password(s) or file(s) containing passwords")

nxc/config.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
reveal_chars_of_pwd = int(nxc_config.get("nxc", "reveal_chars_of_pwd", fallback=0))
3838
config_log = nxc_config.getboolean("nxc", "log_mode", fallback=False)
3939
host_info_colors = literal_eval(nxc_config.get("nxc", "host_info_colors", fallback=["green", "red", "yellow", "cyan"]))
40-
40+
check_guest_account = nxc_config.getboolean("nxc", "check_guest_account", fallback=False)
4141

4242
if len(host_info_colors) != 4:
4343
nxc_logger.error("Config option host_info_colors must have 4 values! Using default values.")

nxc/data/nxc.conf

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ audit_mode =
66
reveal_chars_of_pwd = 0
77
log_mode = False
88
host_info_colors = ["green", "red", "yellow", "cyan"]
9+
check_guest_account = False
910

1011
[BloodHound]
1112
bh_enabled = False

nxc/helpers/args.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,18 @@ def __call__(self, parser, namespace, values, option_string=None):
2424
# Set an attribute to track whether the value was explicitly set
2525
setattr(namespace, self.dest, values)
2626
setattr(namespace, f"{self.dest}_explicitly_set", True)
27+
28+
29+
def get_conditional_action(baseAction):
30+
class ConditionalAction(baseAction):
31+
def __init__(self, option_strings, dest, **kwargs):
32+
x = kwargs.pop("make_required", [])
33+
super().__init__(option_strings, dest, **kwargs)
34+
self.make_required = x
35+
36+
def __call__(self, parser, namespace, values, option_string=None):
37+
for x in self.make_required:
38+
x.required = True
39+
super().__call__(parser, namespace, values, option_string)
40+
41+
return ConditionalAction

nxc/helpers/misc.py

Lines changed: 98 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
1+
from enum import Enum
12
import random
23
import string
34
import re
45
import inspect
56
import os
6-
7+
from termcolor import colored
78
from ipaddress import ip_address
9+
from nxc.logger import nxc_logger
10+
from time import strftime, gmtime
811

912

1013
def identify_target_file(target_file):
@@ -145,3 +148,97 @@ def detect_if_ip(target):
145148
return True
146149
except Exception:
147150
return False
151+
152+
153+
def d2b(a):
154+
"""
155+
Function used to convert password property flags from decimal to binary
156+
format for easier interpretation of individual flag bits.
157+
"""
158+
tbin = []
159+
while a:
160+
tbin.append(a % 2)
161+
a //= 2
162+
163+
t2bin = tbin[::-1]
164+
if len(t2bin) != 8:
165+
for _x in range(6 - len(t2bin)):
166+
t2bin.insert(0, 0)
167+
return "".join([str(g) for g in t2bin])
168+
169+
170+
def convert(low, high, lockout=False):
171+
"""
172+
Convert Windows FILETIME (64-bit) values to human-readable time strings.
173+
174+
Windows stores time intervals as 64-bit values representing 100-nanosecond
175+
intervals since January 1, 1601. This function converts these values to
176+
readable format like "30 days 5 hours 15 minutes".
177+
178+
Args:
179+
low (int): Low 32 bits of the FILETIME value
180+
high (int): High 32 bits of the FILETIME value
181+
lockout (bool): If True, treats the value as a lockout duration (simpler conversion)
182+
183+
Returns:
184+
str: Human-readable time string (e.g., "42 days 5 hours 30 minutes") or
185+
special values like "Not Set", "None", or "[-] Invalid TIME"
186+
"""
187+
time = ""
188+
tmp = 0
189+
190+
if (low == 0 and high == -0x8000_0000) or (low == 0 and high == -0x8000_0000_0000_0000):
191+
return "Not Set"
192+
if low == 0 and high == 0:
193+
return "None"
194+
195+
if not lockout:
196+
if low != 0:
197+
high = abs(high + 1)
198+
else:
199+
high = abs(high)
200+
low = abs(low)
201+
202+
tmp = low + (high << 32) # convert to 64bit int
203+
tmp *= 1e-7 # convert to seconds
204+
else:
205+
tmp = abs(high) * (1e-7)
206+
207+
try:
208+
minutes = int(strftime("%M", gmtime(tmp)))
209+
hours = int(strftime("%H", gmtime(tmp)))
210+
days = int(strftime("%j", gmtime(tmp))) - 1
211+
except ValueError:
212+
return "[-] Invalid TIME"
213+
214+
if days > 1:
215+
time += f"{days} days "
216+
elif days == 1:
217+
time += f"{days} day "
218+
if hours > 1:
219+
time += f"{hours} hours "
220+
elif hours == 1:
221+
time += f"{hours} hour "
222+
if minutes > 1:
223+
time += f"{minutes} minutes "
224+
elif minutes == 1:
225+
time += f"{minutes} minute "
226+
return time
227+
228+
229+
def display_modules(args, modules):
230+
for category, color in {CATEGORY.ENUMERATION: "green", CATEGORY.CREDENTIAL_DUMPING: "cyan", CATEGORY.PRIVILEGE_ESCALATION: "magenta"}.items():
231+
# Add category filter for module listing
232+
if args.list_modules and args.list_modules.lower() != category.name.lower():
233+
continue
234+
if len([module for module in modules.values() if module["category"] == category]) > 0:
235+
nxc_logger.highlight(colored(f"{category.name}", color, attrs=["bold"]))
236+
for name, props in sorted(modules.items()):
237+
if props["category"] == category:
238+
nxc_logger.display(f"{name:<25} {props['description']}")
239+
240+
241+
class CATEGORY(Enum):
242+
ENUMERATION = "Enumeration"
243+
CREDENTIAL_DUMPING = "Credential Dumping"
244+
PRIVILEGE_ESCALATION = "Privilege Escalation"

nxc/loaders/moduleloader.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from os.path import join as path_join
99

1010
from nxc.context import Context
11+
from nxc.helpers.misc import CATEGORY
1112
from nxc.logger import NXCAdapter
1213
from nxc.paths import NXC_PATH
1314

@@ -30,6 +31,9 @@ def module_is_sane(self, module, module_path):
3031
elif not hasattr(module, "description"):
3132
self.logger.fail(f"{module_path} missing the description variable")
3233
module_error = True
34+
elif not hasattr(module, "category") or module.category not in [CATEGORY.ENUMERATION, CATEGORY.CREDENTIAL_DUMPING, CATEGORY.PRIVILEGE_ESCALATION]:
35+
self.logger.fail(f"{module_path} missing the category variable or invalid category")
36+
module_error = True
3337
elif not hasattr(module, "supported_protocols"):
3438
self.logger.fail(f"{module_path} missing the supported_protocols variable")
3539
module_error = True
@@ -92,6 +96,7 @@ def get_module_info(self, module_path):
9296
"description": module_spec.description,
9397
"options": module_spec.options.__doc__,
9498
"supported_protocols": module_spec.supported_protocols,
99+
"category": module_spec.category,
95100
"requires_admin": bool(hasattr(module_spec, "on_admin_login") and callable(module_spec.on_admin_login)),
96101
}
97102
}

nxc/logger.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,9 @@ def __init__(self, extra=None, merge_extra=False):
102102
logging.getLogger("lsassy").disabled = True
103103
logging.getLogger("dploot").disabled = True
104104
logging.getLogger("certipy").disabled = True
105+
logging.getLogger("aardwolf").disabled = True
106+
logging.getLogger("unicrypto").disabled = True
107+
logging.getLogger("asyncio").setLevel(logging.ERROR)
105108
logging.getLogger("neo4j").setLevel(logging.ERROR)
106109

107110
def format(self, msg, *args, **kwargs):

nxc/modules/adcs.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import re
22
from impacket.ldap import ldap, ldapasn1
33
from impacket.ldap.ldap import LDAPSearchError
4+
from nxc.helpers.misc import CATEGORY
45

56

67
class NXCModule:
@@ -13,6 +14,7 @@ class NXCModule:
1314
name = "adcs"
1415
description = "Find PKI Enrollment Services in Active Directory and Certificate Templates Names"
1516
supported_protocols = ["ldap"]
17+
category = CATEGORY.ENUMERATION
1618

1719
def __init__(self, context=None, module_options=None):
1820
self.context = context

nxc/modules/add-computer.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import sys
44
from impacket.dcerpc.v5 import samr, epm, transport
55
from impacket.dcerpc.v5.rpcrt import RPC_C_AUTHN_GSS_NEGOTIATE
6+
from nxc.helpers.misc import CATEGORY
67

78

89
class NXCModule:
@@ -16,6 +17,7 @@ class NXCModule:
1617
name = "add-computer"
1718
description = "Adds or deletes a domain computer"
1819
supported_protocols = ["smb"]
20+
category = CATEGORY.PRIVILEGE_ESCALATION
1921

2022
def options(self, context, module_options):
2123
"""

nxc/modules/aws-credentials.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
from nxc.helpers.misc import CATEGORY
2+
3+
14
class NXCModule:
25
"""
36
Search for aws credentials files on linux and windows machines
@@ -8,6 +11,7 @@ class NXCModule:
811
name = "aws-credentials"
912
description = "Search for aws credentials files."
1013
supported_protocols = ["ssh", "smb", "winrm"]
14+
category = CATEGORY.CREDENTIAL_DUMPING
1115

1216
def __init__(self):
1317
self.search_path_linux = "'/home/' '/tmp/'"

0 commit comments

Comments
 (0)