Skip to content

Commit 3b424af

Browse files
committed
Resolve dependency conflicts, regenerate lockfile, and sync with upstream
2 parents 8f96e01 + 08b43bf commit 3b424af

64 files changed

Lines changed: 3295 additions & 1761 deletions

Some content is hidden

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

.github/workflows/build-binaries.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ jobs:
2727
if: runner.os == 'windows'
2828
uses: actions/upload-artifact@v4
2929
with:
30-
name: nxc.exe
30+
name: nxc-windows.exe
3131
path: dist/nxc.exe
3232
- name: Upload Nix/OSx Binary
3333
if: runner.os != 'windows'

netexec.spec

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@ a = Analysis(
1919
'aardwolf.commons.iosettings',
2020
'aardwolf.commons.target',
2121
'aardwolf.protocol.x224.constants',
22-
'certipy',
22+
'certipy.commands.find',
23+
'certipy.lib.formatting',
24+
'certipy.lib.target',
2325
'impacket.examples.secretsdump',
2426
'impacket.examples.regsecrets',
2527
'impacket.dcerpc.v5.lsat',

nxc/cli.py

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,18 +27,19 @@ def gen_cli_args():
2727
CODENAME = "BLS"
2828

2929
generic_parser = argparse.ArgumentParser(add_help=False, formatter_class=DisplayDefaultsNotNone)
30-
generic_group = generic_parser.add_argument_group("Generic", "Generic options for nxc across protocols")
30+
generic_group = generic_parser.add_argument_group("Generic Options")
3131
generic_group.add_argument("--version", action="store_true", help="Display nxc version")
3232
generic_group.add_argument("-t", "--threads", type=int, dest="threads", default=256, help="set how many concurrent threads to use")
3333
generic_group.add_argument("--timeout", default=None, type=int, help="max timeout in seconds of each thread")
3434
generic_group.add_argument("--jitter", metavar="INTERVAL", type=str, help="sets a random delay between each authentication")
3535

3636
output_parser = argparse.ArgumentParser(add_help=False, formatter_class=DisplayDefaultsNotNone)
37-
output_group = output_parser.add_argument_group("Output", "Options to set verbosity levels and control output")
38-
output_group.add_argument("--verbose", action="store_true", help="enable verbose output")
39-
output_group.add_argument("--debug", action="store_true", help="enable debug level information")
37+
output_group = output_parser.add_argument_group("Output Options")
4038
output_group.add_argument("--no-progress", action="store_true", help="do not displaying progress bar during scan")
4139
output_group.add_argument("--log", metavar="LOG", help="export result into a custom file")
40+
log_level = output_group.add_mutually_exclusive_group()
41+
log_level.add_argument("--verbose", action="store_true", help="enable verbose output")
42+
log_level.add_argument("--debug", action="store_true", help="enable debug level information")
4243

4344
dns_parser = argparse.ArgumentParser(add_help=False, formatter_class=DisplayDefaultsNotNone)
4445
dns_group = dns_parser.add_argument_group("DNS")
@@ -73,7 +74,7 @@ def gen_cli_args():
7374

7475
# we do module arg parsing here so we can reference the module_list attribute below
7576
module_parser = argparse.ArgumentParser(add_help=False, formatter_class=DisplayDefaultsNotNone)
76-
mgroup = module_parser.add_argument_group("Modules", "Options for nxc modules")
77+
mgroup = module_parser.add_argument_group("Modules")
7778
mgroup.add_argument("-M", "--module", choices=get_module_names(), action="append", metavar="MODULE", help="module to use")
7879
mgroup.add_argument("-o", metavar="MODULE_OPTION", nargs="+", default=[], dest="module_options", help="module options")
7980
mgroup.add_argument("-L", "--list-modules", nargs="?", type=str, const="", help="list available modules")
@@ -83,7 +84,7 @@ def gen_cli_args():
8384

8485
std_parser = argparse.ArgumentParser(add_help=False, parents=[generic_parser, output_parser, dns_parser], formatter_class=DisplayDefaultsNotNone)
8586
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)")
86-
credential_group = std_parser.add_argument_group("Authentication", "Options for authenticating")
87+
credential_group = std_parser.add_argument_group("Authentication")
8788
credential_group.add_argument("-u", "--username", metavar="USERNAME", dest="username", nargs="+", default=[], help="username(s) or file(s) containing usernames")
8889
credential_group.add_argument("-p", "--password", metavar="PASSWORD", dest="password", nargs="+", default=[], help="password(s) or file(s) containing passwords")
8990
credential_group.add_argument("-id", metavar="CRED_ID", nargs="+", default=[], type=str, dest="cred_id", help="database credential ID(s) to use for authentication")
@@ -94,13 +95,13 @@ def gen_cli_args():
9495
credential_group.add_argument("--ufail-limit", metavar="LIMIT", type=int, help="max number of failed login attempts per username")
9596
credential_group.add_argument("--fail-limit", metavar="LIMIT", type=int, help="max number of failed login attempts per host")
9697

