Skip to content

Commit 857cb47

Browse files
committed
Merge branch 'main' into ntlm_reflection
2 parents 47f90aa + 2f62843 commit 857cb47

131 files changed

Lines changed: 525 additions & 225 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/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: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@ def module_is_sane(self, module, module_path):
3030
elif not hasattr(module, "description"):
3131
self.logger.fail(f"{module_path} missing the description variable")
3232
module_error = True
33+
elif not hasattr(module, "category"):
34+
self.logger.fail(f"{module_path} missing the category variable")
35+
module_error = True
3336
elif not hasattr(module, "supported_protocols"):
3437
self.logger.fail(f"{module_path} missing the supported_protocols variable")
3538
module_error = True
@@ -92,6 +95,7 @@ def get_module_info(self, module_path):
9295
"description": module_spec.description,
9396
"options": module_spec.options.__doc__,
9497
"supported_protocols": module_spec.supported_protocols,
98+
"category": module_spec.category,
9599
"requires_admin": bool(hasattr(module_spec, "on_admin_login") and callable(module_spec.on_admin_login)),
96100
}
97101
}

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/'"

nxc/modules/backup_operator.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,15 @@
66
from impacket.smbconnection import SessionError
77
from impacket.dcerpc.v5 import transport, rrp
88
from impacket.dcerpc.v5.rpcrt import RPC_C_AUTHN_GSS_NEGOTIATE
9+
from nxc.helpers.misc import CATEGORY
910
from nxc.paths import NXC_PATH
1011

1112

1213
class NXCModule:
1314
name = "backup_operator"
1415
description = "Exploit user in backup operator group to dump NTDS @mpgn_x64"
1516
supported_protocols = ["smb"]
17+
category = CATEGORY.PRIVILEGE_ESCALATION
1618

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

nxc/modules/badsuccessor.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from impacket.ldap import ldaptypes
2+
from nxc.helpers.misc import CATEGORY
23
from nxc.parsers.ldap_results import parse_result_attributes
34
from ldap3.protocol.microsoft import security_descriptor_control
45

@@ -79,6 +80,7 @@ class NXCModule:
7980
name = "badsuccessor"
8081
description = "Check if vulnerable to bad successor attack (DMSA)"
8182
supported_protocols = ["ldap"]
83+
category = CATEGORY.ENUMERATION
8284

8385
def __init__(self):
8486
self.context = None

nxc/modules/bitlocker.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,14 @@
33
from impacket.dcerpc.v5.dtypes import NULL
44
from impacket.dcerpc.v5.dcomrt import DCOMConnection
55
from impacket.dcerpc.v5.rpcrt import RPC_C_AUTHN_LEVEL_PKT_PRIVACY
6+
from nxc.helpers.misc import CATEGORY
67

78

89
class NXCModule:
910
name = "bitlocker"
1011
description = "Enumerating BitLocker Status on target(s) If it is enabled or disabled."
1112
supported_protocols = ["smb", "wmi"]
13+
category = CATEGORY.ENUMERATION
1214

1315
def __init__(self, context=None, module_options=None):
1416
self.context = context

nxc/modules/change-password.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import sys
22
from impacket.dcerpc.v5 import samr, epm, transport
33
from impacket.dcerpc.v5.rpcrt import DCERPCException
4+
from nxc.helpers.misc import CATEGORY
45

56

67
class NXCModule:
@@ -12,6 +13,7 @@ class NXCModule:
1213
name = "change-password"
1314
description = "Change or reset user passwords via various protocols"
1415
supported_protocols = ["smb"]
16+
category = CATEGORY.PRIVILEGE_ESCALATION
1517

1618
def options(self, context, module_options):
1719
"""

0 commit comments

Comments
 (0)