Merge pull request #649 from d4ytox/Refactoring-NXC_PATH

Refactoring nxc path and adding support for XDG Base Directory - addressing issue #558
This commit is contained in:
Alex
2025-06-10 14:54:32 +02:00
committed by GitHub
23 changed files with 80 additions and 85 deletions
+5 -6
View File
@@ -1,7 +1,6 @@
import os
from os.path import join as path_join
import configparser
from nxc.paths import NXC_PATH, DATA_PATH
from nxc.paths import DATA_PATH, CONFIG_PATH
from nxc.first_run import first_run_setup
from nxc.logger import nxc_logger
from ast import literal_eval
@@ -10,25 +9,25 @@ nxc_default_config = configparser.ConfigParser()
nxc_default_config.read(path_join(DATA_PATH, "nxc.conf"))
nxc_config = configparser.ConfigParser()
nxc_config.read(os.path.join(NXC_PATH, "nxc.conf"))
nxc_config.read(CONFIG_PATH)
if "nxc" not in nxc_config.sections():
first_run_setup()
nxc_config.read(os.path.join(NXC_PATH, "nxc.conf"))
nxc_config.read(CONFIG_PATH)
# Check if there are any missing options in the config file
for section in nxc_default_config.sections():
if not nxc_config.has_section(section):
nxc_logger.display(f"Adding missing section '{section}' to nxc.conf")
nxc_config.add_section(section)
with open(path_join(NXC_PATH, "nxc.conf"), "w") as config_file:
with open(CONFIG_PATH, "w") as config_file:
nxc_config.write(config_file)
for option in nxc_default_config.options(section):
if not nxc_config.has_option(section, option):
nxc_logger.display(f"Adding missing option '{option}' in config section '{section}' to nxc.conf")
nxc_config.set(section, option, nxc_default_config.get(section, option))
with open(path_join(NXC_PATH, "nxc.conf"), "w") as config_file:
with open(CONFIG_PATH, "w") as config_file:
nxc_config.write(config_file)
# THESE OPTIONS HAVE TO EXIST IN THE DEFAULT CONFIG FILE
+4 -2
View File
@@ -1,6 +1,8 @@
import configparser
import os
from nxc.paths import NXC_PATH, CONFIG_PATH
class Context:
def __init__(self, db, logger, args):
@@ -8,10 +10,10 @@ class Context:
setattr(self, key, value)
self.db = db
self.log_folder_path = os.path.join(os.path.expanduser("~/.nxc"), "logs")
self.log_folder_path = os.path.join(NXC_PATH, "logs")
self.localip = None
self.conf = configparser.ConfigParser()
self.conf.read(os.path.expanduser("~/.nxc/nxc.conf"))
self.conf.read(CONFIG_PATH)
self.log = logger
+2 -8
View File
@@ -8,18 +8,16 @@ from nxc.logger import nxc_logger
def first_run_setup(logger=nxc_logger):
if not exists(TMP_PATH):
mkdir(TMP_PATH)
if not exists(NXC_PATH):
logger.display("First time use detected")
logger.display("Creating home directory structure")
mkdir(NXC_PATH)
if not exists(TMP_PATH):
mkdir(TMP_PATH)
folders = (
"logs",
"modules",
"protocols",
"workspaces",
"obfuscated_scripts",
"screenshots",
@@ -46,7 +44,3 @@ def first_run_setup(logger=nxc_logger):
logger.display("Copying default configuration file")
default_path = path_join(DATA_PATH, "nxc.conf")
shutil.copy(default_path, NXC_PATH)
# if not exists(CERT_PATH):
# if os.name != 'nt':
# if e.errno == errno.ENOENT:
+2 -1
View File
@@ -1,9 +1,10 @@
import os
from termcolor import colored
from nxc.paths import NXC_PATH
def write_log(data, log_name):
logs_dir = os.path.join(os.path.expanduser("~/.nxc"), "logs")
logs_dir = os.path.join(NXC_PATH, "logs")
with open(os.path.join(logs_dir, log_name), "w") as log_output:
log_output.write(data)
+17 -24
View File
@@ -2,14 +2,12 @@ from types import ModuleType
from importlib.machinery import SourceFileLoader
from os import listdir
from os.path import join as path_join
from os.path import dirname, exists, expanduser
from os.path import dirname, exists
import nxc
class ProtocolLoader:
def __init__(self):
self.nxc_path = expanduser("~/.nxc")
def load_protocol(self, protocol_path):
loader = SourceFileLoader("protocol", protocol_path)
protocol = ModuleType(loader.name)
@@ -18,27 +16,22 @@ class ProtocolLoader:
def get_protocols(self):
protocols = {}
protocol_paths = [
path_join(dirname(nxc.__file__), "protocols"),
path_join(self.nxc_path, "protocols"),
]
for path in protocol_paths:
for protocol in listdir(path):
if protocol[-3:] == ".py" and protocol[:-3] != "__init__":
protocol_path = path_join(path, protocol)
protocol_name = protocol[:-3]
proto_path = path_join(dirname(nxc.__file__), "protocols")
for protocol in listdir(proto_path):
if protocol[-3:] == ".py" and protocol[:-3] != "__init__":
protocol_path = path_join(proto_path, protocol)
protocol_name = protocol[:-3]
protocols[protocol_name] = {"path": protocol_path}
db_file_path = path_join(path, protocol_name, "database.py")
db_nav_path = path_join(path, protocol_name, "db_navigator.py")
protocol_args_path = path_join(path, protocol_name, "proto_args.py")
if exists(db_file_path):
protocols[protocol_name]["dbpath"] = db_file_path
if exists(db_nav_path):
protocols[protocol_name]["nvpath"] = db_nav_path
if exists(protocol_args_path):
protocols[protocol_name]["argspath"] = protocol_args_path
protocols[protocol_name] = {"path": protocol_path}
db_file_path = path_join(proto_path, protocol_name, "database.py")
db_nav_path = path_join(proto_path, protocol_name, "db_navigator.py")
protocol_args_path = path_join(proto_path, protocol_name, "proto_args.py")
if exists(db_file_path):
protocols[protocol_name]["dbpath"] = db_file_path
if exists(db_nav_path):
protocols[protocol_name]["nvpath"] = db_nav_path
if exists(protocol_args_path):
protocols[protocol_name]["argspath"] = protocol_args_path
return protocols
-1
View File
@@ -6,7 +6,6 @@ from impacket.examples.secretsdump import SAMHashes, LSASecrets, LocalOperations
from impacket.smbconnection import SessionError
from impacket.dcerpc.v5 import transport, rrp
from impacket.dcerpc.v5.rpcrt import RPC_C_AUTHN_GSS_NEGOTIATE
from nxc.paths import NXC_PATH
+3 -2
View File
@@ -1,5 +1,6 @@
from datetime import datetime
from nxc.helpers.logger import write_log
from nxc.paths import NXC_PATH
class NXCModule:
@@ -34,7 +35,7 @@ class NXCModule:
else:
domains = [self.domains]
data = ""
for domain in domains:
output = connection.wmi(
f"Select TextRepresentation FROM MicrosoftDNS_ResourceRecord WHERE DomainName = {domain}",
@@ -64,4 +65,4 @@ class NXCModule:
log_name = f"DNS-Enum-{connection.host}-{datetime.now().strftime('%Y-%m-%d_%H%M%S')}.log"
write_log(data, log_name)
context.log.display(f"Saved raw output to ~/.nxc/logs/{log_name}")
context.log.display(f"Saved raw output to {NXC_PATH}/logs/{log_name}")
+3 -2
View File
@@ -2,7 +2,6 @@
# Credit to https://github.com/dirkjanm/adidnsdump @_dirkjan
# module by @mpgn_x64
import re
from os.path import expanduser
import codecs
import socket
from datetime import datetime
@@ -14,6 +13,8 @@ from impacket.ldap import ldap
from impacket.structure import Structure
from impacket.ldap import ldapasn1 as ldapasn1_impacket
from ldap3 import LEVEL
from os.path import expanduser
from nxc.paths import NXC_PATH
def get_dns_zones(connection, root, debug=False):
@@ -179,7 +180,7 @@ class NXCModule:
)
context.log.highlight(f"Found {len(outdata)} records")
path = expanduser(f"~/.nxc/logs/{connection.domain}_network_{datetime.now().strftime('%Y-%m-%d_%H%M%S')}.log")
path = expanduser(f"{NXC_PATH}/logs/{connection.domain}_network_{datetime.now().strftime('%Y-%m-%d_%H%M%S')}.log")
with codecs.open(path, "w", "utf-8") as outfile:
for row in outdata:
if self.showhosts:
+2 -1
View File
@@ -1,5 +1,6 @@
from datetime import datetime
from nxc.helpers.logger import write_log
from nxc.paths import NXC_PATH
import json
@@ -31,4 +32,4 @@ class NXCModule:
log_name = f"network-connections-{connection.host}-{datetime.now().strftime('%Y-%m-%d_%H%M%S')}.log"
write_log(json.dumps(data), log_name)
context.log.display(f"Saved raw output to ~/.nxc/logs/{log_name}")
context.log.display(f"Saved raw output to {NXC_PATH}/logs/{log_name}")
+2 -1
View File
@@ -6,6 +6,7 @@ from base64 import b64encode
from io import BytesIO, StringIO
from xml.etree import ElementTree as ET
from nxc.helpers.powershell import get_ps_script
from nxc.paths import TMP_PATH
class NXCModule:
@@ -39,7 +40,7 @@ class NXCModule:
self.share = "C$"
self.remote_temp_script_path = "C:\\Windows\\Temp\\temp.ps1"
self.keepass_binary_path = "C:\\Program Files\\KeePass Password Safe 2\\KeePass.exe"
self.local_export_path = "/tmp"
self.local_export_path = TMP_PATH
self.trigger_name = "export_database"
self.poll_frequency_seconds = 5
self.dummy_service_name = "OneDrive Sync KeePass"
+1 -1
View File
@@ -485,7 +485,7 @@ class NXCModule:
EXCLUDE_EXTS Case-insensitive extension filter to exclude (Default: ico,lnk)
EXCLUDE_FILTER Case-insensitive filter to exclude folders/files (Default: print$,ipc$)
MAX_FILE_SIZE Max file size to download (Default: 51200)
OUTPUT_FOLDER Path of the local folder to save files (Default: ~/.nxc/nxc_spider_plus)
OUTPUT_FOLDER Path of the local folder to save files (Default: NXC_PATH/nxc_spider_plus)
"""
self.download_flag = False
if any("DOWNLOAD" in key for key in module_options):
+3 -1
View File
@@ -3,6 +3,8 @@ from datetime import datetime
from impacket.ldap import ldap, ldapasn1
from impacket.ldap.ldap import LDAPSearchError
from nxc.paths import NXC_PATH
class NXCModule:
"""
@@ -91,7 +93,7 @@ class NXCModule:
def create_log_file(self, host, time):
"""Create a log file for dumping user descriptions."""
logfile = f"UserDesc-{host}-{time}.log"
logfile = Path.home().joinpath(".nxc").joinpath("logs").joinpath(logfile)
logfile = Path(NXC_PATH).joinpath(logfile)
self.context.log.info(f"Creating log file '{logfile}'")
self.log_file = open(logfile, "w") # noqa: SIM115
+6 -5
View File
@@ -9,7 +9,7 @@ from nxc.cli import gen_cli_args
from nxc.loaders.protocolloader import ProtocolLoader
from nxc.loaders.moduleloader import ModuleLoader
from nxc.first_run import first_run_setup
from nxc.paths import NXC_PATH
from nxc.paths import CONFIG_PATH, NXC_PATH, WORKSPACE_DIR
from nxc.console import nxc_console
from nxc.logger import nxc_logger
from nxc.config import nxc_config, nxc_workspace, config_log, ignore_opsec
@@ -127,8 +127,9 @@ def main():
# The following is a quick hack for the powershell obfuscation functionality, I know this is yucky
if hasattr(args, "clear_obfscripts") and args.clear_obfscripts:
shutil.rmtree(os.path.expanduser("~/.nxc/obfuscated_scripts/"))
os.mkdir(os.path.expanduser("~/.nxc/obfuscated_scripts/"))
obfuscated_dir = os.path.join(NXC_PATH, "obfuscated_scripts")
shutil.rmtree(obfuscated_dir)
os.mkdir(obfuscated_dir)
nxc_logger.success("Cleared cached obfuscated PowerShell scripts")
if hasattr(args, "obfs") and args.obfs:
@@ -146,7 +147,7 @@ def main():
protocol_db_object = p_loader.load_protocol(protocol_db_path).database
nxc_logger.debug(f"Protocol DB Object: {protocol_db_object}")
db_path = path_join(NXC_PATH, "workspaces", nxc_workspace, f"{args.protocol}.db")
db_path = path_join(WORKSPACE_DIR, nxc_workspace, f"{args.protocol}.db")
nxc_logger.debug(f"DB Path: {db_path}")
db_engine = create_db_engine(db_path)
@@ -194,7 +195,7 @@ def main():
nxc_logger.debug("ignore_opsec is set in the configuration, skipping prompt")
nxc_logger.display("Ignore OPSEC in configuration is set and OPSEC unsafe module loaded")
else:
ans = input(highlight("[!] Module is not opsec safe, are you sure you want to run this? [Y/n] For global configuration, change ignore_opsec value to True on ~/nxc/nxc.conf", "red"))
ans = input(highlight(f"[!] Module is not opsec safe, are you sure you want to run this? [Y/n] For global configuration, change ignore_opsec value to True on {CONFIG_PATH}", "red"))
if ans.lower() not in ["y", "yes", ""]:
exit(1)
+9 -12
View File
@@ -1,16 +1,13 @@
import os
import sys
from os.path import join, normpath, expanduser, dirname
from os import environ, getenv
import nxc
NXC_PATH = os.path.expanduser("~/.nxc")
if os.name == "nt":
TMP_PATH = os.getenv("LOCALAPPDATA") + "\\Temp\\nxc_hosted"
elif hasattr(sys, "getandroidapilevel"):
TMP_PATH = os.path.join("/data", "data", "com.termux", "files", "usr", "tmp", "nxc_hosted")
if "NXC_PATH" in environ: # noqa: SIM108
NXC_PATH = normpath(getenv("NXC_PATH"))
else:
TMP_PATH = os.path.join("/tmp", "nxc_hosted")
NXC_PATH = normpath(expanduser("~/.nxc"))
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")
DATA_PATH = os.path.join(os.path.dirname(nxc.__file__), "data")
TMP_PATH = join(NXC_PATH, "tmp")
CONFIG_PATH = join(NXC_PATH, "nxc.conf")
WORKSPACE_DIR = join(NXC_PATH, "workspaces")
DATA_PATH = join(dirname(nxc.__file__), "data")
+3 -2
View File
@@ -44,6 +44,7 @@ from nxc.protocols.ldap.kerberos import KerberosAttacks
from nxc.parsers.ldap_results import parse_result_attributes
from nxc.helpers.ntlm_parser import parse_challenge
from nxc.helpers.misc import get_bloodhound_info
from nxc.paths import CONFIG_PATH, NXC_PATH
ldap_error_status = {
"1": "STATUS_NOT_SUPPORTED",
@@ -319,7 +320,7 @@ class ldap(connection):
self.kdcHost = result["host"] if result else None
self.logger.info(f"Resolved domain: {self.domain} with dns, kdcHost: {self.kdcHost}")
self.output_filename = os.path.expanduser(f"~/.nxc/logs/{self.hostname}_{self.host}".replace(":", "-"))
self.output_filename = os.path.expanduser(f"{NXC_PATH}/logs/{self.hostname}_{self.host}".replace(":", "-"))
try:
self.db.add_host(
@@ -1354,7 +1355,7 @@ class ldap(connection):
if use_bhce and not is_ce:
self.logger.fail("⚠️ Configuration Issue Detected ⚠️")
self.logger.fail("Your configuration has BloodHound-CE enabled, but the regular BloodHound package is installed. Modify your ~/.nxc/nxc.conf config file or follow the instructions:")
self.logger.fail(f"Your configuration has BloodHound-CE enabled, but the regular BloodHound package is installed. Modify your {CONFIG_PATH} config file or follow the instructions:")
self.logger.fail("Please run the following commands to fix this:")
self.logger.fail("poetry remove bloodhound-ce # poetry falsely recognizes bloodhound-ce as a the old bloodhound package")
self.logger.fail("poetry add bloodhound-ce")
+4 -6
View File
@@ -9,8 +9,8 @@ from impacket.krb5.ccache import CCache
from nxc.connection import connection
from nxc.helpers.bloodhound import add_user_bh
from nxc.logger import NXCAdapter
from nxc.config import host_info_colors
from nxc.config import process_secret
from nxc.config import host_info_colors, process_secret
from nxc.paths import NXC_PATH
from aardwolf.connection import RDPConnection
from aardwolf.commons.queuedata.constants import VIDEO_FORMAT
@@ -22,8 +22,6 @@ from asyauth.common.credentials.kerberos import KerberosCredential
from asyauth.common.constants import asyauthSecret
from asysocks.unicomm.common.target import UniTarget, UniProto
from nxc.paths import NXC_PATH
class rdp(connection):
def __init__(self, args, db, host):
@@ -141,7 +139,7 @@ class rdp(connection):
self.hostname = info_domain["computername"]
self.server_os = info_domain["os_guess"] + " Build " + str(info_domain["os_build"])
self.logger.extra["hostname"] = self.hostname
self.output_filename = os.path.expanduser(f"~/.nxc/logs/{self.hostname}_{self.host}_{datetime.now().strftime('%Y-%m-%d_%H%M%S')}".replace(":", "-"))
self.output_filename = os.path.expanduser(f"{NXC_PATH}/logs/{self.hostname}_{self.host}_{datetime.now().strftime('%Y-%m-%d_%H%M%S')}".replace(":", "-"))
break
if self.args.domain:
@@ -368,7 +366,7 @@ class rdp(connection):
await asyncio.sleep(5)
if self.conn is not None and self.conn.desktop_buffer_has_data is True:
buffer = self.conn.get_desktop_buffer(VIDEO_FORMAT.PIL)
filename = os.path.expanduser(f"~/.nxc/screenshots/{self.hostname}_{self.host}_{datetime.now().strftime('%Y-%m-%d_%H%M%S')}.png")
filename = os.path.expanduser(f"{NXC_PATH}/screenshots/{self.hostname}_{self.host}_{datetime.now().strftime('%Y-%m-%d_%H%M%S')}.png")
buffer.save(filename, "png")
self.logger.highlight(f"Screenshot saved {filename}")
+2 -1
View File
@@ -3,6 +3,7 @@ from impacket.dcerpc.v5 import tsch, transport
from impacket.dcerpc.v5.dtypes import NULL
from impacket.dcerpc.v5.rpcrt import RPC_C_AUTHN_GSS_NEGOTIATE, RPC_C_AUTHN_LEVEL_PKT_PRIVACY
from nxc.helpers.misc import gen_random_string
from nxc.paths import TMP_PATH
from time import sleep
from datetime import datetime, timedelta
@@ -167,7 +168,7 @@ class TSCH_EXEC:
if fileless:
while True:
try:
with open(os.path.join("/tmp", "nxc_hosted", self.__output_filename)) as output:
with open(os.path.join(TMP_PATH, self.__output_filename)) as output:
self.output_callback(output.read())
break
except OSError:
+2 -1
View File
@@ -29,6 +29,7 @@ from os.path import join as path_join
from time import sleep
from nxc.connection import dcom_FirewallChecker
from nxc.helpers.misc import gen_random_string
from nxc.paths import TMP_PATH
from impacket.dcerpc.v5.dcom.oaut import (
IID_IDispatch,
@@ -238,7 +239,7 @@ class MMCEXEC:
while True:
try:
with open(path_join("/tmp", "nxc_hosted", self.__output)) as output:
with open(path_join(TMP_PATH, self.__output)) as output:
self.output_callback(output.read())
break
except OSError:
+2 -1
View File
@@ -3,6 +3,7 @@ import os
from time import sleep
from nxc.connection import dcom_FirewallChecker
from nxc.helpers.misc import gen_random_string
from nxc.paths import TMP_PATH
from impacket.dcerpc.v5.dcomrt import DCOMConnection
from impacket.dcerpc.v5.dcom import wmi
from impacket.dcerpc.v5.dtypes import NULL
@@ -129,7 +130,7 @@ class WMIEXEC:
def get_output_fileless(self):
while True:
try:
with open(os.path.join("/tmp", "nxc_hosted", self.__output)) as output:
with open(os.path.join(TMP_PATH, self.__output)) as output:
self.output_callback(output.read())
break
except OSError:
+2 -3
View File
@@ -1,5 +1,4 @@
import configparser
import os
import sys
from sqlalchemy import Table, select, func, delete
@@ -11,11 +10,11 @@ from sqlalchemy.exc import (
from nxc.database import BaseDB, format_host_query
from nxc.logger import nxc_logger
from nxc.paths import NXC_PATH
from nxc.paths import CONFIG_PATH
# we can't import config.py due to a circular dependency, so we have to create redundant code unfortunately
nxc_config = configparser.ConfigParser()
nxc_config.read(os.path.join(NXC_PATH, "nxc.conf"))
nxc_config.read(CONFIG_PATH)
nxc_workspace = nxc_config.get("nxc", "workspace", fallback="default")
+2 -1
View File
@@ -7,6 +7,7 @@ from aardwolf.commons.target import RDPTarget
from nxc.connection import connection
from nxc.helpers.logger import highlight
from nxc.logger import NXCAdapter
from nxc.paths import NXC_PATH
from aardwolf.vncconnection import VNCConnection
from aardwolf.commons.iosettings import RDPIOSettings
from aardwolf.commons.queuedata.constants import VIDEO_FORMAT
@@ -112,7 +113,7 @@ class vnc(connection):
await asyncio.sleep(int(self.args.screentime))
if self.conn is not None and self.conn.desktop_buffer_has_data is True:
buffer = self.conn.get_desktop_buffer(VIDEO_FORMAT.PIL)
filename = os.path.expanduser(f"~/.nxc/screenshots/{self.hostname}_{self.host}_{datetime.now().strftime('%Y-%m-%d_%H%M%S')}.png")
filename = os.path.expanduser(f"{NXC_PATH}/screenshots/{self.hostname}_{self.host}_{datetime.now().strftime('%Y-%m-%d_%H%M%S')}.png")
buffer.save(filename, "png")
self.logger.highlight(f"Screenshot saved {filename}")
+2 -2
View File
@@ -19,7 +19,7 @@ from nxc.helpers.bloodhound import add_user_bh
from nxc.helpers.misc import gen_random_string
from nxc.helpers.ntlm_parser import parse_challenge
from nxc.logger import NXCAdapter
from nxc.paths import NXC_PATH
urllib3.disable_warnings()
@@ -75,7 +75,7 @@ class winrm(connection):
if self.args.local_auth:
self.domain = self.hostname
self.output_filename = os.path.expanduser(f"~/.nxc/logs/{self.hostname}_{self.host}_{datetime.now().strftime('%Y-%m-%d_%H%M%S')}".replace(":", "-"))
self.output_filename = os.path.expanduser(f"{NXC_PATH}/logs/{self.hostname}_{self.host}_{datetime.now().strftime('%Y-%m-%d_%H%M%S')}".replace(":", "-"))
def print_host_info(self):
self.logger.extra["protocol"] = "WINRM-SSL" if self.ssl else "WINRM"
+2 -1
View File
@@ -8,6 +8,7 @@ from nxc.config import process_secret
from nxc.connection import connection, dcom_FirewallChecker, requires_admin
from nxc.logger import NXCAdapter
from nxc.protocols.wmi import wmiexec, wmiexec_event
from nxc.paths import NXC_PATH
from impacket import ntlm
from impacket.uuid import uuidtup_to_bin
@@ -140,7 +141,7 @@ class wmi(connection):
self.kdcHost = result["host"] if result else None
self.logger.info(f"Resolved domain: {self.domain} with dns, kdcHost: {self.kdcHost}")
self.output_filename = os.path.expanduser(f"~/.nxc/logs/{self.hostname}_{self.host}_{datetime.now().strftime('%Y-%m-%d_%H%M%S')}".replace(":", "-"))
self.output_filename = os.path.expanduser(f"{NXC_PATH}/logs/{self.hostname}_{self.host}_{datetime.now().strftime('%Y-%m-%d_%H%M%S')}".replace(":", "-"))
def print_host_info(self):
self.logger.extra["protocol"] = "RPC"