97-
kerberos_group = std_parser.add_argument_group("Kerberos", "Options for Kerberos authentication")
98+
kerberos_group = std_parser.add_argument_group("Kerberos Authentication")
9899
kerberos_group.add_argument("-k", "--kerberos", action="store_true", help="Use Kerberos authentication")
99100
kerberos_group.add_argument("--use-kcache", action="store_true", help="Use Kerberos authentication from ccache file (KRB5CCNAME)")
100101
kerberos_group.add_argument("--aesKey", metavar="AESKEY", nargs="+", help="AES key to use for Kerberos Authentication (128 or 256 bits)")
101102
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")
102103

103-
certificate_group = std_parser.add_argument_group("Certificate", "Options for certificate authentication")
104+
certificate_group = std_parser.add_argument_group("Certificate Authentication")
104105
certificate_group.add_argument("--pfx-cert", metavar="PFXCERT", help="Use certificate authentication from pfx file .pfx")
105106
certificate_group.add_argument("--pfx-base64", metavar="PFXB64", help="Use certificate authentication from pfx file encoded in base64")
106107
certificate_group.add_argument("--pfx-pass", metavar="PFXPASS", help="Password of the pfx certificate")

nxc/connection.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,8 @@ def __init__(self, args, db, target):
176176

177177
try:
178178
self.proto_flow()
179+
except FileNotFoundError as e:
180+
self.logger.error(f"File not found error on target {target}: {e}")
179181
except Exception as e:
180182
if "ERROR_DEPENDENT_SERVICES_RUNNING" in str(e):
181183
self.logger.error(f"Exception while calling proto_flow() on target {target}: {e}")

nxc/database.py

Lines changed: 48 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,21 @@
11
import configparser
22
import ipaddress
3+
import platform
34
import shutil
45
import sys
56
from os import mkdir
67
from os.path import exists
78
from os.path import join as path_join
89
from pathlib import Path
9-
from sqlite3 import connect
1010
from threading import Lock
1111

12-
from sqlalchemy import create_engine, MetaData, func
13-
from sqlalchemy.exc import IllegalStateChangeError
12+
from sqlalchemy import Table, create_engine, MetaData, func
13+
from sqlalchemy.exc import (
14+
IllegalStateChangeError,
15+
NoInspectionAvailable,
16+
NoSuchTableError,
17+
)
1418
from sqlalchemy.orm import sessionmaker, scoped_session
15-
1619
from nxc.loaders.protocolloader import ProtocolLoader
1720
from nxc.logger import nxc_logger
1821
from nxc.paths import WORKSPACE_DIR
@@ -57,25 +60,15 @@ def init_protocol_dbs(workspace_name, p_loader=None):
5760
if p_loader is None:
5861
p_loader = ProtocolLoader()
5962
protocols = p_loader.get_protocols()
60-
6163
for protocol in protocols:
6264
protocol_object = p_loader.load_protocol(protocols[protocol]["dbpath"])
6365
proto_db_path = path_join(WORKSPACE_DIR, workspace_name, f"{protocol}.db")
6466

