Skip to content

Commit 6104b5d

Browse files
authored
Merge branch 'main' into marshall-bugfix-3292024
2 parents 2e493f7 + 3be7a02 commit 6104b5d

9 files changed

Lines changed: 119 additions & 44 deletions

File tree

nxc/cli.py

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,20 @@
99
from nxc.paths import NXC_PATH
1010
from nxc.loaders.protocolloader import ProtocolLoader
1111
from nxc.helpers.logger import highlight
12-
from nxc.logger import nxc_logger
12+
from nxc.logger import nxc_logger, setup_debug_logging
1313
import importlib.metadata
1414

1515

1616
def gen_cli_args():
17-
VERSION = importlib.metadata.version("netexec")
17+
setup_debug_logging()
18+
19+
try:
20+
VERSION, COMMIT = importlib.metadata.version("netexec").split("+")
21+
except ValueError:
22+
VERSION = importlib.metadata.version("netexec")
23+
COMMIT = ""
1824
CODENAME = "nxc4u"
25+
nxc_logger.debug(f"NXC VERSION: {VERSION} - {CODENAME} - {COMMIT}")
1926

2027
parser = argparse.ArgumentParser(description=rf"""
2128
. .
@@ -34,9 +41,10 @@ def gen_cli_args():
3441
3542
{highlight('Version', 'red')} : {highlight(VERSION)}
3643
{highlight('Codename', 'red')}: {highlight(CODENAME)}
44+
{highlight('Commit', 'red')} : {highlight(COMMIT)}
3745
""", formatter_class=RawTextHelpFormatter)
3846

39-
parser.add_argument("-t", type=int, dest="threads", default=100, help="set how many concurrent threads to use (default: 100)")
47+
parser.add_argument("-t", type=int, dest="threads", default=256, help="set how many concurrent threads to use (default: 256)")
4048
parser.add_argument("--timeout", default=None, type=int, help="max timeout in seconds of each thread (default: None)")
4149
parser.add_argument("--jitter", metavar="INTERVAL", type=str, help="sets a random delay between each connection (default: None)")
4250
parser.add_argument("--no-progress", action="store_true", help="Not displaying progress bar during scan")
@@ -95,7 +103,7 @@ def gen_cli_args():
95103
sys.exit(1)
96104

97105
if args.version:
98-
print(f"{VERSION} - {CODENAME}")
106+
print(f"{VERSION} - {CODENAME} - {COMMIT}")
99107
sys.exit(1)
100108

101109
return args

nxc/connection.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,10 @@ def __init__(self, args, db, host):
121121
try:
122122
self.proto_flow()
123123
except Exception as e:
124-
self.logger.exception(f"Exception while calling proto_flow() on target {self.host}: {e}")
124+
if "ERROR_DEPENDENT_SERVICES_RUNNING" in str(e):
125+
self.logger.error(f"Exception while calling proto_flow() on target {self.host}: {e}")
126+
else:
127+
self.logger.exception(f"Exception while calling proto_flow() on target {self.host}: {e}")
125128

126129
@staticmethod
127130
def proto_args(std_parser, module_parser):

nxc/logger.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,31 @@
1111
from rich.logging import RichHandler
1212
import functools
1313
import inspect
14+
import argparse
1415

1516

17+
def parse_debug_args():
18+
debug_parser = argparse.ArgumentParser(add_help=False)
19+
debug_parser.add_argument("--debug", action="store_true")
20+
debug_parser.add_argument("--verbose", action="store_true")
21+
args, _ = debug_parser.parse_known_args()
22+
return args
23+
24+
def setup_debug_logging():
25+
debug_args = parse_debug_args()
26+
root_logger = logging.getLogger("root")
27+
28+
if debug_args.verbose:
29+
nxc_logger.logger.setLevel(logging.INFO)
30+
root_logger.setLevel(logging.INFO)
31+
elif debug_args.debug:
32+
nxc_logger.logger.setLevel(logging.DEBUG)
33+
root_logger.setLevel(logging.DEBUG)
34+
else:
35+
nxc_logger.logger.setLevel(logging.ERROR)
36+
root_logger.setLevel(logging.ERROR)
37+
38+
1639
def create_temp_logger(caller_frame, formatted_text, args, kwargs):
1740
"""Create a temporary logger for emitting a log where we need to override the calling file & line number, since these are obfuscated"""
1841
temp_logger = logging.getLogger("temp")
@@ -72,6 +95,7 @@ def __init__(self, extra=None):
7295
logging.getLogger("pypykatz").disabled = True
7396
logging.getLogger("minidump").disabled = True
7497
logging.getLogger("lsassy").disabled = True
98+
logging.getLogger("neo4j").setLevel(logging.ERROR)
7599

76100
def format(self, msg, *args, **kwargs): # noqa: A003
77101
"""Format msg for output

nxc/modules/msol.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
# Based on the article : https://blog.xpnsec.com/azuread-connect-for-redteam/
44
from sys import exit
55
from os import path
6-
import sys
6+
from nxc.paths import TMP_PATH
77
from nxc.helpers.powershell import get_ps_script
88

99

@@ -49,14 +49,14 @@ def exec_script(self, _, connection):
4949

5050
def on_admin_login(self, context, connection):
5151
if self.use_embedded:
52-
file_to_upload = "/tmp/msol.ps1"
52+
file_to_upload = f"{TMP_PATH}/msol.ps1"
5353

5454
try:
5555
with open(file_to_upload, "w") as msol:
5656
msol.write(self.msol_embedded)
5757
except FileNotFoundError:
5858
context.log.fail(f"Impersonate file specified '{file_to_upload}' does not exist!")
59-
sys.exit(1)
59+
exit(1)
6060

6161
else:
6262
if path.isfile(self.MSOL_PS1):

nxc/modules/scuffy.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import ntpath
22
from sys import exit
3+
from nxc.paths import TMP_PATH
34

45

56
class NXCModule:
@@ -44,7 +45,7 @@ def options(self, context, module_options):
4445
exit(1)
4546

4647
self.scf_name = module_options["NAME"]
47-
self.scf_path = f"/tmp/{self.scf_name}.scf"
48+
self.scf_path = f"{TMP_PATH}/{self.scf_name}.scf"
4849
self.file_path = ntpath.join("\\", f"{self.scf_name}.scf")
4950

5051
if not self.cleanup:

nxc/netexec.py

Lines changed: 4 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@
2121
from os.path import exists
2222
from os.path import join as path_join
2323
from sys import exit
24-
import logging
2524
from rich.progress import Progress
2625
import platform
2726

@@ -42,11 +41,11 @@ async def start_run(protocol_obj, args, db, targets):
4241
futures = []
4342
nxc_logger.debug("Creating ThreadPoolExecutor")
4443
if args.no_progress or len(targets) == 1:
45-
with ThreadPoolExecutor(max_workers=args.threads + 1) as executor:
44+
with ThreadPoolExecutor(max_workers=args.threads) as executor:
4645
nxc_logger.debug(f"Creating thread for {protocol_obj}")
4746
futures = [executor.submit(protocol_obj, args, db, target) for target in targets]
4847
else:
49-
with Progress(console=nxc_console) as progress, ThreadPoolExecutor(max_workers=args.threads + 1) as executor:
48+
with Progress(console=nxc_console) as progress, ThreadPoolExecutor(max_workers=args.threads) as executor:
5049
current = 0
5150
total = len(targets)
5251
tasks = progress.add_task(
@@ -67,29 +66,17 @@ async def start_run(protocol_obj, args, db, targets):
6766

6867
def main():
6968
first_run_setup(nxc_logger)
70-
root_logger = logging.getLogger("root")
7169
args = gen_cli_args()
7270

73-
if args.verbose:
74-
nxc_logger.logger.setLevel(logging.INFO)
75-
root_logger.setLevel(logging.INFO)
76-
elif args.debug:
77-
nxc_logger.logger.setLevel(logging.DEBUG)
78-
root_logger.setLevel(logging.DEBUG)
79-
else:
80-
nxc_logger.logger.setLevel(logging.ERROR)
81-
root_logger.setLevel(logging.ERROR)
82-
logging.getLogger("neo4j").setLevel(logging.ERROR)
83-
8471
# if these are the same, it might double log to file (two FileHandlers will be added)
8572
# but this should never happen by accident
8673
if config_log:
8774
nxc_logger.add_file_log()
8875
if hasattr(args, "log") and args.log:
8976
nxc_logger.add_file_log(args.log)
9077

91-
nxc_logger.debug("PYTHON VERSION: " + sys.version)
92-
nxc_logger.debug("RUNNING ON: " + platform.system() + " Release: " + platform.release())
78+
nxc_logger.debug(f"PYTHON VERSION: {sys.version}")
79+
nxc_logger.debug(f"RUNNING ON: {platform.system()} Release: {platform.release()}")
9380
nxc_logger.debug(f"Passed args: {args}")
9481

9582
# FROM HERE ON A PROTOCOL IS REQUIRED

nxc/protocols/ldap.py

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -753,11 +753,14 @@ def users(self):
753753
self.logger.highlight(f"{'-Username-':<30}{'-Last PW Set-':<20}{'-BadPW-':<8}{'-Description-':<60}")
754754
for user in users:
755755
# TODO: functionize this - we do this calculation in a bunch of places, different, including in the `pso` module
756-
timestamp_seconds = int(user.get("pwdLastSet", "")) / 10**7
757-
start_date = datetime(1601, 1, 1)
758-
parsed_pw_last_set = (start_date + timedelta(seconds=timestamp_seconds)).replace(microsecond=0).strftime("%Y-%m-%d %H:%M:%S")
759-
if parsed_pw_last_set == "1601-01-01 00:00:00":
760-
parsed_pw_last_set = "<never>"
756+
parsed_pw_last_set = ""
757+
pwd_last_set = user.get("pwdLastSet", "")
758+
if pwd_last_set != "":
759+
timestamp_seconds = int(pwd_last_set) / 10**7
760+
start_date = datetime(1601, 1, 1)
761+
parsed_pw_last_set = (start_date + timedelta(seconds=timestamp_seconds)).replace(microsecond=0).strftime("%Y-%m-%d %H:%M:%S")
762+
if parsed_pw_last_set == "1601-01-01 00:00:00":
763+
parsed_pw_last_set = "<never>"
761764
# we default attributes to blank strings if they don't exist in the dict
762765
self.logger.highlight(f"{user.get('sAMAccountName', ''):<30}{parsed_pw_last_set:<20}{user.get('badPwdCount', ''):<8}{user.get('description', ''):<60}")
763766

@@ -939,7 +942,7 @@ def asreproast(self):
939942

940943
def kerberoasting(self):
941944
# Building the search filter
942-
searchFilter = "(&(servicePrincipalName=*)(UserAccountControl:1.2.840.113556.1.4.803:=512)(!(UserAccountControl:1.2.840.113556.1.4.803:=2))(!(objectCategory=computer)))"
945+
searchFilter = "(&(servicePrincipalName=*)(!(objectCategory=computer)))"
943946
attributes = [
944947
"servicePrincipalName",
945948
"sAMAccountName",
@@ -990,7 +993,7 @@ def kerberoasting(self):
990993

991994
if mustCommit is True:
992995
if int(userAccountControl) & UF_ACCOUNTDISABLE:
993-
self.logger.debug(f"Bypassing disabled account {sAMAccountName} ")
996+
self.logger.highlight(f"Bypassing disabled account {sAMAccountName} ")
994997
else:
995998
answers += [[spn, sAMAccountName, memberOf, pwdLastSet, lastLogon, delegation] for spn in SPNs]
996999
except Exception as e:

poetry.lock

Lines changed: 51 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ neo4j = "^5.0.0"
4343
pylnk3 = "^0.4.2"
4444
pypsrp = "^0.8.1"
4545
paramiko = "^3.3.1"
46-
impacket = { git = "https://github.com/Pennyw0rth/impacket.git", branch = "gkdi" }
46+
impacket = { git = "https://github.com/fortra/impacket.git" }
4747
dsinternals = "^1.2.4"
4848
xmltodict = "^0.13.0"
4949
terminaltables = "^3.1.0"
@@ -61,9 +61,9 @@ aiosqlite = "^0.19.0"
6161
pyasn1-modules = "^0.3.0"
6262
rich = "^13.3.5"
6363
python-libnmap = "^0.7.3"
64-
oscrypto = { git = "https://github.com/Pennyw0rth/oscrypto" } # Pypi version currently broken, see: https://github.com/wbond/oscrypto/issues/78 (as of 9/23)
6564
argcomplete = "^3.1.4"
6665
python-dateutil = ">=2.8.2"
66+
poetry-dynamic-versioning = "^1.2.0"
6767

6868
[tool.poetry.group.dev.dependencies]
6969
flake8 = "*"
@@ -72,8 +72,13 @@ pytest = "^7.2.2"
7272
ruff = "=0.0.292"
7373

7474
[build-system]
75-
requires = ["poetry-core>=1.2.0"]
76-
build-backend = "poetry.core.masonry.api"
75+
requires = ["poetry-core>=1.2.0", "poetry-dynamic-versioning>=1.0.0,<2.0.0"]
76+
build-backend = "poetry_dynamic_versioning.backend"
77+
78+
[tool.poetry-dynamic-versioning]
79+
enable = true
80+
pattern = "(?P<base>\\d+\\.\\d+\\.\\d+)"
81+
format = "{base}+{commit}"
7782

7883
[tool.ruff]
7984
# Ruff doesn't enable pycodestyle warnings (`W`) or

0 commit comments

Comments
 (0)