From b4f3bacb9980a9e3719236921ad3c391b9f130d7 Mon Sep 17 00:00:00 2001 From: Marshall Hallenbeck Date: Fri, 17 Nov 2023 16:55:57 -0500 Subject: [PATCH 01/14] refactor(nxcdb): move shared fdatabase functions to single file --- nxc/database.py | 90 +++++++++++++++++++++++++++++++++++++++++++++++ nxc/nxcdb.py | 93 +++++-------------------------------------------- 2 files changed, 99 insertions(+), 84 deletions(-) create mode 100644 nxc/database.py diff --git a/nxc/database.py b/nxc/database.py new file mode 100644 index 00000000..556e192a --- /dev/null +++ b/nxc/database.py @@ -0,0 +1,90 @@ +import sys +import configparser +import shutil +from sqlalchemy import create_engine +from sqlite3 import connect +from os import mkdir +from os.path import exists +from os.path import join as path_join + +from nxc.loaders.protocolloader import ProtocolLoader +from nxc.paths import WS_PATH, WORKSPACE_DIR + +def create_db_engine(db_path): + return create_engine(f"sqlite:///{db_path}", isolation_level="AUTOCOMMIT", future=True) + + +def open_config(config_path): + try: + config = configparser.ConfigParser() + config.read(config_path) + except Exception as e: + print(f"[-] Error reading nxc.conf: {e}") + sys.exit(1) + return config + + +def get_workspace(config): + return config.get("nxc", "workspace") + + +def get_db(config): + return config.get("nxc", "last_used_db") + + +def write_configfile(config, config_path): + with open(config_path, "w") as configfile: + config.write(configfile) + + +def create_workspace(workspace_name, p_loader, protocols): + mkdir(path_join(WORKSPACE_DIR, workspace_name)) + + 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") + 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 delete_workspace(workspace_name): + shutil.rmtree(path_join(WORKSPACE_DIR, workspace_name)) + + +def initialize_db(logger): + if not exists(path_join(WS_PATH, "default")): + logger.debug("Creating default workspace") + mkdir(path_join(WS_PATH, "default")) + + 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(WS_PATH, "default", f"{protocol}.db") + + if not exists(proto_db_path): + logger.debug(f"Initializing {protocol.upper()} protocol database") + conn = connect(proto_db_path) + c = conn.cursor() + # try to prevent some weird sqlite I/O errors + c.execute("PRAGMA journal_mode = OFF") # could try setting to PERSIST if DB corruption starts occurring + c.execute("PRAGMA foreign_keys = 1") + # set a small timeout (5s) so if another thread is writing to the database, the entire program doesn't crash + c.execute("PRAGMA busy_timeout = 5000") + protocol_object.database.db_schema(c) + # commit the changes and close everything off + conn.commit() + conn.close() \ No newline at end of file diff --git a/nxc/nxcdb.py b/nxc/nxcdb.py index 0c9f4c28..27ce0c7f 100644 --- a/nxc/nxcdb.py +++ b/nxc/nxcdb.py @@ -1,31 +1,23 @@ import cmd -import configparser import csv +import sys import os from os import listdir from os.path import exists from os.path import join as path_join -import shutil -from sqlite3 import connect -import sys from textwrap import dedent - from requests import get, post, ConnectionError -from sqlalchemy import create_engine from terminaltables import AsciiTable from nxc.loaders.protocolloader import ProtocolLoader -from nxc.paths import CONFIG_PATH, WS_PATH, WORKSPACE_DIR +from nxc.paths import CONFIG_PATH, WORKSPACE_DIR +from nxc.database import create_db_engine, open_config, get_workspace, get_db, write_configfile, create_workspace class UserExitedProto(Exception): pass -def create_db_engine(db_path): - return create_engine(f"sqlite:///{db_path}", isolation_level="AUTOCOMMIT", future=True) - - def print_table(data, title=None): print() table = AsciiTable(data) @@ -446,29 +438,15 @@ class NXCDBMenu(cmd.Cmd): def __init__(self, config_path): cmd.Cmd.__init__(self) self.config_path = config_path - - try: - self.config = configparser.ConfigParser() - self.config.read(self.config_path) - except Exception as e: - print(f"[-] Error reading nxc.conf: {e}") - sys.exit(1) - self.conn = None self.p_loader = ProtocolLoader() self.protocols = self.p_loader.get_protocols() - self.workspace = self.config.get("nxc", "workspace") + self.config = open_config(self.config_path) + self.workspace = get_workspace(self.config) + self.db = get_db(self.config) self.do_workspace(self.workspace) - self.db = self.config.get("nxc", "last_used_db") - if self.db: - self.do_proto(self.db) - - def write_configfile(self): - with open(self.config_path, "w") as configfile: - self.config.write(configfile) - def do_proto(self, proto): if not proto: return @@ -479,7 +457,7 @@ class NXCDBMenu(cmd.Cmd): db_nav_object = self.p_loader.load_protocol(self.protocols[proto]["nvpath"]) db_object = self.p_loader.load_protocol(self.protocols[proto]["dbpath"]) self.config.set("nxc", "last_used_db", proto) - self.write_configfile() + write_configfile(self.config, self.config_path) try: proto_menu = db_nav_object.navigator(self, db_object.database(self.conn), proto) proto_menu.cmdloop() @@ -506,7 +484,7 @@ class NXCDBMenu(cmd.Cmd): if subcommand == "create": new_workspace = line.split()[1].strip() print(f"[*] Creating workspace '{new_workspace}'") - self.create_workspace(new_workspace, self.p_loader, self.protocols) + create_workspace(new_workspace, self.p_loader, self.protocols) self.do_workspace(new_workspace) elif subcommand == "list": print("[*] Enumerating Workspaces") @@ -517,7 +495,7 @@ class NXCDBMenu(cmd.Cmd): print(workspace) elif exists(path_join(WORKSPACE_DIR, line)): self.config.set("nxc", "workspace", line) - self.write_configfile() + write_configfile(self.config, self.config_path) self.workspace = line self.prompt = f"nxcdb ({line}) > " @@ -539,59 +517,6 @@ class NXCDBMenu(cmd.Cmd): """ print_help(help_string) - @staticmethod - def create_workspace(workspace_name, p_loader, protocols): - os.mkdir(path_join(WORKSPACE_DIR, workspace_name)) - - 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") - 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 delete_workspace(workspace_name): - shutil.rmtree(path_join(WORKSPACE_DIR, workspace_name)) - - -def initialize_db(logger): - if not exists(path_join(WS_PATH, "default")): - logger.debug("Creating default workspace") - os.mkdir(path_join(WS_PATH, "default")) - - 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(WS_PATH, "default", f"{protocol}.db") - - if not exists(proto_db_path): - logger.debug(f"Initializing {protocol.upper()} protocol database") - conn = connect(proto_db_path) - c = conn.cursor() - # try to prevent some weird sqlite I/O errors - c.execute("PRAGMA journal_mode = OFF") # could try setting to PERSIST if DB corruption starts occurring - c.execute("PRAGMA foreign_keys = 1") - # set a small timeout (5s) so if another thread is writing to the database, the entire program doesn't crash - c.execute("PRAGMA busy_timeout = 5000") - protocol_object.database.db_schema(c) - # commit the changes and close everything off - conn.commit() - conn.close() - def main(): if not exists(CONFIG_PATH): From 861626d06127976a4dc25ce170ede3e01286614c Mon Sep 17 00:00:00 2001 From: Marshall Hallenbeck Date: Fri, 17 Nov 2023 21:24:03 -0500 Subject: [PATCH 02/14] refactor: deduplicate code and simplify initial db setup --- nxc/database.py | 51 +++++++++++++++++++++++------------------------- nxc/first_run.py | 4 ++-- nxc/netexec.py | 7 +------ nxc/nxcdb.py | 2 +- nxc/paths.py | 2 +- 5 files changed, 29 insertions(+), 37 deletions(-) diff --git a/nxc/database.py b/nxc/database.py index 556e192a..45ddc241 100644 --- a/nxc/database.py +++ b/nxc/database.py @@ -8,7 +8,9 @@ from os.path import exists from os.path import join as path_join from nxc.loaders.protocolloader import ProtocolLoader -from nxc.paths import WS_PATH, WORKSPACE_DIR +from nxc.paths import WORKSPACE_DIR +from nxc.logger import nxc_logger + def create_db_engine(db_path): return create_engine(f"sqlite:///{db_path}", isolation_level="AUTOCOMMIT", future=True) @@ -37,8 +39,25 @@ def write_configfile(config, config_path): config.write(configfile) -def create_workspace(workspace_name, p_loader, protocols): - mkdir(path_join(WORKSPACE_DIR, workspace_name)) +def create_workspace(workspace_name, p_loader=None): + """ + Create a new workspace with the given name. + + Args: + ---- + workspace_name (str): The name of the workspace. + + Returns: + ------- + None + """ + if not exists(path_join(WORKSPACE_DIR, workspace_name)): + nxc_logger.debug(f"Creating {workspace_name} workspace") + mkdir(path_join(WORKSPACE_DIR, workspace_name)) + + 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"]) @@ -64,27 +83,5 @@ def delete_workspace(workspace_name): shutil.rmtree(path_join(WORKSPACE_DIR, workspace_name)) -def initialize_db(logger): - if not exists(path_join(WS_PATH, "default")): - logger.debug("Creating default workspace") - mkdir(path_join(WS_PATH, "default")) - - 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(WS_PATH, "default", f"{protocol}.db") - - if not exists(proto_db_path): - logger.debug(f"Initializing {protocol.upper()} protocol database") - conn = connect(proto_db_path) - c = conn.cursor() - # try to prevent some weird sqlite I/O errors - c.execute("PRAGMA journal_mode = OFF") # could try setting to PERSIST if DB corruption starts occurring - c.execute("PRAGMA foreign_keys = 1") - # set a small timeout (5s) so if another thread is writing to the database, the entire program doesn't crash - c.execute("PRAGMA busy_timeout = 5000") - protocol_object.database.db_schema(c) - # commit the changes and close everything off - conn.commit() - conn.close() \ No newline at end of file +def initialize_db(): + create_workspace("default") \ No newline at end of file diff --git a/nxc/first_run.py b/nxc/first_run.py index e60979bc..c3b55f14 100755 --- a/nxc/first_run.py +++ b/nxc/first_run.py @@ -3,7 +3,7 @@ from os.path import exists from os.path import join as path_join import shutil from nxc.paths import NXC_PATH, CONFIG_PATH, TMP_PATH, DATA_PATH -from nxc.nxcdb import initialize_db +from nxc.database import initialize_db from nxc.logger import nxc_logger @@ -29,7 +29,7 @@ def first_run_setup(logger=nxc_logger): logger.display(f"Creating missing folder {folder}") mkdir(path_join(NXC_PATH, folder)) - initialize_db(logger) + initialize_db() if not exists(CONFIG_PATH): logger.display("Copying default configuration file") diff --git a/nxc/netexec.py b/nxc/netexec.py index e09e249e..d41dbc77 100755 --- a/nxc/netexec.py +++ b/nxc/netexec.py @@ -12,6 +12,7 @@ from nxc.paths import NXC_PATH from nxc.console import nxc_console from nxc.logger import nxc_logger from nxc.config import nxc_config, nxc_workspace, config_log, ignore_opsec +from nxc.database import create_db_engine from concurrent.futures import ThreadPoolExecutor, as_completed import asyncio from nxc.helpers import powershell @@ -21,7 +22,6 @@ from os.path import exists from os.path import join as path_join from sys import exit import logging -import sqlalchemy from rich.progress import Progress import platform @@ -38,11 +38,6 @@ if platform.system() != "Windows": resource.setrlimit(resource.RLIMIT_NOFILE, file_limit) - -def create_db_engine(db_path): - return sqlalchemy.create_engine(f"sqlite:///{db_path}", isolation_level="AUTOCOMMIT", future=True) - - async def start_run(protocol_obj, args, db, targets): nxc_logger.debug("Creating ThreadPoolExecutor") if args.no_progress or len(targets) == 1: diff --git a/nxc/nxcdb.py b/nxc/nxcdb.py index 27ce0c7f..db89cd58 100644 --- a/nxc/nxcdb.py +++ b/nxc/nxcdb.py @@ -484,7 +484,7 @@ class NXCDBMenu(cmd.Cmd): if subcommand == "create": new_workspace = line.split()[1].strip() print(f"[*] Creating workspace '{new_workspace}'") - create_workspace(new_workspace, self.p_loader, self.protocols) + create_workspace(new_workspace, self.p_loader) self.do_workspace(new_workspace) elif subcommand == "list": print("[*] Enumerating Workspaces") diff --git a/nxc/paths.py b/nxc/paths.py index 5b16c191..5ebed0e8 100644 --- a/nxc/paths.py +++ b/nxc/paths.py @@ -8,7 +8,7 @@ if os.name == "nt": TMP_PATH = os.getenv("LOCALAPPDATA") + "\\Temp\\nxc_hosted" if hasattr(sys, "getandroidapilevel"): TMP_PATH = os.path.join("/data", "data", "com.termux", "files", "usr", "tmp", "nxc_hosted") -WS_PATH = os.path.join(NXC_PATH, "workspaces") + CERT_PATH = os.path.join(NXC_PATH, "nxc.pem") CONFIG_PATH = os.path.join(NXC_PATH, "nxc.conf") WORKSPACE_DIR = os.path.join(NXC_PATH, "workspaces") From d0c996fc056ff81157857539e65b848eb9866116 Mon Sep 17 00:00:00 2001 From: Marshall Hallenbeck Date: Fri, 17 Nov 2023 22:51:43 -0500 Subject: [PATCH 03/14] feat(nxcdb): add functionality to create and set workspaces without entering interactive console --- nxc/database.py | 14 ++++++++++++-- nxc/nxcdb.py | 28 ++++++++++++++++++++++++++-- 2 files changed, 38 insertions(+), 4 deletions(-) diff --git a/nxc/database.py b/nxc/database.py index 45ddc241..bcae9cec 100644 --- a/nxc/database.py +++ b/nxc/database.py @@ -30,6 +30,13 @@ def get_workspace(config): return config.get("nxc", "workspace") +def set_workspace(config_path, workspace_name): + config = open_config(config_path) + config.set("nxc", "workspace", workspace_name) + write_configfile(config, config_path) + print(f"[*] Workspace set to {workspace_name}") + + def get_db(config): return config.get("nxc", "last_used_db") @@ -51,8 +58,10 @@ def create_workspace(workspace_name, p_loader=None): ------- None """ - if not exists(path_join(WORKSPACE_DIR, workspace_name)): - nxc_logger.debug(f"Creating {workspace_name} workspace") + if exists(path_join(WORKSPACE_DIR, workspace_name)): + print(f"[-] Workspace {workspace_name} already exists") + else: + print(f"[*] Creating {workspace_name} workspace") mkdir(path_join(WORKSPACE_DIR, workspace_name)) if p_loader is None: @@ -81,6 +90,7 @@ def create_workspace(workspace_name, p_loader=None): def delete_workspace(workspace_name): shutil.rmtree(path_join(WORKSPACE_DIR, workspace_name)) + print(f"[*] Workspace {workspace_name} deleted") def initialize_db(): diff --git a/nxc/nxcdb.py b/nxc/nxcdb.py index db89cd58..5527e7c6 100644 --- a/nxc/nxcdb.py +++ b/nxc/nxcdb.py @@ -2,6 +2,7 @@ import cmd import csv import sys import os +import argparse from os import listdir from os.path import exists from os.path import join as path_join @@ -11,7 +12,7 @@ from terminaltables import AsciiTable from nxc.loaders.protocolloader import ProtocolLoader from nxc.paths import CONFIG_PATH, WORKSPACE_DIR -from nxc.database import create_db_engine, open_config, get_workspace, get_db, write_configfile, create_workspace +from nxc.database import create_db_engine, open_config, get_workspace, get_db, write_configfile, create_workspace, set_workspace class UserExitedProto(Exception): @@ -516,12 +517,35 @@ class NXCDBMenu(cmd.Cmd): Exits """ print_help(help_string) - + def main(): if not exists(CONFIG_PATH): print("[-] Unable to find config file") sys.exit(1) + + parser = argparse.ArgumentParser( + description="NXCDB is a database navigator for NXC", + ) + parser.add_argument( + "-cw", + "--create-workspace", + help="create a new workspace", + ) + parser.add_argument( + "-ws", + "--set-workspace", + help="set the current workspace", + ) + args = parser.parse_args() + + if args.create_workspace: + create_workspace(args.create_workspace) + sys.exit() + if args.set_workspace: + set_workspace(CONFIG_PATH, args.set_workspace) + sys.exit() + try: nxcdbnav = NXCDBMenu(CONFIG_PATH) nxcdbnav.cmdloop() From e02fabc6b4a11a39351c6ef6fb0c4d2a2a85f903 Mon Sep 17 00:00:00 2001 From: Marshall Hallenbeck Date: Fri, 17 Nov 2023 22:56:32 -0500 Subject: [PATCH 04/14] ruff: remove unused import --- nxc/database.py | 1 - 1 file changed, 1 deletion(-) diff --git a/nxc/database.py b/nxc/database.py index bcae9cec..f3d5509a 100644 --- a/nxc/database.py +++ b/nxc/database.py @@ -9,7 +9,6 @@ from os.path import join as path_join from nxc.loaders.protocolloader import ProtocolLoader from nxc.paths import WORKSPACE_DIR -from nxc.logger import nxc_logger def create_db_engine(db_path): From f08b58de8a3bb4d954fbe212107facbbdc1d5c16 Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Fri, 24 Nov 2023 19:42:24 -0500 Subject: [PATCH 05/14] Add cli option to get current workspace --- nxc/nxcdb.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/nxc/nxcdb.py b/nxc/nxcdb.py index 5527e7c6..08daa16d 100644 --- a/nxc/nxcdb.py +++ b/nxc/nxcdb.py @@ -527,6 +527,12 @@ def main(): parser = argparse.ArgumentParser( description="NXCDB is a database navigator for NXC", ) + parser.add_argument( + "-gw", + "--get-workspace", + action="store_true", + help="get the current workspace", + ) parser.add_argument( "-cw", "--create-workspace", @@ -545,6 +551,10 @@ def main(): if args.set_workspace: set_workspace(CONFIG_PATH, args.set_workspace) sys.exit() + if args.get_workspace: + config = open_config(CONFIG_PATH) + print(f"Current workspace: {get_workspace(config)}") + sys.exit() try: nxcdbnav = NXCDBMenu(CONFIG_PATH) From 7c3e9a22da1e09cf15a44aff2f847342a216f202 Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Fri, 24 Nov 2023 19:55:16 -0500 Subject: [PATCH 06/14] Cli command now lists all workspaces and changed workspaces output to match git style --- nxc/nxcdb.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/nxc/nxcdb.py b/nxc/nxcdb.py index 08daa16d..8dafd027 100644 --- a/nxc/nxcdb.py +++ b/nxc/nxcdb.py @@ -9,6 +9,7 @@ from os.path import join as path_join from textwrap import dedent from requests import get, post, ConnectionError from terminaltables import AsciiTable +from termcolor import colored from nxc.loaders.protocolloader import ProtocolLoader from nxc.paths import CONFIG_PATH, WORKSPACE_DIR @@ -491,9 +492,9 @@ class NXCDBMenu(cmd.Cmd): print("[*] Enumerating Workspaces") for workspace in listdir(path_join(WORKSPACE_DIR)): if workspace == self.workspace: - print("==> " + workspace) + print(f" * {colored(workspace, 'green')}") else: - print(workspace) + print(f" {workspace}") elif exists(path_join(WORKSPACE_DIR, line)): self.config.set("nxc", "workspace", line) write_configfile(self.config, self.config_path) @@ -552,8 +553,12 @@ def main(): set_workspace(CONFIG_PATH, args.set_workspace) sys.exit() if args.get_workspace: - config = open_config(CONFIG_PATH) - print(f"Current workspace: {get_workspace(config)}") + current_workspace = get_workspace(open_config(CONFIG_PATH)) + for workspace in listdir(path_join(WORKSPACE_DIR)): + if workspace == current_workspace: + print(f" * {colored(workspace, 'green')}") + else: + print(f" {workspace}") sys.exit() try: From e48a02d7d84195da8c7f2bf96142639a3b393621 Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Fri, 24 Nov 2023 19:58:49 -0500 Subject: [PATCH 07/14] Switch command line short form to match syntax --- nxc/nxcdb.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nxc/nxcdb.py b/nxc/nxcdb.py index 8dafd027..7bf7dcd1 100644 --- a/nxc/nxcdb.py +++ b/nxc/nxcdb.py @@ -540,7 +540,7 @@ def main(): help="create a new workspace", ) parser.add_argument( - "-ws", + "-sw", "--set-workspace", help="set the current workspace", ) From b59246da68f92d6c2c994e56dec3e013d9c08c1e Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Fri, 24 Nov 2023 19:59:58 -0500 Subject: [PATCH 08/14] Replace old WS_PATH variable --- tests/test_smb_database.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_smb_database.py b/tests/test_smb_database.py index a1d7592b..f4da5c09 100644 --- a/tests/test_smb_database.py +++ b/tests/test_smb_database.py @@ -7,13 +7,13 @@ from nxc.nxcdb import delete_workspace, NXCDBMenu from nxc.first_run import first_run_setup from nxc.loaders.protocolloader import ProtocolLoader from nxc.logger import NXCAdapter -from nxc.paths import WS_PATH +from nxc.paths import WORKSPACE_DIR from sqlalchemy.dialects.sqlite import Insert @pytest.fixture(scope="session") def db_engine(): - db_path = os.path.join(WS_PATH, "test/smb.db") + db_path = os.path.join(WORKSPACE_DIR, "test/smb.db") db_engine = create_engine(f"sqlite:///{db_path}", isolation_level="AUTOCOMMIT", future=True) yield db_engine db_engine.dispose() From 275bc4147755c29ac18d4915bcff230905545364 Mon Sep 17 00:00:00 2001 From: Marshall Hallenbeck Date: Mon, 22 Jan 2024 16:52:18 -0500 Subject: [PATCH 09/14] fix(database): only attempt to initialize default workspace if it doesnt exist --- nxc/database.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/nxc/database.py b/nxc/database.py index f3d5509a..3f93d7db 100644 --- a/nxc/database.py +++ b/nxc/database.py @@ -93,4 +93,5 @@ def delete_workspace(workspace_name): def initialize_db(): - create_workspace("default") \ No newline at end of file + if not exists(path_join(WORKSPACE_DIR, "default")): + create_workspace("default") \ No newline at end of file From 0442376287e23fa05f2766bae9a8ffe8fd4d2a71 Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Fri, 9 Feb 2024 14:29:39 +0100 Subject: [PATCH 10/14] Add error handling for protocol level --- nxc/netexec.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/nxc/netexec.py b/nxc/netexec.py index e09e249e..ce1a0140 100755 --- a/nxc/netexec.py +++ b/nxc/netexec.py @@ -44,11 +44,12 @@ def create_db_engine(db_path): async def start_run(protocol_obj, args, db, targets): + futures = [] nxc_logger.debug("Creating ThreadPoolExecutor") if args.no_progress or len(targets) == 1: with ThreadPoolExecutor(max_workers=args.threads + 1) as executor: nxc_logger.debug(f"Creating thread for {protocol_obj}") - _ = [executor.submit(protocol_obj, args, db, target) for target in targets] + futures = [executor.submit(protocol_obj, args, db, target) for target in targets] else: with Progress(console=nxc_console) as progress, ThreadPoolExecutor(max_workers=args.threads + 1) as executor: current = 0 @@ -62,6 +63,11 @@ async def start_run(protocol_obj, args, db, targets): for _ in as_completed(futures): current += 1 progress.update(tasks, completed=current) + for future in as_completed(futures): + try: + future.result() + except Exception: + nxc_logger.exception(f"Exception for target {targets[futures.index(future)]}: {future.exception()}") def main(): From 3d9cbca7a879df30d2204922408005436767b6cd Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Wed, 14 Feb 2024 15:06:41 +0100 Subject: [PATCH 11/14] Allow a whole word as audit mode character --- nxc/config.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nxc/config.py b/nxc/config.py index ce9c688f..07f2e55b 100644 --- a/nxc/config.py +++ b/nxc/config.py @@ -43,5 +43,5 @@ if len(host_info_colors) != 4: # this should probably be put somewhere else, but if it's in the config helpers, there is a circular import def process_secret(text): - hidden = text[:reveal_chars_of_pwd] - return text if not audit_mode else hidden + audit_mode * 8 + reveal = text[:reveal_chars_of_pwd] + return text if not audit_mode else reveal + (audit_mode if len(audit_mode) > 1 else audit_mode * 8) From 7d99d519e543e9930fc685f1fe54da957b626d8b Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Sun, 18 Feb 2024 01:04:37 +0100 Subject: [PATCH 12/14] Write without delete will now be displayed as write access --- nxc/protocols/smb.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/nxc/protocols/smb.py b/nxc/protocols/smb.py index 386468ec..bb0629ea 100755 --- a/nxc/protocols/smb.py +++ b/nxc/protocols/smb.py @@ -741,12 +741,18 @@ class smb(connection): if not self.args.no_write_check: try: self.conn.createDirectory(share_name, temp_dir) - self.conn.deleteDirectory(share_name, temp_dir) write = True share_info["access"].append("WRITE") except SessionError as e: error = get_error_string(e) - self.logger.debug(f"Error checking WRITE access on share: {error}") + self.logger.debug(f"Error checking WRITE access on share {share_name}: {error}") + + if write: + try: + self.conn.deleteDirectory(share_name, temp_dir) + except SessionError as e: + error = get_error_string(e) + self.logger.debug(f"Error DELETING created temp dir {temp_dir} on share {share_name}: {error}") permissions.append(share_info) From c16aa4cd9f0c56b53cc34cb6f1e7800f2913ab4e Mon Sep 17 00:00:00 2001 From: Alex <61382599+NeffIsBack@users.noreply.github.com> Date: Sun, 18 Feb 2024 11:48:28 +0100 Subject: [PATCH 13/14] Add share name in debug info for write check --- nxc/protocols/smb.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nxc/protocols/smb.py b/nxc/protocols/smb.py index bb0629ea..b169f2b9 100755 --- a/nxc/protocols/smb.py +++ b/nxc/protocols/smb.py @@ -736,7 +736,7 @@ class smb(connection): share_info["access"].append("READ") except SessionError as e: error = get_error_string(e) - self.logger.debug(f"Error checking READ access on share: {error}") + self.logger.debug(f"Error checking READ access on share {share_name}: {error}") if not self.args.no_write_check: try: From 16c02372457ff87bb1b11ab1339984930ffe7368 Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Wed, 21 Feb 2024 19:11:09 +0100 Subject: [PATCH 14/14] Impacket already checks if remote_ops is running, we don't need it in here Besides that, it intruduces a bug where nxc think rrp is enabled, but we closed it before without setting the self.remote_ops variable to None. This leads to sometimes crashing in lsa/sam dump if the service wasnt started originally. --- nxc/protocols/smb.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/nxc/protocols/smb.py b/nxc/protocols/smb.py index 386468ec..76235f22 100755 --- a/nxc/protocols/smb.py +++ b/nxc/protocols/smb.py @@ -1258,12 +1258,11 @@ class smb(connection): os.remove(download_path) def enable_remoteops(self): - if self.remote_ops is not None and self.bootkey is not None: - return try: self.remote_ops = RemoteOperations(self.conn, self.kerberos, self.kdcHost) self.remote_ops.enableRegistry() - self.bootkey = self.remote_ops.getBootKey() + if self.bootkey is None: + self.bootkey = self.remote_ops.getBootKey() except Exception as e: self.logger.fail(f"RemoteOperations failed: {e}")