6567
if not exists(proto_db_path):
6668
print(f"[*] Initializing {protocol.upper()} protocol database")
67-
conn = connect(proto_db_path)
68-
c = conn.cursor()
69-
70-
# try to prevent some weird sqlite I/O errors
71-
c.execute("PRAGMA journal_mode = OFF")
72-
c.execute("PRAGMA foreign_keys = 1")
73-
74-
protocol_object.database.db_schema(c)
75-
76-
# commit the changes and close everything off
77-
conn.commit()
78-
conn.close()
69+
db_engine = create_db_engine(proto_db_path)
70+
protocol_object.database.db_schema(db_engine)
71+
db_engine.dispose()
7972

8073

8174
def create_workspace(workspace_name, p_loader=None):
@@ -155,6 +148,44 @@ def __init__(self, db_engine):
155148
def reflect_tables(self):
156149
raise NotImplementedError("Reflect tables not implemented")
157150

151+
def reflect_table(self, table):
152+
with self.db_engine.connect():
153+
try:
154+
reflected_table = Table(table.__tablename__, self.metadata, autoload_with=self.db_engine)
155+
156+
# Check for column addition / deletion
157+
reflected_columns = set(reflected_table.columns.keys())
158+
orm_columns = {column.name for column in table.__table__.columns}
159+
if reflected_columns != orm_columns:
160+
raise ValueError(f"Schema mismatch detected! ORM columns: {orm_columns}, Reflected columns: {reflected_columns}")
161+
162+
# Check for constraint changes
163+
reflected_constraints = [(type(c), c.columns.keys()) for c in reflected_table._sorted_constraints]
164+
orm_constraints = [(type(c), c.columns.keys()) for c in table.__table__._sorted_constraints]
165+
if reflected_constraints != orm_constraints:
166+
raise ValueError(f"Schema mismatch detected! ORM constraints: {orm_constraints}, Reflected constraints: {reflected_constraints}")
167+
168+
return reflected_table
169+
except (NoInspectionAvailable, NoSuchTableError, ValueError) as e:
170+
commands_platform = {
171+
"Windows": [
172+
f"cmd /c copy {self.db_path} %USERPROFILE%\\nxc_{self.protocol.lower()}.bak",
173+
f"del {self.db_path}"
174+
],
175+
"Linux": [
176+
f"cp {self.db_path} ~/nxc_{self.protocol.lower()}.bak",
177+
f"rm -f {self.db_path}"
178+
]
179+
}
180+
copy_command = commands_platform["Windows"][0] if platform.system() == "Windows" else commands_platform["Linux"][0]
181+
delete_command = commands_platform["Windows"][1] if platform.system() == "Windows" else commands_platform["Linux"][1]
182+
nxc_logger.fail(f"Schema mismatch detected for table '{table.__tablename__}' in protocol '{self.protocol}'")
183+
nxc_logger.debug(e)
184+
nxc_logger.fail("This is probably because a newer version of nxc is being run on an old DB schema.")
185+
nxc_logger.fail(f"Optionally save the old DB data (`{copy_command}`)")
186+
nxc_logger.fail(f"Then remove the {self.protocol} DB (`{delete_command}`) and run nxc to initialize the new DB")
187+
sys.exit()
188+
158189
def shutdown_db(self):
159190
try:
160191
self.sess.close()

nxc/logger.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ def __init__(self, extra=None, merge_extra=False):
106106
logging.getLogger("unicrypto").disabled = True
107107
logging.getLogger("asyncio").setLevel(logging.ERROR)
108108
logging.getLogger("neo4j").setLevel(logging.ERROR)
109+
logging.getLogger("pypsrp").setLevel(logging.ERROR)
109110

110111
def format(self, msg, *args, **kwargs):
111112
"""Format msg for output
@@ -179,7 +180,11 @@ def add_file_log(self, log_file=None):
179180
file_creation = False
180181

181182
if not os.path.isfile(output_file):
182-
open(output_file, "x") # noqa: SIM115
183+
try:
184+
open(output_file, "x") # noqa: SIM115
185+
except FileNotFoundError:
186+
print(f"{colored('[-]', 'red', attrs=['bold'])} Log file path does not exist: {os.path.dirname(output_file)}")
187+
exit(1)
183188
file_creation = True
184189

185190
file_handler = RotatingFileHandler(output_file, maxBytes=100000, encoding="utf-8")

0 commit comments

Comments
 (0)