From 3deabf5787764a02dfbdbf02a7086cea88d2ebf9 Mon Sep 17 00:00:00 2001 From: Sant0rryu <111064983+Sant0rryu@users.noreply.github.com> Date: Tue, 7 May 2024 17:17:14 +0200 Subject: [PATCH 01/17] Adding module to retrieve network interfaces info Signed-off-by: Sant0rryu <111064983+Sant0rryu@users.noreply.github.com> --- nxc/modules/interface.py | 83 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 nxc/modules/interface.py diff --git a/nxc/modules/interface.py b/nxc/modules/interface.py new file mode 100644 index 00000000..95dff2f3 --- /dev/null +++ b/nxc/modules/interface.py @@ -0,0 +1,83 @@ +#!/usr/bin/env python3 + +from impacket.dcerpc.v5 import rrp +from impacket.examples.secretsdump import RemoteOperations +from impacket.dcerpc.v5.rpcrt import DCERPCException + +class NXCModule: + ''' + Retrieve the list of network interfaces info (Name, IP Address, Subnet Mask, Default Gateway) from remote Windows registry' + Module by Sant0rryu : @Sant0rryu + ''' + name = 'interface' + description = 'Retrieve the list of network interfaces info (Name, IP Address, Subnet Mask, Default Gateway) from remote Windows registry' + supported_protocols = ['smb'] + opsec_safe = True + multiple_hosts = True + + def options(self, context, module_options): + pass + + def on_admin_login(self, context, connection): + self.output = "Name: {} | IP Address: {} | SubnetMask: {} | Gateway: {}" + try: + remoteOps = RemoteOperations(connection.conn, False) + remoteOps.enableRegistry() + + if remoteOps._RemoteOperations__rrp: + ans = rrp.hOpenLocalMachine(remoteOps._RemoteOperations__rrp) + regHandle = ans['phKey'] + + ans = rrp.hBaseRegOpenKey(remoteOps._RemoteOperations__rrp, regHandle, 'SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters\\Interfaces') + keyHandle = ans['phkResult'] + + interface = {} + subKeys = [] + i = 0 + while True: + try: + key = rrp.hBaseRegEnumKey(remoteOps._RemoteOperations__rrp, keyHandle, i) + subKeys.append(key['lpNameOut'][:-1]) + i += 1 + except Exception: + break + + for subKey in subKeys: + try: + interfaceKey = 'SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters\\Interfaces\\{}'.format(subKey) + ans = rrp.hBaseRegOpenKey(remoteOps._RemoteOperations__rrp, regHandle, interfaceKey) + interfaceHandle = ans['phkResult'] + + #Retrieve IPAddress + ip_address = rrp.hBaseRegQueryValue(remoteOps._RemoteOperations__rrp, interfaceHandle, 'IPAddress') + interface[subKey] = {'IPAddress' : str(ip_address[1])} + + #Retrieve SubnetMask + subnetmask = rrp.hBaseRegQueryValue(remoteOps._RemoteOperations__rrp, interfaceHandle, 'SubnetMask') + interface[subKey]['SubnetMask'] = str(subnetmask[1]) + + + #Retrieve DefaultGateway + defaultgateway = rrp.hBaseRegQueryValue(remoteOps._RemoteOperations__rrp, interfaceHandle, 'DefaultGateway') + interface[subKey]['DefaultGateway'] = str(defaultgateway[1]) + + #Retrieve Interace Name + interfaceNameKey = 'SYSTEM\\ControlSet001\\Control\\Network\\' + '{4D36E972-E325-11CE-BFC1-08002BE10318}' + '\\{}\\Connection'.format(subKey) + ans = rrp.hBaseRegOpenKey(remoteOps._RemoteOperations__rrp, regHandle, interfaceNameKey) + interfaceNameHandle = ans['phkResult'] + name = rrp.hBaseRegQueryValue(remoteOps._RemoteOperations__rrp, interfaceNameHandle, 'Name') + interface[subKey]['Name'] = str(name[1]) + + + context.log.highlight(self.output.format(interface[subKey]['Name'], interface[subKey]['IPAddress'], interface[subKey]['SubnetMask'], interface[subKey]['DefaultGateway'])) + + except DCERPCException as e: + continue + + try: + remoteOps.finish() + except: + pass + + except DCERPCException as e: + context.log.error(f"Failed to connect to the target: {str(e)}") From 169a7dc1483fb4edb1bb22647c398bac3ff6ecde Mon Sep 17 00:00:00 2001 From: Sant0rryu <111064983+Sant0rryu@users.noreply.github.com> Date: Sun, 12 May 2024 16:30:25 +0200 Subject: [PATCH 02/17] Update interface.py Fixed Ruff checks Signed-off-by: Sant0rryu <111064983+Sant0rryu@users.noreply.github.com> --- nxc/modules/interface.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nxc/modules/interface.py b/nxc/modules/interface.py index 95dff2f3..567ea063 100644 --- a/nxc/modules/interface.py +++ b/nxc/modules/interface.py @@ -71,12 +71,12 @@ class NXCModule: context.log.highlight(self.output.format(interface[subKey]['Name'], interface[subKey]['IPAddress'], interface[subKey]['SubnetMask'], interface[subKey]['DefaultGateway'])) - except DCERPCException as e: + except DCERPCException: continue try: remoteOps.finish() - except: + except Exception: pass except DCERPCException as e: From 219deaf07344e95307e91327aa78bf182958b786 Mon Sep 17 00:00:00 2001 From: termanix <50464194+termanix@users.noreply.github.com> Date: Wed, 1 May 2024 17:11:47 +0300 Subject: [PATCH 03/17] BitLocker --- nxc/modules/bitlocker.py | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 nxc/modules/bitlocker.py diff --git a/nxc/modules/bitlocker.py b/nxc/modules/bitlocker.py new file mode 100644 index 00000000..b8b506f4 --- /dev/null +++ b/nxc/modules/bitlocker.py @@ -0,0 +1,40 @@ +import re + +class NXCModule: + + name = "bitlocker" + description = "Enumerating BitLocker Status on target(s) If it is enabled or disabled." + supported_protocols = ["smb"] + opsec_safe = True # only running commands are executed on the remote host for check + multiple_hosts = True + + def options(self, context, module_options): + """ """ + + def on_admin_login(self, context, connection): + + # PowerShell command to check BitLocker volumes status. + check_bitlocker_command_str = 'powershell.exe "Get-BitLockerVolume | Select-Object MountPoint, ProtectionStatus"' + + try: + # Executing the PowerShell command to get BitLocker volumes status. + check_bitlocker_command_str_output = connection.execute(check_bitlocker_command_str, True) + # Splitting the output into lines. + lines = check_bitlocker_command_str_output.strip().split("\n") + + # Getting data lines. + data_lines = lines[2:] + + # Analyzing data lines. + for line in data_lines: + parts = re.split(r"\s{2,}", line.strip()) # Stripping spaces and splitting the line. + MountPoint = parts[0] # Getting the mount point of the drive. + protection_status = parts[1] # Getting the protection status. + + # Checking if BitLocker is enabled. + if protection_status == "On": + context.log.success(f"BitLocker is enabled on {MountPoint} drive!") + else: + context.log.highlight(f"BitLocker is disabled on {MountPoint} drive!") + except Exception as e: + context.log.exception(f"Exception occurred: {e}") From f55cbf6f23d8e18218841beb3e5a0caee1d47a6d Mon Sep 17 00:00:00 2001 From: termanix <50464194+termanix@users.noreply.github.com> Date: Wed, 1 May 2024 22:24:41 +0300 Subject: [PATCH 04/17] Update bitlocker.py Using WMI command Signed-off-by: termanix <50464194+termanix@users.noreply.github.com> --- nxc/modules/bitlocker.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/nxc/modules/bitlocker.py b/nxc/modules/bitlocker.py index b8b506f4..4ce7ddbd 100644 --- a/nxc/modules/bitlocker.py +++ b/nxc/modules/bitlocker.py @@ -14,7 +14,7 @@ class NXCModule: def on_admin_login(self, context, connection): # PowerShell command to check BitLocker volumes status. - check_bitlocker_command_str = 'powershell.exe "Get-BitLockerVolume | Select-Object MountPoint, ProtectionStatus"' + check_bitlocker_command_str = 'powershell.exe "Get-CimInstance -Namespace "root/CIMV2/Security/MicrosoftVolumeEncryption" -ClassName "Win32_EncryptableVolume" | Select-Object DriveLetter, ProtectionStatus"' try: # Executing the PowerShell command to get BitLocker volumes status. @@ -28,13 +28,13 @@ class NXCModule: # Analyzing data lines. for line in data_lines: parts = re.split(r"\s{2,}", line.strip()) # Stripping spaces and splitting the line. - MountPoint = parts[0] # Getting the mount point of the drive. + drive_letter = parts[0] # Getting the mount point of the drive. protection_status = parts[1] # Getting the protection status. # Checking if BitLocker is enabled. - if protection_status == "On": - context.log.success(f"BitLocker is enabled on {MountPoint} drive!") + if protection_status == "1": + context.log.success(f"BitLocker is enabled on {drive_letter} drive!") else: - context.log.highlight(f"BitLocker is disabled on {MountPoint} drive!") + context.log.highlight(f"BitLocker is disabled on {drive_letter} drive!") except Exception as e: context.log.exception(f"Exception occurred: {e}") From 50ed8279e14b93b56fd49a8f82fe95f05f7805fc Mon Sep 17 00:00:00 2001 From: termanix <50464194+termanix@users.noreply.github.com> Date: Fri, 3 May 2024 17:32:00 +0300 Subject: [PATCH 05/17] Update bitlocker.py Co-authored-by: Adamkadaban Signed-off-by: termanix <50464194+termanix@users.noreply.github.com> --- nxc/modules/bitlocker.py | 146 ++++++++++++++++++++++++++++++--------- 1 file changed, 115 insertions(+), 31 deletions(-) diff --git a/nxc/modules/bitlocker.py b/nxc/modules/bitlocker.py index 4ce7ddbd..eacaa78f 100644 --- a/nxc/modules/bitlocker.py +++ b/nxc/modules/bitlocker.py @@ -1,40 +1,124 @@ import re +from impacket.dcerpc.v5.dcom import wmi +from impacket.dcerpc.v5.dtypes import NULL +from impacket.dcerpc.v5.dcomrt import DCOMConnection +from impacket.dcerpc.v5.rpcrt import RPC_C_AUTHN_LEVEL_PKT_PRIVACY class NXCModule: + name = "bitlocker" + description = "Enumerating BitLocker Status on target(s) If it is enabled or disabled." + supported_protocols = ["smb", "wmi"] + opsec_safe = True + multiple_hosts = True - name = "bitlocker" - description = "Enumerating BitLocker Status on target(s) If it is enabled or disabled." - supported_protocols = ["smb"] - opsec_safe = True # only running commands are executed on the remote host for check - multiple_hosts = True + def __init__(self, context=None, module_options=None): + self.context = context + self.module_options = module_options - def options(self, context, module_options): - """ """ + def options(self, context, module_options): + """ """ - def on_admin_login(self, context, connection): - - # PowerShell command to check BitLocker volumes status. - check_bitlocker_command_str = 'powershell.exe "Get-CimInstance -Namespace "root/CIMV2/Security/MicrosoftVolumeEncryption" -ClassName "Win32_EncryptableVolume" | Select-Object DriveLetter, ProtectionStatus"' + def on_admin_login(self, context, connection): + if context.protocol == "smb": + bitlocker_smb = BitLockerSMB(context, connection) + bitlocker_smb.check_bitlocker_status() + elif context.protocol == "wmi": + bitlocker_wmi = BitLockerWMI(context, connection) + bitlocker_wmi.check_bitlocker_status() - try: - # Executing the PowerShell command to get BitLocker volumes status. - check_bitlocker_command_str_output = connection.execute(check_bitlocker_command_str, True) - # Splitting the output into lines. - lines = check_bitlocker_command_str_output.strip().split("\n") - # Getting data lines. - data_lines = lines[2:] +class BitLockerSMB: + def __init__(self, context, connection): + self.context = context + self.connection = connection - # Analyzing data lines. - for line in data_lines: - parts = re.split(r"\s{2,}", line.strip()) # Stripping spaces and splitting the line. - drive_letter = parts[0] # Getting the mount point of the drive. - protection_status = parts[1] # Getting the protection status. - - # Checking if BitLocker is enabled. - if protection_status == "1": - context.log.success(f"BitLocker is enabled on {drive_letter} drive!") - else: - context.log.highlight(f"BitLocker is disabled on {drive_letter} drive!") - except Exception as e: - context.log.exception(f"Exception occurred: {e}") + def check_bitlocker_status(self): + # PowerShell command to check BitLocker volumes status. + check_bitlocker_command_str = 'powershell.exe "Get-BitLockerVolume | Select-Object MountPoint, EncryptionMethod, ProtectionStatus"' + + try: + # Executing the PowerShell command to get BitLocker volumes status. + check_bitlocker_command_str_output = self.connection.execute(check_bitlocker_command_str, True) + # Splitting the output into lines. + lines = check_bitlocker_command_str_output.strip().split("\n") + + # Getting data lines. + data_lines = lines[2:] + + # Analyzing data lines. + for line in data_lines: + + parts = re.split(r"\s{2,}", line.strip()) # Stripping spaces and splitting the line. + MountPoint = parts[0] # Getting the mount point of the drive. + EncryptionMethod = parts[1] # Getting the mount point of the drive. + protection_status = parts[2] # Getting the protection status. + + # Checking if BitLocker is enabled. + if protection_status == "On": + self.context.log.highlight(f"BitLocker is enabled on drive {MountPoint} (Encryption Method: {EncryptionMethod})") + else: + self.context.log.highlight(f"BitLocker is disabled on drive {MountPoint}") + except Exception as e: + self.context.log.exception(f"Exception occurred: {e}") + + +class BitLockerWMI: + def __init__(self, context, connection): + self.context = context + self.connection = connection + + def check_bitlocker_status(self): + try: + # Create a DCOM connection + dcom_conn = DCOMConnection( + self.connection.host, + self.connection.username, + self.connection.password, + self.connection.domain, + self.connection.lmhash, + self.connection.nthash, + oxidResolver=True, + doKerberos=self.connection.kerberos, + kdcHost=self.connection.kdcHost, + ) + + try: + + # CoCreateInstanceEx for WMI login + i_interface = dcom_conn.CoCreateInstanceEx(wmi.CLSID_WbemLevel1Login, wmi.IID_IWbemLevel1Login) + iWbemLevel1Login = wmi.IWbemLevel1Login(i_interface) + + # Specify the namespace for BitLocker + bitlockerNamespace = "root\\CIMv2\\Security\\MicrosoftVolumeEncryption" + + # NTLM login for WMI + iWbemServices = iWbemLevel1Login.NTLMLogin(bitlockerNamespace, NULL, NULL) + + # Set authentication level + iWbemServices.get_dce_rpc().set_auth_level(RPC_C_AUTHN_LEVEL_PKT_PRIVACY) + + # Query to get BitLocker status + classQuery = "SELECT DriveLetter, ProtectionStatus, EncryptionMethod FROM Win32_EncryptableVolume" + iEnumWbemClassObject = iWbemServices.ExecQuery(classQuery) + encryptionTypeMapping = {0: "None", 1: "AES_256_WITH_DIFFUSER", 2: "AES_256_WITH_DIFFUSER", 3: "AES_128", 4: "AES_256", 5: "HARDWARE_ENCRYPTION", 6: "XTS_AES_128", 7: "XTS_AES_256"} + + try: + while True: + iWbemClassObject = iEnumWbemClassObject.Next(0xffffffff, 1) + encryptionMethod = int(iWbemClassObject[0].EncryptionMethod) + if iWbemClassObject[0].ProtectionStatus == 1: + self.context.log.highlight(f"BitLocker is enabled on drive {iWbemClassObject[0].DriveLetter} (Encryption Method: {encryptionTypeMapping.get(encryptionMethod, 'Unknown')})") + else: + assert (encryptionMethod == 0) # Should be 0 if disabled + self.context.log.highlight(f"BitLocker is disabled on drive {iWbemClassObject[0].DriveLetter}") + except Exception: + pass # Using pass because if try to log or printing, getting "WMI Session Error: code: 0x1 - WBEM_S_FALSE" + + # Release resources + iWbemLevel1Login.RemRelease() + iWbemServices.RemRelease() + dcom_conn.disconnect() + except Exception as e: + self.context.log.error(f"Error occurred during BitLocker check: {e}") + except Exception as e: + self.context.log.error(f"Error occurred during BitLocker check: {e}") From c354978fa133f49f0f54e8b875beca1d3466c869 Mon Sep 17 00:00:00 2001 From: termanix <50464194+termanix@users.noreply.github.com> Date: Sun, 5 May 2024 17:11:53 +0300 Subject: [PATCH 06/17] Update bitlocker.py Fixed a bug, if bitlockervolume and namespace does not exist. Co-authored-by: Adamkadaban Signed-off-by: termanix <50464194+termanix@users.noreply.github.com> --- nxc/modules/bitlocker.py | 203 ++++++++++++++++++++------------------- 1 file changed, 106 insertions(+), 97 deletions(-) diff --git a/nxc/modules/bitlocker.py b/nxc/modules/bitlocker.py index eacaa78f..cdc9f12a 100644 --- a/nxc/modules/bitlocker.py +++ b/nxc/modules/bitlocker.py @@ -1,124 +1,133 @@ import re +import sys from impacket.dcerpc.v5.dcom import wmi from impacket.dcerpc.v5.dtypes import NULL from impacket.dcerpc.v5.dcomrt import DCOMConnection from impacket.dcerpc.v5.rpcrt import RPC_C_AUTHN_LEVEL_PKT_PRIVACY class NXCModule: - name = "bitlocker" - description = "Enumerating BitLocker Status on target(s) If it is enabled or disabled." - supported_protocols = ["smb", "wmi"] - opsec_safe = True - multiple_hosts = True + name = "bitlocker" + description = "Enumerating BitLocker Status on target(s) If it is enabled or disabled." + supported_protocols = ["smb", "wmi"] + opsec_safe = True + multiple_hosts = True - def __init__(self, context=None, module_options=None): - self.context = context - self.module_options = module_options + def __init__(self, context=None, module_options=None): + self.context = context + self.module_options = module_options - def options(self, context, module_options): - """ """ + def options(self, context, module_options): + """ """ - def on_admin_login(self, context, connection): - if context.protocol == "smb": - bitlocker_smb = BitLockerSMB(context, connection) - bitlocker_smb.check_bitlocker_status() - elif context.protocol == "wmi": - bitlocker_wmi = BitLockerWMI(context, connection) - bitlocker_wmi.check_bitlocker_status() + def on_admin_login(self, context, connection): + if context.protocol == "smb": + bitlocker_smb = BitLockerSMB(context, connection) + bitlocker_smb.check_bitlocker_status() + elif context.protocol == "wmi": + bitlocker_wmi = BitLockerWMI(context, connection) + bitlocker_wmi.check_bitlocker_status() class BitLockerSMB: - def __init__(self, context, connection): - self.context = context - self.connection = connection + def __init__(self, context, connection): + self.context = context + self.connection = connection - def check_bitlocker_status(self): - # PowerShell command to check BitLocker volumes status. - check_bitlocker_command_str = 'powershell.exe "Get-BitLockerVolume | Select-Object MountPoint, EncryptionMethod, ProtectionStatus"' + def check_bitlocker_status(self): + # PowerShell command to check BitLocker volumes status. + check_bitlocker_command_str = 'powershell.exe "Get-BitLockerVolume | Select-Object MountPoint, EncryptionMethod, ProtectionStatus"' - try: - # Executing the PowerShell command to get BitLocker volumes status. - check_bitlocker_command_str_output = self.connection.execute(check_bitlocker_command_str, True) - # Splitting the output into lines. - lines = check_bitlocker_command_str_output.strip().split("\n") + try: + # Executing the PowerShell command to get BitLocker volumes status. + check_bitlocker_command_str_output = self.connection.execute(check_bitlocker_command_str, True) + + if "'Get-BitLockerVolume' is not recognized" in check_bitlocker_command_str_output: + self.context.log.fail("BitLockerVolume not found on target.") + sys.exit(1) - # Getting data lines. - data_lines = lines[2:] + # Splitting the output into lines. + lines = check_bitlocker_command_str_output.strip().split("\n") - # Analyzing data lines. - for line in data_lines: - - parts = re.split(r"\s{2,}", line.strip()) # Stripping spaces and splitting the line. - MountPoint = parts[0] # Getting the mount point of the drive. - EncryptionMethod = parts[1] # Getting the mount point of the drive. - protection_status = parts[2] # Getting the protection status. + # Getting data lines. + data_lines = lines[2:] - # Checking if BitLocker is enabled. - if protection_status == "On": - self.context.log.highlight(f"BitLocker is enabled on drive {MountPoint} (Encryption Method: {EncryptionMethod})") - else: - self.context.log.highlight(f"BitLocker is disabled on drive {MountPoint}") - except Exception as e: - self.context.log.exception(f"Exception occurred: {e}") + # Analyzing data lines. + for line in data_lines: + + parts = re.split(r"\s{2,}", line.strip()) # Stripping spaces and splitting the line. + MountPoint = parts[0] # Getting the mount point of the drive. + EncryptionMethod = parts[1] # Getting the mount point of the drive. + protection_status = parts[2] # Getting the protection status. + + # Checking if BitLocker is enabled. + if protection_status == "On": + self.context.log.highlight(f"BitLocker is enabled on drive {MountPoint} (Encryption Method: {EncryptionMethod})") + else: + self.context.log.highlight(f"BitLocker is disabled on drive {MountPoint}") + except Exception as e: + self.context.log.exception(f"Exception occurred: {e}") class BitLockerWMI: - def __init__(self, context, connection): - self.context = context - self.connection = connection + def __init__(self, context, connection): + self.context = context + self.connection = connection - def check_bitlocker_status(self): - try: - # Create a DCOM connection - dcom_conn = DCOMConnection( - self.connection.host, - self.connection.username, - self.connection.password, - self.connection.domain, - self.connection.lmhash, - self.connection.nthash, - oxidResolver=True, - doKerberos=self.connection.kerberos, - kdcHost=self.connection.kdcHost, - ) - - try: + def check_bitlocker_status(self): + try: + # Create a DCOM connection + dcom_conn = DCOMConnection( + self.connection.host, + self.connection.username, + self.connection.password, + self.connection.domain, + self.connection.lmhash, + self.connection.nthash, + oxidResolver=True, + doKerberos=self.connection.kerberos, + kdcHost=self.connection.kdcHost, + ) + + try: - # CoCreateInstanceEx for WMI login - i_interface = dcom_conn.CoCreateInstanceEx(wmi.CLSID_WbemLevel1Login, wmi.IID_IWbemLevel1Login) - iWbemLevel1Login = wmi.IWbemLevel1Login(i_interface) + # CoCreateInstanceEx for WMI login + i_interface = dcom_conn.CoCreateInstanceEx(wmi.CLSID_WbemLevel1Login, wmi.IID_IWbemLevel1Login) + iWbemLevel1Login = wmi.IWbemLevel1Login(i_interface) - # Specify the namespace for BitLocker - bitlockerNamespace = "root\\CIMv2\\Security\\MicrosoftVolumeEncryption" - - # NTLM login for WMI - iWbemServices = iWbemLevel1Login.NTLMLogin(bitlockerNamespace, NULL, NULL) + # Specify the namespace for BitLocker + bitlockerNamespace = "root\\CIMv2\\Security\\MicrosoftVolumeEncryption" + + # NTLM login for WMI + iWbemServices = iWbemLevel1Login.NTLMLogin(bitlockerNamespace, NULL, NULL) - # Set authentication level - iWbemServices.get_dce_rpc().set_auth_level(RPC_C_AUTHN_LEVEL_PKT_PRIVACY) + # Set authentication level + iWbemServices.get_dce_rpc().set_auth_level(RPC_C_AUTHN_LEVEL_PKT_PRIVACY) - # Query to get BitLocker status - classQuery = "SELECT DriveLetter, ProtectionStatus, EncryptionMethod FROM Win32_EncryptableVolume" - iEnumWbemClassObject = iWbemServices.ExecQuery(classQuery) - encryptionTypeMapping = {0: "None", 1: "AES_256_WITH_DIFFUSER", 2: "AES_256_WITH_DIFFUSER", 3: "AES_128", 4: "AES_256", 5: "HARDWARE_ENCRYPTION", 6: "XTS_AES_128", 7: "XTS_AES_256"} - - try: - while True: - iWbemClassObject = iEnumWbemClassObject.Next(0xffffffff, 1) - encryptionMethod = int(iWbemClassObject[0].EncryptionMethod) - if iWbemClassObject[0].ProtectionStatus == 1: - self.context.log.highlight(f"BitLocker is enabled on drive {iWbemClassObject[0].DriveLetter} (Encryption Method: {encryptionTypeMapping.get(encryptionMethod, 'Unknown')})") - else: - assert (encryptionMethod == 0) # Should be 0 if disabled - self.context.log.highlight(f"BitLocker is disabled on drive {iWbemClassObject[0].DriveLetter}") - except Exception: - pass # Using pass because if try to log or printing, getting "WMI Session Error: code: 0x1 - WBEM_S_FALSE" + # Query to get BitLocker status + classQuery = "SELECT DriveLetter, ProtectionStatus, EncryptionMethod FROM Win32_EncryptableVolume" + iEnumWbemClassObject = iWbemServices.ExecQuery(classQuery) + encryptionTypeMapping = {0: "None", 1: "AES_256_WITH_DIFFUSER", 2: "AES_256_WITH_DIFFUSER", 3: "AES_128", 4: "AES_256", 5: "HARDWARE_ENCRYPTION", 6: "XTS_AES_128", 7: "XTS_AES_256"} + + try: + while True: + iWbemClassObject = iEnumWbemClassObject.Next(0xffffffff, 1) + encryptionMethod = int(iWbemClassObject[0].EncryptionMethod) + if iWbemClassObject[0].ProtectionStatus == 1: + self.context.log.highlight(f"BitLocker is enabled on drive {iWbemClassObject[0].DriveLetter} (Encryption Method: {encryptionTypeMapping.get(encryptionMethod, 'Unknown')})") + else: + assert (encryptionMethod == 0) # Should be 0 if disabled + self.context.log.highlight(f"BitLocker is disabled on drive {iWbemClassObject[0].DriveLetter}") + except Exception: + pass # Using pass because if try to log or printing, getting "WMI Session Error: code: 0x1 - WBEM_S_FALSE" - # Release resources - iWbemLevel1Login.RemRelease() - iWbemServices.RemRelease() - dcom_conn.disconnect() - except Exception as e: - self.context.log.error(f"Error occurred during BitLocker check: {e}") - except Exception as e: - self.context.log.error(f"Error occurred during BitLocker check: {e}") + # Release resources + iWbemLevel1Login.RemRelease() + iWbemServices.RemRelease() + dcom_conn.disconnect() + except Exception as e: + if "WBEM_E_INVALID_NAMESPACE" in str(e): + self.context.log.fail("BitLockerNamespace not found on target.") + dcom_conn.disconnect() + except Exception as e: + self.context.log.error(f"Error occurred during BitLocker check: {e}") + dcom_conn.disconnect() From 5ff488eae5ca0118a4af54c50d60686e13ffd2fa Mon Sep 17 00:00:00 2001 From: termanix <50464194+termanix@users.noreply.github.com> Date: Sun, 5 May 2024 17:15:30 +0300 Subject: [PATCH 07/17] Fixed ruff checks bitlocker.py Signed-off-by: termanix <50464194+termanix@users.noreply.github.com> --- nxc/modules/bitlocker.py | 213 ++++++++++++++++++++------------------- 1 file changed, 109 insertions(+), 104 deletions(-) diff --git a/nxc/modules/bitlocker.py b/nxc/modules/bitlocker.py index cdc9f12a..3ed18371 100644 --- a/nxc/modules/bitlocker.py +++ b/nxc/modules/bitlocker.py @@ -6,128 +6,133 @@ from impacket.dcerpc.v5.dcomrt import DCOMConnection from impacket.dcerpc.v5.rpcrt import RPC_C_AUTHN_LEVEL_PKT_PRIVACY class NXCModule: - name = "bitlocker" - description = "Enumerating BitLocker Status on target(s) If it is enabled or disabled." - supported_protocols = ["smb", "wmi"] - opsec_safe = True - multiple_hosts = True + name = "bitlocker" + description = "Enumerating BitLocker Status on target(s) If it is enabled or disabled." + supported_protocols = ["smb", "wmi"] + opsec_safe = True + multiple_hosts = True - def __init__(self, context=None, module_options=None): - self.context = context - self.module_options = module_options + def __init__(self, context=None, module_options=None): + self.context = context + self.module_options = module_options - def options(self, context, module_options): - """ """ + def options(self, context, module_options): + """ + USAGE: + + NetExec smb -u -p -M bitlocker + NetExec wmi -u -p -M bitlocker (Better option to use on real life.) + """ - def on_admin_login(self, context, connection): - if context.protocol == "smb": - bitlocker_smb = BitLockerSMB(context, connection) - bitlocker_smb.check_bitlocker_status() - elif context.protocol == "wmi": - bitlocker_wmi = BitLockerWMI(context, connection) - bitlocker_wmi.check_bitlocker_status() + def on_admin_login(self, context, connection): + if context.protocol == "smb": + bitlocker_smb = BitLockerSMB(context, connection) + bitlocker_smb.check_bitlocker_status() + elif context.protocol == "wmi": + bitlocker_wmi = BitLockerWMI(context, connection) + bitlocker_wmi.check_bitlocker_status() class BitLockerSMB: - def __init__(self, context, connection): - self.context = context - self.connection = connection + def __init__(self, context, connection): + self.context = context + self.connection = connection - def check_bitlocker_status(self): - # PowerShell command to check BitLocker volumes status. - check_bitlocker_command_str = 'powershell.exe "Get-BitLockerVolume | Select-Object MountPoint, EncryptionMethod, ProtectionStatus"' + def check_bitlocker_status(self): + # PowerShell command to check BitLocker volumes status. + check_bitlocker_command_str = 'powershell.exe "Get-BitLockerVolume | Select-Object MountPoint, EncryptionMethod, ProtectionStatus"' - try: - # Executing the PowerShell command to get BitLocker volumes status. - check_bitlocker_command_str_output = self.connection.execute(check_bitlocker_command_str, True) - - if "'Get-BitLockerVolume' is not recognized" in check_bitlocker_command_str_output: - self.context.log.fail("BitLockerVolume not found on target.") - sys.exit(1) + try: + # Executing the PowerShell command to get BitLocker volumes status. + check_bitlocker_command_str_output = self.connection.execute(check_bitlocker_command_str, True) + + if "'Get-BitLockerVolume' is not recognized" in check_bitlocker_command_str_output: + self.context.log.fail("BitLockerVolume not found on target.") + sys.exit(1) - # Splitting the output into lines. - lines = check_bitlocker_command_str_output.strip().split("\n") + # Splitting the output into lines. + lines = check_bitlocker_command_str_output.strip().split("\n") - # Getting data lines. - data_lines = lines[2:] + # Getting data lines. + data_lines = lines[2:] - # Analyzing data lines. - for line in data_lines: - - parts = re.split(r"\s{2,}", line.strip()) # Stripping spaces and splitting the line. - MountPoint = parts[0] # Getting the mount point of the drive. - EncryptionMethod = parts[1] # Getting the mount point of the drive. - protection_status = parts[2] # Getting the protection status. + # Analyzing data lines. + for line in data_lines: + + parts = re.split(r"\s{2,}", line.strip()) # Stripping spaces and splitting the line. + MountPoint = parts[0] # Getting the mount point of the drive. + EncryptionMethod = parts[1] # Getting the mount point of the drive. + protection_status = parts[2] # Getting the protection status. - # Checking if BitLocker is enabled. - if protection_status == "On": - self.context.log.highlight(f"BitLocker is enabled on drive {MountPoint} (Encryption Method: {EncryptionMethod})") - else: - self.context.log.highlight(f"BitLocker is disabled on drive {MountPoint}") - except Exception as e: - self.context.log.exception(f"Exception occurred: {e}") + # Checking if BitLocker is enabled. + if protection_status == "On": + self.context.log.highlight(f"BitLocker is enabled on drive {MountPoint} (Encryption Method: {EncryptionMethod})") + else: + self.context.log.highlight(f"BitLocker is disabled on drive {MountPoint}") + except Exception as e: + self.context.log.exception(f"Exception occurred: {e}") class BitLockerWMI: - def __init__(self, context, connection): - self.context = context - self.connection = connection + def __init__(self, context, connection): + self.context = context + self.connection = connection - def check_bitlocker_status(self): - try: - # Create a DCOM connection - dcom_conn = DCOMConnection( - self.connection.host, - self.connection.username, - self.connection.password, - self.connection.domain, - self.connection.lmhash, - self.connection.nthash, - oxidResolver=True, - doKerberos=self.connection.kerberos, - kdcHost=self.connection.kdcHost, - ) - - try: + def check_bitlocker_status(self): + try: + # Create a DCOM connection + dcom_conn = DCOMConnection( + self.connection.host, + self.connection.username, + self.connection.password, + self.connection.domain, + self.connection.lmhash, + self.connection.nthash, + oxidResolver=True, + doKerberos=self.connection.kerberos, + kdcHost=self.connection.kdcHost, + ) + + try: - # CoCreateInstanceEx for WMI login - i_interface = dcom_conn.CoCreateInstanceEx(wmi.CLSID_WbemLevel1Login, wmi.IID_IWbemLevel1Login) - iWbemLevel1Login = wmi.IWbemLevel1Login(i_interface) + # CoCreateInstanceEx for WMI login + i_interface = dcom_conn.CoCreateInstanceEx(wmi.CLSID_WbemLevel1Login, wmi.IID_IWbemLevel1Login) + iWbemLevel1Login = wmi.IWbemLevel1Login(i_interface) - # Specify the namespace for BitLocker - bitlockerNamespace = "root\\CIMv2\\Security\\MicrosoftVolumeEncryption" - - # NTLM login for WMI - iWbemServices = iWbemLevel1Login.NTLMLogin(bitlockerNamespace, NULL, NULL) + # Specify the namespace for BitLocker + bitlockerNamespace = "root\\CIMv2\\Security\\MicrosoftVolumeEncryption" + + # NTLM login for WMI + iWbemServices = iWbemLevel1Login.NTLMLogin(bitlockerNamespace, NULL, NULL) - # Set authentication level - iWbemServices.get_dce_rpc().set_auth_level(RPC_C_AUTHN_LEVEL_PKT_PRIVACY) + # Set authentication level + iWbemServices.get_dce_rpc().set_auth_level(RPC_C_AUTHN_LEVEL_PKT_PRIVACY) - # Query to get BitLocker status - classQuery = "SELECT DriveLetter, ProtectionStatus, EncryptionMethod FROM Win32_EncryptableVolume" - iEnumWbemClassObject = iWbemServices.ExecQuery(classQuery) - encryptionTypeMapping = {0: "None", 1: "AES_256_WITH_DIFFUSER", 2: "AES_256_WITH_DIFFUSER", 3: "AES_128", 4: "AES_256", 5: "HARDWARE_ENCRYPTION", 6: "XTS_AES_128", 7: "XTS_AES_256"} - - try: - while True: - iWbemClassObject = iEnumWbemClassObject.Next(0xffffffff, 1) - encryptionMethod = int(iWbemClassObject[0].EncryptionMethod) - if iWbemClassObject[0].ProtectionStatus == 1: - self.context.log.highlight(f"BitLocker is enabled on drive {iWbemClassObject[0].DriveLetter} (Encryption Method: {encryptionTypeMapping.get(encryptionMethod, 'Unknown')})") - else: - assert (encryptionMethod == 0) # Should be 0 if disabled - self.context.log.highlight(f"BitLocker is disabled on drive {iWbemClassObject[0].DriveLetter}") - except Exception: - pass # Using pass because if try to log or printing, getting "WMI Session Error: code: 0x1 - WBEM_S_FALSE" + # Query to get BitLocker status + classQuery = "SELECT DriveLetter, ProtectionStatus, EncryptionMethod FROM Win32_EncryptableVolume" + iEnumWbemClassObject = iWbemServices.ExecQuery(classQuery) + encryptionTypeMapping = {0: "None", 1: "AES_256_WITH_DIFFUSER", 2: "AES_256_WITH_DIFFUSER", 3: "AES_128", 4: "AES_256", 5: "HARDWARE_ENCRYPTION", 6: "XTS_AES_128", 7: "XTS_AES_256"} + + try: + while True: + iWbemClassObject = iEnumWbemClassObject.Next(0xffffffff, 1) + encryptionMethod = int(iWbemClassObject[0].EncryptionMethod) + if iWbemClassObject[0].ProtectionStatus == 1: + self.context.log.highlight(f"BitLocker is enabled on drive {iWbemClassObject[0].DriveLetter} (Encryption Method: {encryptionTypeMapping.get(encryptionMethod, 'Unknown')})") + else: + assert (encryptionMethod == 0) # Should be 0 if disabled + self.context.log.highlight(f"BitLocker is disabled on drive {iWbemClassObject[0].DriveLetter}") + except Exception: + pass # Using pass because if try to log or printing, getting "WMI Session Error: code: 0x1 - WBEM_S_FALSE" - # Release resources - iWbemLevel1Login.RemRelease() - iWbemServices.RemRelease() - dcom_conn.disconnect() - except Exception as e: - if "WBEM_E_INVALID_NAMESPACE" in str(e): - self.context.log.fail("BitLockerNamespace not found on target.") - dcom_conn.disconnect() - except Exception as e: - self.context.log.error(f"Error occurred during BitLocker check: {e}") - dcom_conn.disconnect() + # Release resources + iWbemLevel1Login.RemRelease() + iWbemServices.RemRelease() + dcom_conn.disconnect() + except Exception as e: + if "WBEM_E_INVALID_NAMESPACE" in str(e): + self.context.log.fail("BitLockerNamespace not found on target.") + dcom_conn.disconnect() + except Exception as e: + self.context.log.error(f"Error occurred during BitLocker check: {e}") + dcom_conn.disconnect() From 6c4e8ead5e4158788b7fc9a01fb002b36c0f6444 Mon Sep 17 00:00:00 2001 From: termanix <50464194+termanix@users.noreply.github.com> Date: Fri, 10 May 2024 22:05:40 +0300 Subject: [PATCH 08/17] Update e2e commands --- tests/e2e_commands.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/e2e_commands.txt b/tests/e2e_commands.txt index b895b6ad..ba0cded9 100644 --- a/tests/e2e_commands.txt +++ b/tests/e2e_commands.txt @@ -1,3 +1,5 @@ +##### WMI Modules +netexec wmi TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M bitlocker ##### SMB netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS # need an extra space after this command due to regex netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS --shares @@ -34,6 +36,7 @@ netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M add-comp netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M add-computer -o NAME="BADPC" DELETE=True netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M bh_owned --options netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M bh_owned +netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M bitlocker netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M dfscoerce --options netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M dfscoerce netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M drop-sc From 406507e8deffb9ca636a155a9cc53516a75fcf91 Mon Sep 17 00:00:00 2001 From: termanix <50464194+termanix@users.noreply.github.com> Date: Wed, 15 May 2024 19:23:56 +0300 Subject: [PATCH 09/17] Update bitlocker.py Updated according to the new powershell update. Signed-off-by: termanix <50464194+termanix@users.noreply.github.com> --- nxc/modules/bitlocker.py | 45 +++++++++++++++++----------------------- 1 file changed, 19 insertions(+), 26 deletions(-) diff --git a/nxc/modules/bitlocker.py b/nxc/modules/bitlocker.py index 3ed18371..4665552b 100644 --- a/nxc/modules/bitlocker.py +++ b/nxc/modules/bitlocker.py @@ -1,5 +1,4 @@ import re -import sys from impacket.dcerpc.v5.dcom import wmi from impacket.dcerpc.v5.dtypes import NULL from impacket.dcerpc.v5.dcomrt import DCOMConnection @@ -40,35 +39,31 @@ class BitLockerSMB: def check_bitlocker_status(self): # PowerShell command to check BitLocker volumes status. - check_bitlocker_command_str = 'powershell.exe "Get-BitLockerVolume | Select-Object MountPoint, EncryptionMethod, ProtectionStatus"' + check_bitlocker_command_str = "Get-BitLockerVolume | Select-Object MountPoint, EncryptionMethod, ProtectionStatus" try: # Executing the PowerShell command to get BitLocker volumes status. - check_bitlocker_command_str_output = self.connection.execute(check_bitlocker_command_str, True) + check_bitlocker_command_str_output = self.connection.ps_execute(check_bitlocker_command_str, True) if "'Get-BitLockerVolume' is not recognized" in check_bitlocker_command_str_output: self.context.log.fail("BitLockerVolume not found on target.") - sys.exit(1) + return # Splitting the output into lines. - lines = check_bitlocker_command_str_output.strip().split("\n") - - # Getting data lines. - data_lines = lines[2:] - - # Analyzing data lines. + lines = str(check_bitlocker_command_str_output).split("\\n") + data_lines = [line for line in lines if re.match(r"\w:", line)] + for line in data_lines: - - parts = re.split(r"\s{2,}", line.strip()) # Stripping spaces and splitting the line. - MountPoint = parts[0] # Getting the mount point of the drive. - EncryptionMethod = parts[1] # Getting the mount point of the drive. - protection_status = parts[2] # Getting the protection status. + # Checking every line for starting with drive + if line[1] == ":": + parts = line.split() + MountPoint, EncryptionMethod, protection_status = parts[0], parts[1], parts[2] - # Checking if BitLocker is enabled. - if protection_status == "On": - self.context.log.highlight(f"BitLocker is enabled on drive {MountPoint} (Encryption Method: {EncryptionMethod})") - else: - self.context.log.highlight(f"BitLocker is disabled on drive {MountPoint}") + # Checking if BitLocker is enabled. + if protection_status == "On": + self.context.log.highlight(f"BitLocker is enabled on drive {MountPoint} (Encryption Method: {EncryptionMethod})") + else: + self.context.log.highlight(f"BitLocker is disabled on drive {MountPoint}") except Exception as e: self.context.log.exception(f"Exception occurred: {e}") @@ -90,11 +85,9 @@ class BitLockerWMI: self.connection.nthash, oxidResolver=True, doKerberos=self.connection.kerberos, - kdcHost=self.connection.kdcHost, - ) - + kdcHost=self.connection.kdcHost) + try: - # CoCreateInstanceEx for WMI login i_interface = dcom_conn.CoCreateInstanceEx(wmi.CLSID_WbemLevel1Login, wmi.IID_IWbemLevel1Login) iWbemLevel1Login = wmi.IWbemLevel1Login(i_interface) @@ -120,8 +113,8 @@ class BitLockerWMI: if iWbemClassObject[0].ProtectionStatus == 1: self.context.log.highlight(f"BitLocker is enabled on drive {iWbemClassObject[0].DriveLetter} (Encryption Method: {encryptionTypeMapping.get(encryptionMethod, 'Unknown')})") else: - assert (encryptionMethod == 0) # Should be 0 if disabled - self.context.log.highlight(f"BitLocker is disabled on drive {iWbemClassObject[0].DriveLetter}") + if encryptionMethod == 0: # Should be 0 if disabled + self.context.log.highlight(f"BitLocker is disabled on drive {iWbemClassObject[0].DriveLetter}") except Exception: pass # Using pass because if try to log or printing, getting "WMI Session Error: code: 0x1 - WBEM_S_FALSE" From 0aaad63819cb27b3d29004c1efff4bbe6e946bc4 Mon Sep 17 00:00:00 2001 From: termanix <50464194+termanix@users.noreply.github.com> Date: Thu, 16 May 2024 14:37:06 +0300 Subject: [PATCH 10/17] Update bitlocker.py Fixed smb error line. Signed-off-by: termanix <50464194+termanix@users.noreply.github.com> --- nxc/modules/bitlocker.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nxc/modules/bitlocker.py b/nxc/modules/bitlocker.py index 4665552b..63c387ff 100644 --- a/nxc/modules/bitlocker.py +++ b/nxc/modules/bitlocker.py @@ -39,13 +39,13 @@ class BitLockerSMB: def check_bitlocker_status(self): # PowerShell command to check BitLocker volumes status. - check_bitlocker_command_str = "Get-BitLockerVolume | Select-Object MountPoint, EncryptionMethod, ProtectionStatus" + check_bitlocker_command_str = "Get-BitLockerVolume -EA SilentlyContinue | Select-Object MountPoint, EncryptionMethod, ProtectionStatus" try: # Executing the PowerShell command to get BitLocker volumes status. check_bitlocker_command_str_output = self.connection.ps_execute(check_bitlocker_command_str, True) - if "'Get-BitLockerVolume' is not recognized" in check_bitlocker_command_str_output: + if any("'Get-BitLockerVolume' is not recognized" in line for line in check_bitlocker_command_str_output): self.context.log.fail("BitLockerVolume not found on target.") return From 610bad1bae9834b1a4bb6920023bae696d0b1433 Mon Sep 17 00:00:00 2001 From: termanix <50464194+termanix@users.noreply.github.com> Date: Thu, 16 May 2024 16:15:27 +0300 Subject: [PATCH 11/17] Update bitlocker.py Signed-off-by: termanix <50464194+termanix@users.noreply.github.com> --- nxc/modules/bitlocker.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nxc/modules/bitlocker.py b/nxc/modules/bitlocker.py index 63c387ff..b6796263 100644 --- a/nxc/modules/bitlocker.py +++ b/nxc/modules/bitlocker.py @@ -39,7 +39,7 @@ class BitLockerSMB: def check_bitlocker_status(self): # PowerShell command to check BitLocker volumes status. - check_bitlocker_command_str = "Get-BitLockerVolume -EA SilentlyContinue | Select-Object MountPoint, EncryptionMethod, ProtectionStatus" + check_bitlocker_command_str = "Get-BitLockerVolume | Select-Object MountPoint, EncryptionMethod, ProtectionStatus" try: # Executing the PowerShell command to get BitLocker volumes status. From 9d3bc4d071dd4dd84ac011e26b9f9039a311f602 Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Fri, 14 Jun 2024 17:36:40 -0400 Subject: [PATCH 12/17] Formating and ruff --- nxc/modules/interface.py | 64 +++++++++++++++++++--------------------- 1 file changed, 31 insertions(+), 33 deletions(-) diff --git a/nxc/modules/interface.py b/nxc/modules/interface.py index 567ea063..2297c474 100644 --- a/nxc/modules/interface.py +++ b/nxc/modules/interface.py @@ -3,20 +3,22 @@ from impacket.dcerpc.v5 import rrp from impacket.examples.secretsdump import RemoteOperations from impacket.dcerpc.v5.rpcrt import DCERPCException +import contextlib + class NXCModule: - ''' + """ Retrieve the list of network interfaces info (Name, IP Address, Subnet Mask, Default Gateway) from remote Windows registry' Module by Sant0rryu : @Sant0rryu - ''' - name = 'interface' - description = 'Retrieve the list of network interfaces info (Name, IP Address, Subnet Mask, Default Gateway) from remote Windows registry' - supported_protocols = ['smb'] + """ + name = "interface" + description = "Retrieve the list of network interfaces info (Name, IP Address, Subnet Mask, Default Gateway) from remote Windows registry" + supported_protocols = ["smb"] opsec_safe = True multiple_hosts = True def options(self, context, module_options): - pass + """No options""" def on_admin_login(self, context, connection): self.output = "Name: {} | IP Address: {} | SubnetMask: {} | Gateway: {}" @@ -26,10 +28,10 @@ class NXCModule: if remoteOps._RemoteOperations__rrp: ans = rrp.hOpenLocalMachine(remoteOps._RemoteOperations__rrp) - regHandle = ans['phKey'] + regHandle = ans["phKey"] - ans = rrp.hBaseRegOpenKey(remoteOps._RemoteOperations__rrp, regHandle, 'SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters\\Interfaces') - keyHandle = ans['phkResult'] + ans = rrp.hBaseRegOpenKey(remoteOps._RemoteOperations__rrp, regHandle, "SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters\\Interfaces") + keyHandle = ans["phkResult"] interface = {} subKeys = [] @@ -37,47 +39,43 @@ class NXCModule: while True: try: key = rrp.hBaseRegEnumKey(remoteOps._RemoteOperations__rrp, keyHandle, i) - subKeys.append(key['lpNameOut'][:-1]) + subKeys.append(key["lpNameOut"][:-1]) i += 1 except Exception: break for subKey in subKeys: try: - interfaceKey = 'SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters\\Interfaces\\{}'.format(subKey) + interfaceKey = f"SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters\\Interfaces\\{subKey}" ans = rrp.hBaseRegOpenKey(remoteOps._RemoteOperations__rrp, regHandle, interfaceKey) - interfaceHandle = ans['phkResult'] + interfaceHandle = ans["phkResult"] - #Retrieve IPAddress - ip_address = rrp.hBaseRegQueryValue(remoteOps._RemoteOperations__rrp, interfaceHandle, 'IPAddress') - interface[subKey] = {'IPAddress' : str(ip_address[1])} + # Retrieve IPAddress + ip_address = rrp.hBaseRegQueryValue(remoteOps._RemoteOperations__rrp, interfaceHandle, "IPAddress") + interface[subKey] = {"IPAddress": str(ip_address[1])} - #Retrieve SubnetMask - subnetmask = rrp.hBaseRegQueryValue(remoteOps._RemoteOperations__rrp, interfaceHandle, 'SubnetMask') - interface[subKey]['SubnetMask'] = str(subnetmask[1]) + # Retrieve SubnetMask + subnetmask = rrp.hBaseRegQueryValue(remoteOps._RemoteOperations__rrp, interfaceHandle, "SubnetMask") + interface[subKey]["SubnetMask"] = str(subnetmask[1]) + # Retrieve DefaultGateway + defaultgateway = rrp.hBaseRegQueryValue(remoteOps._RemoteOperations__rrp, interfaceHandle, "DefaultGateway") + interface[subKey]["DefaultGateway"] = str(defaultgateway[1]) - #Retrieve DefaultGateway - defaultgateway = rrp.hBaseRegQueryValue(remoteOps._RemoteOperations__rrp, interfaceHandle, 'DefaultGateway') - interface[subKey]['DefaultGateway'] = str(defaultgateway[1]) - - #Retrieve Interace Name - interfaceNameKey = 'SYSTEM\\ControlSet001\\Control\\Network\\' + '{4D36E972-E325-11CE-BFC1-08002BE10318}' + '\\{}\\Connection'.format(subKey) + # Retrieve Interace Name + interfaceNameKey = "SYSTEM\\ControlSet001\\Control\\Network\\" + "{4D36E972-E325-11CE-BFC1-08002BE10318}" + f"\\{subKey}\\Connection" ans = rrp.hBaseRegOpenKey(remoteOps._RemoteOperations__rrp, regHandle, interfaceNameKey) - interfaceNameHandle = ans['phkResult'] - name = rrp.hBaseRegQueryValue(remoteOps._RemoteOperations__rrp, interfaceNameHandle, 'Name') - interface[subKey]['Name'] = str(name[1]) + interfaceNameHandle = ans["phkResult"] + name = rrp.hBaseRegQueryValue(remoteOps._RemoteOperations__rrp, interfaceNameHandle, "Name") + interface[subKey]["Name"] = str(name[1]) - - context.log.highlight(self.output.format(interface[subKey]['Name'], interface[subKey]['IPAddress'], interface[subKey]['SubnetMask'], interface[subKey]['DefaultGateway'])) + context.log.highlight(self.output.format(interface[subKey]["Name"], interface[subKey]["IPAddress"], interface[subKey]["SubnetMask"], interface[subKey]["DefaultGateway"])) except DCERPCException: continue - try: + with contextlib.suppress(Exception): remoteOps.finish() - except Exception: - pass except DCERPCException as e: - context.log.error(f"Failed to connect to the target: {str(e)}") + context.log.error(f"Failed to connect to the target: {e!s}") From 008c490f5b0823f212dd5eb86b853820310ceff6 Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Fri, 14 Jun 2024 20:38:29 -0400 Subject: [PATCH 13/17] Reworked interface module to core functionality and fixed all its bugs --- nxc/modules/interface.py | 81 --------------------------------- nxc/protocols/smb.py | 70 +++++++++++++++++++++++++++- nxc/protocols/smb/proto_args.py | 1 + 3 files changed, 70 insertions(+), 82 deletions(-) delete mode 100644 nxc/modules/interface.py diff --git a/nxc/modules/interface.py b/nxc/modules/interface.py deleted file mode 100644 index 2297c474..00000000 --- a/nxc/modules/interface.py +++ /dev/null @@ -1,81 +0,0 @@ -#!/usr/bin/env python3 - -from impacket.dcerpc.v5 import rrp -from impacket.examples.secretsdump import RemoteOperations -from impacket.dcerpc.v5.rpcrt import DCERPCException -import contextlib - - -class NXCModule: - """ - Retrieve the list of network interfaces info (Name, IP Address, Subnet Mask, Default Gateway) from remote Windows registry' - Module by Sant0rryu : @Sant0rryu - """ - name = "interface" - description = "Retrieve the list of network interfaces info (Name, IP Address, Subnet Mask, Default Gateway) from remote Windows registry" - supported_protocols = ["smb"] - opsec_safe = True - multiple_hosts = True - - def options(self, context, module_options): - """No options""" - - def on_admin_login(self, context, connection): - self.output = "Name: {} | IP Address: {} | SubnetMask: {} | Gateway: {}" - try: - remoteOps = RemoteOperations(connection.conn, False) - remoteOps.enableRegistry() - - if remoteOps._RemoteOperations__rrp: - ans = rrp.hOpenLocalMachine(remoteOps._RemoteOperations__rrp) - regHandle = ans["phKey"] - - ans = rrp.hBaseRegOpenKey(remoteOps._RemoteOperations__rrp, regHandle, "SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters\\Interfaces") - keyHandle = ans["phkResult"] - - interface = {} - subKeys = [] - i = 0 - while True: - try: - key = rrp.hBaseRegEnumKey(remoteOps._RemoteOperations__rrp, keyHandle, i) - subKeys.append(key["lpNameOut"][:-1]) - i += 1 - except Exception: - break - - for subKey in subKeys: - try: - interfaceKey = f"SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters\\Interfaces\\{subKey}" - ans = rrp.hBaseRegOpenKey(remoteOps._RemoteOperations__rrp, regHandle, interfaceKey) - interfaceHandle = ans["phkResult"] - - # Retrieve IPAddress - ip_address = rrp.hBaseRegQueryValue(remoteOps._RemoteOperations__rrp, interfaceHandle, "IPAddress") - interface[subKey] = {"IPAddress": str(ip_address[1])} - - # Retrieve SubnetMask - subnetmask = rrp.hBaseRegQueryValue(remoteOps._RemoteOperations__rrp, interfaceHandle, "SubnetMask") - interface[subKey]["SubnetMask"] = str(subnetmask[1]) - - # Retrieve DefaultGateway - defaultgateway = rrp.hBaseRegQueryValue(remoteOps._RemoteOperations__rrp, interfaceHandle, "DefaultGateway") - interface[subKey]["DefaultGateway"] = str(defaultgateway[1]) - - # Retrieve Interace Name - interfaceNameKey = "SYSTEM\\ControlSet001\\Control\\Network\\" + "{4D36E972-E325-11CE-BFC1-08002BE10318}" + f"\\{subKey}\\Connection" - ans = rrp.hBaseRegOpenKey(remoteOps._RemoteOperations__rrp, regHandle, interfaceNameKey) - interfaceNameHandle = ans["phkResult"] - name = rrp.hBaseRegQueryValue(remoteOps._RemoteOperations__rrp, interfaceNameHandle, "Name") - interface[subKey]["Name"] = str(name[1]) - - context.log.highlight(self.output.format(interface[subKey]["Name"], interface[subKey]["IPAddress"], interface[subKey]["SubnetMask"], interface[subKey]["DefaultGateway"])) - - except DCERPCException: - continue - - with contextlib.suppress(Exception): - remoteOps.finish() - - except DCERPCException as e: - context.log.error(f"Failed to connect to the target: {e!s}") diff --git a/nxc/protocols/smb.py b/nxc/protocols/smb.py index b18ee7ab..6ea05453 100755 --- a/nxc/protocols/smb.py +++ b/nxc/protocols/smb.py @@ -14,7 +14,7 @@ from impacket.examples.secretsdump import ( NTDSHashes, ) from impacket.nmb import NetBIOSError, NetBIOSTimeout -from impacket.dcerpc.v5 import transport, lsat, lsad, scmr +from impacket.dcerpc.v5 import transport, lsat, lsad, scmr, rrp from impacket.dcerpc.v5.rpcrt import DCERPCException from impacket.dcerpc.v5.transport import DCERPCTransportFactory, SMBTransport from impacket.dcerpc.v5.rpcrt import RPC_C_AUTHN_GSS_NEGOTIATE @@ -833,6 +833,74 @@ class smb(connection): continue self.logger.highlight(f"{name:<15} {','.join(perms):<15} {remark}") return permissions + + def interfaces(self): + """ + Retrieve the list of network interfaces info (Name, IP Address, Subnet Mask, Default Gateway) from remote Windows registry' + Made by: @Sant0rryu, @NeffIsBack + """ + try: + remoteOps = RemoteOperations(self.conn, False) + remoteOps.enableRegistry() + + if remoteOps._RemoteOperations__rrp: + reg_handle = rrp.hOpenLocalMachine(remoteOps._RemoteOperations__rrp)["phKey"] + key_handle = rrp.hBaseRegOpenKey(remoteOps._RemoteOperations__rrp, reg_handle, "SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters\\Interfaces")["phkResult"] + sub_key_list = rrp.hBaseRegQueryInfoKey(remoteOps._RemoteOperations__rrp, key_handle)["lpcSubKeys"] + sub_keys = [rrp.hBaseRegEnumKey(remoteOps._RemoteOperations__rrp, key_handle, i)["lpNameOut"][:-1] for i in range(sub_key_list)] + + self.logger.highlight(f"{'-Name-':<11} | {'-IP Address-':<15} | {'-SubnetMask-':<15} | {'-Gateway-':<15} | -DHCP-") + for sub_key in sub_keys: + interface = {} + try: + interface_key = f"SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters\\Interfaces\\{sub_key}" + interface_handle = rrp.hBaseRegOpenKey(remoteOps._RemoteOperations__rrp, reg_handle, interface_key)["phkResult"] + + # Retrieve Interace Name + interface_name_key = f"SYSTEM\\ControlSet001\\Control\\Network\\{{4D36E972-E325-11CE-BFC1-08002BE10318}}\\{sub_key}\\Connection" + interface_name_handle = rrp.hBaseRegOpenKey(remoteOps._RemoteOperations__rrp, reg_handle, interface_name_key)["phkResult"] + interface_name = rrp.hBaseRegQueryValue(remoteOps._RemoteOperations__rrp, interface_name_handle, "Name")[1].rstrip("\x00") + interface["Name"] = str(interface_name) + if "Kernel" in interface_name: + continue + + # Retrieve DHCP + try: + dhcp_enabled = rrp.hBaseRegQueryValue(remoteOps._RemoteOperations__rrp, interface_handle, "EnableDHCP")[1] + except DCERPCException: + dhcp_enabled = False + interface["DHCP"] = bool(dhcp_enabled) + + # Retrieve IPAddress + try: + ip_address = rrp.hBaseRegQueryValue(remoteOps._RemoteOperations__rrp, interface_handle, "DhcpIPAddress" if dhcp_enabled else "IPAddress")[1].rstrip("\x00").replace("\x00", ", ") + except DCERPCException: + ip_address = None + interface["IPAddress"] = ip_address if ip_address else None + + # Retrieve SubnetMask + try: + subnetmask = rrp.hBaseRegQueryValue(remoteOps._RemoteOperations__rrp, interface_handle, "SubnetMask")[1].rstrip("\x00").replace("\x00", ", ") + except DCERPCException: + subnetmask = None + interface["SubnetMask"] = subnetmask if subnetmask else None + + # Retrieve DefaultGateway + try: + default_gateway = rrp.hBaseRegQueryValue(remoteOps._RemoteOperations__rrp, interface_handle, "DhcpDefaultGateway")[1].rstrip("\x00").replace("\x00", ", ") + except DCERPCException: + default_gateway = None + interface["DefaultGateway"] = default_gateway if default_gateway else None + + self.logger.highlight(f"{interface['Name']:<11} | {interface['IPAddress']!s:<15} | {interface['SubnetMask']!s:<15} | {interface['DefaultGateway']!s:<15} | {interface['DHCP']}") + + except DCERPCException as e: + self.logger.info(f"Failed to retrieve the network interface info for {sub_key}: {e!s}") + + with contextlib.suppress(Exception): + remoteOps.finish() + except DCERPCException as e: + self.logger.error(f"Failed to connect to the target: {e!s}") def get_dc_ips(self): dc_ips = [dc[1] for dc in self.db.get_domain_controllers(domain=self.domain)] diff --git a/nxc/protocols/smb/proto_args.py b/nxc/protocols/smb/proto_args.py index 052db2aa..35dd8fe2 100644 --- a/nxc/protocols/smb/proto_args.py +++ b/nxc/protocols/smb/proto_args.py @@ -34,6 +34,7 @@ def proto_args(parser, parents): mapping_enum_group = smb_parser.add_argument_group("Mapping/Enumeration", "Options for Mapping/Enumerating") mapping_enum_group.add_argument("--shares", action="store_true", help="enumerate shares and access") + mapping_enum_group.add_argument("--interfaces", action="store_true", help="enumerate network interfaces") mapping_enum_group.add_argument("--no-write-check", action="store_true", help="Skip write check on shares (avoid leaving traces when missing delete permissions)") mapping_enum_group.add_argument("--filter-shares", nargs="+", help="Filter share by access, option 'read' 'write' or 'read,write'") mapping_enum_group.add_argument("--sessions", action="store_true", help="enumerate active sessions") From c09304ef6c9606a8a84970c318a70c6fa9f7c8fc Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Fri, 14 Jun 2024 20:39:04 -0400 Subject: [PATCH 14/17] Autoformat --- nxc/protocols/smb.py | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/nxc/protocols/smb.py b/nxc/protocols/smb.py index 6ea05453..f089d5e4 100755 --- a/nxc/protocols/smb.py +++ b/nxc/protocols/smb.py @@ -273,7 +273,7 @@ class smb(connection): self.conn.logoff() except Exception as e: self.logger.debug(f"Error logging off system: {e}") - + # DCOM connection with kerberos needed self.remoteName = self.host if not self.kerberos else f"{self.hostname}.{self.domain}" @@ -707,11 +707,11 @@ class smb(connection): except UnicodeDecodeError: self.logger.debug("Decoding error detected, consider running chcp.com at the target, map the result with https://docs.python.org/3/library/codecs.html#standard-encodings") output = output.decode("cp437") - + self.logger.debug(f"Raw Output: {output}") output = "\n".join([ll.rstrip() for ll in output.splitlines() if ll.strip()]) self.logger.debug(f"Cleaned Output: {output}") - + if "This script contains malicious content" in output: self.logger.fail("Command execution blocked by AMSI") return None @@ -732,24 +732,24 @@ class smb(connection): if not payload: self.logger.error("No command to execute specified!") return None - + response = [] obfs = obfs if obfs else self.args.obfs encode = encode if encode else not self.args.no_encode force_ps32 = force_ps32 if force_ps32 else self.args.force_ps32 get_output = True if not self.args.no_output else get_output - + self.logger.debug(f"Starting ps_execute(): {payload=} {get_output=} {methods=} {force_ps32=} {obfs=} {encode=}") amsi_bypass = self.args.amsi_bypass[0] if self.args.amsi_bypass else None self.logger.debug(f"AMSI Bypass: {amsi_bypass}") - + if os.path.isfile(payload): self.logger.debug(f"File payload set: {payload}") with open(payload) as commands: response = [self.execute(create_ps_command(c.strip(), force_ps32=force_ps32, obfs=obfs, custom_amsi=amsi_bypass, encode=encode), get_output, methods) for c in commands] else: response = [self.execute(create_ps_command(payload, force_ps32=force_ps32, obfs=obfs, custom_amsi=amsi_bypass, encode=encode), get_output, methods)] - + self.logger.debug(f"ps_execute response: {response}") return response @@ -833,7 +833,7 @@ class smb(connection): continue self.logger.highlight(f"{name:<15} {','.join(perms):<15} {remark}") return permissions - + def interfaces(self): """ Retrieve the list of network interfaces info (Name, IP Address, Subnet Mask, Default Gateway) from remote Windows registry' @@ -1370,7 +1370,7 @@ class smb(connection): self.logger.success(f"Created file {src} on \\\\{self.args.share}\\{dst}") except Exception as e: self.logger.fail(f"Error writing file to share {self.args.share}: {e}") - + def put_file(self): for src, dest in self.args.put_file: self.put_file_single(src, dest) @@ -1393,7 +1393,6 @@ class smb(connection): for src, dest in self.args.get_file: self.get_file_single(src, dest) - def enable_remoteops(self): try: self.remote_ops = RemoteOperations(self.conn, self.kerberos, self.kdcHost) @@ -1476,7 +1475,7 @@ class smb(connection): except Exception as e: self.logger.debug(f"Could not upgrade connection: {e}") return - + try: self.logger.display("Collecting Machine masterkeys, grab a coffee and be patient...") masterkeys_triage = MasterkeysTriage( @@ -1491,7 +1490,7 @@ class smb(connection): if len(masterkeys) == 0: self.logger.fail("No masterkeys looted") return - + self.logger.success(f"Got {highlight(len(masterkeys))} decrypted masterkeys. Looting SCCM Credentials through {self.args.sccm}") try: # Collect Chrome Based Browser stored secrets @@ -1681,7 +1680,6 @@ class smb(connection): "Google Refresh Token", ) - if dump_cookies and cookies: self.logger.display("Start Dumping Cookies") for cookie in cookies: @@ -1869,4 +1867,4 @@ class smb(connection): NTDS.finish() def mark_guest(self): - return highlight(f"{highlight('(Guest)')}" if self.is_guest else "") \ No newline at end of file + return highlight(f"{highlight('(Guest)')}" if self.is_guest else "") From e50f7acbd505dc87ebe0f8cb92df554fb4ca17f8 Mon Sep 17 00:00:00 2001 From: Nicolas Serra <45828470+bfnserra@users.noreply.github.com> Date: Fri, 21 Jun 2024 19:12:03 +0200 Subject: [PATCH 15/17] Update pso.py Fix Typo (obersationWindow to observationWindow) Signed-off-by: Nicolas Serra <45828470+bfnserra@users.noreply.github.com> --- nxc/modules/pso.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nxc/modules/pso.py b/nxc/modules/pso.py index 3dfed063..a9d930d1 100644 --- a/nxc/modules/pso.py +++ b/nxc/modules/pso.py @@ -53,7 +53,7 @@ class NXCModule: for attrs in resp: if not isinstance(attrs, ldapasn1_impacket.SearchResultEntry): continue - policyName, description, passwordLength, passwordhistorylength, lockoutThreshold, obersationWindow, lockoutDuration, complexity, minPassAge, maxPassAge, reverseibleEncryption, precedence, policyApplies = ("",) * 13 + policyName, description, passwordLength, passwordhistorylength, lockoutThreshold, observationWindow, lockoutDuration, complexity, minPassAge, maxPassAge, reverseibleEncryption, precedence, policyApplies = ("",) * 13 for attr in attrs["attributes"]: if str(attr["type"]) == "name": policyName = attr["vals"][0] From 9d32ea0a2b5f764d5869ae22bab78a70384d2619 Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Sun, 23 Jun 2024 13:54:48 -0400 Subject: [PATCH 16/17] Add exception handling to prevent crashes against linux hosts --- nxc/protocols/smb.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/nxc/protocols/smb.py b/nxc/protocols/smb.py index b18ee7ab..e9d4172d 100755 --- a/nxc/protocols/smb.py +++ b/nxc/protocols/smb.py @@ -572,6 +572,9 @@ class smb(connection): self.admin_privs = True except scmr.DCERPCException: self.admin_privs = False + except Exception as e: + self.logger.fail(f"Error checking if user is admin on {self.host}: {e}") + self.admin_privs = False def gen_relay_list(self): if self.server_os.lower().find("windows") != -1 and self.signing is False: From 8bbab764c5b4b29e6d59f89184d5150663737160 Mon Sep 17 00:00:00 2001 From: Alexander Neff Date: Sun, 23 Jun 2024 18:18:39 -0400 Subject: [PATCH 17/17] Change execution to cmd and fix bugs --- nxc/modules/bitlocker.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/nxc/modules/bitlocker.py b/nxc/modules/bitlocker.py index b6796263..ef271183 100644 --- a/nxc/modules/bitlocker.py +++ b/nxc/modules/bitlocker.py @@ -39,18 +39,18 @@ class BitLockerSMB: def check_bitlocker_status(self): # PowerShell command to check BitLocker volumes status. - check_bitlocker_command_str = "Get-BitLockerVolume | Select-Object MountPoint, EncryptionMethod, ProtectionStatus" + check_bitlocker_command_str = 'powershell.exe "Get-BitLockerVolume | Select-Object MountPoint, EncryptionMethod, ProtectionStatus"' try: # Executing the PowerShell command to get BitLocker volumes status. - check_bitlocker_command_str_output = self.connection.ps_execute(check_bitlocker_command_str, True) + check_bitlocker_command_str_output = self.connection.execute(check_bitlocker_command_str, True) - if any("'Get-BitLockerVolume' is not recognized" in line for line in check_bitlocker_command_str_output): + if "'Get-BitLockerVolume' is not recognized" in check_bitlocker_command_str_output: self.context.log.fail("BitLockerVolume not found on target.") return # Splitting the output into lines. - lines = str(check_bitlocker_command_str_output).split("\\n") + lines = str(check_bitlocker_command_str_output).splitlines() data_lines = [line for line in lines if re.match(r"\w:", line)] for line in data_lines: