diff --git a/nxc/logger.py b/nxc/logger.py index caa05e5d..acacfcd4 100755 --- a/nxc/logger.py +++ b/nxc/logger.py @@ -5,6 +5,7 @@ import os.path import sys import re from nxc.console import nxc_console +from nxc.paths import NXC_PATH from termcolor import colored from datetime import datetime from rich.text import Text @@ -30,7 +31,7 @@ def setup_debug_logging(): root_logger.setLevel(logging.INFO) elif debug_args.debug: nxc_logger.logger.setLevel(logging.DEBUG) - root_logger.setLevel(logging.INFO) + root_logger.setLevel(logging.DEBUG) else: nxc_logger.logger.setLevel(logging.ERROR) root_logger.setLevel(logging.ERROR) @@ -163,15 +164,16 @@ class NXCAdapter(logging.LoggerAdapter): If debug or info logging is not enabled, we still want display/success/fail logged to the file specified, so we create a custom LogRecord and pass it to all the additional handlers (which will be all the file handlers) """ - if self.logger.getEffectiveLevel() >= logging.INFO and len(self.logger.handlers): # will be 0 if it's just the console output, so only do this if we actually have file loggers + caller_frame = inspect.currentframe().f_back.f_back.f_back + if len(self.logger.handlers): # will be 0 if it's just the console output, so only do this if we actually have file loggers try: for handler in self.logger.handlers: - handler.handle(LogRecord("nxc", 20, "", kwargs, msg=text, args=args, exc_info=None)) + handler.handle(LogRecord("nxc", 20, pathname=caller_frame.f_code.co_filename, lineno=caller_frame.f_lineno, msg=text, args=args, exc_info=None)) except Exception as e: self.logger.fail(f"Issue while trying to custom print handler: {e}") def add_file_log(self, log_file=None): - file_formatter = TermEscapeCodeFormatter("%(asctime)s - %(levelname)s - %(message)s") + file_formatter = TermEscapeCodeFormatter("%(asctime)s | %(filename)s:%(lineno)s - %(levelname)s - %(message)s", datefmt="%Y-%m-%d %H:%M:%S") output_file = self.init_log_file() if log_file is None else log_file file_creation = False @@ -193,11 +195,10 @@ class NXCAdapter(logging.LoggerAdapter): @staticmethod def init_log_file(): - newpath = os.path.expanduser("~/.nxc") + "/logs/" + datetime.now().strftime("%Y-%m-%d") - if not os.path.exists(newpath): - os.makedirs(newpath) + newpath = NXC_PATH + "/logs/" + datetime.now().strftime("%Y-%m-%d") + os.makedirs(newpath, exist_ok=True) return os.path.join( - os.path.expanduser("~/.nxc"), + NXC_PATH, "logs", datetime.now().strftime("%Y-%m-%d"), f"log_{datetime.now().strftime('%Y-%m-%d-%H-%M-%S')}.log", diff --git a/nxc/modules/hyperv-host.py b/nxc/modules/hyperv-host.py index cde252c5..185eaab4 100644 --- a/nxc/modules/hyperv-host.py +++ b/nxc/modules/hyperv-host.py @@ -22,7 +22,7 @@ class NXCModule: def on_admin_login(self, context, connection): self.context = context - path = "SOFTWARE\Microsoft\Virtual Machine\Guest\Parameters" + path = "SOFTWARE\\Microsoft\\Virtual Machine\\Guest\\Parameters" key = "HostName" try: diff --git a/nxc/modules/maq.py b/nxc/modules/maq.py index 6ea5d44e..00e19628 100644 --- a/nxc/modules/maq.py +++ b/nxc/modules/maq.py @@ -1,3 +1,5 @@ +from pyasn1.error import PyAsn1Error + class NXCModule: """ @@ -21,9 +23,10 @@ class NXCModule: multiple_hosts = False def on_login(self, context, connection): - result = [] context.log.display("Getting the MachineAccountQuota") - searchFilter = "(objectClass=*)" - attributes = ["ms-DS-MachineAccountQuota"] - result = connection.search(searchFilter, attributes) - context.log.highlight("MachineAccountQuota: %d" % result[0]["attributes"][0]["vals"][0]) + result = connection.search("(objectClass=*)", ["ms-DS-MachineAccountQuota"]) + try: + maq = result[0]["attributes"][0]["vals"][0] + context.log.highlight(f"MachineAccountQuota: {maq}") + except PyAsn1Error: + context.log.highlight("MachineAccountQuota: ") diff --git a/nxc/modules/powershell_history.py b/nxc/modules/powershell_history.py new file mode 100644 index 00000000..79de46a5 --- /dev/null +++ b/nxc/modules/powershell_history.py @@ -0,0 +1,73 @@ +import traceback +from os import makedirs +from os.path import join, abspath +from nxc.paths import NXC_PATH + + +class NXCModule: + """Module by @357384n""" + + name = "powershell_history" + description = "Extracts PowerShell history for all users and looks for sensitive commands." + supported_protocols = ["smb"] + opsec_safe = True + multiple_hosts = True + + def options(self, context, module_options): + """To export all the history you can add the following option: -o export=True""" + context.log.info(f"Received module options: {module_options}") + self.export = bool(module_options.get("EXPORT", False)) + context.log.info(f"Option export set to: {self.export}") + + def analyze_history(self, history): + """Analyze PowerShell history for sensitive information.""" + sensitive_keywords = [ + "password", "passwd", "passw", "secret", "credential", "key", + "get-credential", "convertto-securestring", "set-localuser", + "new-localuser", "set-adaccountpassword", "new-object system.net.webclient", + "invoke-webrequest", "invoke-restmethod" + ] + sensitive_commands = [] + for command in history: + command_lower = command.lower() + if any(keyword.lower() in command_lower for keyword in sensitive_keywords): + sensitive_commands.append(command.strip()) + return sensitive_commands + + def on_admin_login(self, context, connection): + """Main function to retrieve and analyze PowerShell history.""" + try: + context.log.info("Retrieving PowerShell history...") + command = 'powershell.exe "type C:\\Users\\*\\AppData\\Roaming\\Microsoft\\Windows\\PowerShell\\PSReadLine\\ConsoleHost_history.txt"' + history = connection.execute(command, True).split("\n") + if history: + sensitive_commands = self.analyze_history(history) + if sensitive_commands: + context.log.highlight("Sensitive commands found in PowerShell history:") + for command in sensitive_commands: + context.log.highlight(f" {command}") + else: + context.log.info("No sensitive commands found in PowerShell history.") + else: + context.log.info("No PowerShell history found.") + + # Check if export is enabled + context.log.info(f"Export option is set to: {self.export}") + if self.export and history: + host = connection.host # Assuming 'host' contains the target IP or hostname + filename = f"{host}_powershell_history.txt" + export_path = join(NXC_PATH, "modules", "powershell_history") + path = abspath(join(export_path, filename)) + makedirs(export_path, exist_ok=True) + + context.log.info(f"Export enabled, writing history to {path}") + try: + with open(path, "w") as file: + for cmd in history: + file.write(cmd + "\n") + context.log.highlight(f"PowerShell history written to: {path}") + except Exception as e: + context.log.fail(f"Failed to write history to {filename}: {e}") + except Exception as e: + context.log.fail(f"UNEXPECTED ERROR: {e}") + context.log.debug(traceback.format_exc())