Skip to content

Commit 2ac0d4d

Browse files
committed
Merge "main" into "coerce_plus"
2 parents 4ac7748 + 83514bf commit 2ac0d4d

26 files changed

Lines changed: 1383 additions & 620 deletions

nxc/connection.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,8 @@ def get_host_addr_info(target, force_ipv6, dns_server, dns_tcp, dns_timeout):
8585
def requires_admin(func):
8686
def _decorator(self, *args, **kwargs):
8787
if self.admin_privs is False:
88+
if hasattr(self.args, "exec_method") and self.args.exec_method == "mmcexec":
89+
return func(self, *args, **kwargs)
8890
return None
8991
return func(self, *args, **kwargs)
9092

@@ -146,6 +148,7 @@ def __init__(self, args, db, target):
146148
self.kdcHost = self.args.kdcHost
147149
self.port = self.args.port
148150
self.local_ip = None
151+
self.dns_server = self.args.dns_server
149152

150153
# DNS resolution
151154
dns_result = self.resolver(target)
@@ -164,6 +167,9 @@ def __init__(self, args, db, target):
164167
except Exception as e:
165168
if "ERROR_DEPENDENT_SERVICES_RUNNING" in str(e):
166169
self.logger.error(f"Exception while calling proto_flow() on target {target}: {e}")
170+
# Catching impacket SMB specific exceptions, which should not be imported due to performance reasons
171+
elif e.__class__.__name__ in ["NetBIOSTimeout", "NetBIOSError"]:
172+
self.logger.error(f"{e.__class__.__name__} on target {target}: {e}")
167173
else:
168174
self.logger.exception(f"Exception while calling proto_flow() on target {target}: {e}")
169175
finally:
@@ -541,7 +547,7 @@ def login(self):
541547

542548
if hasattr(self.args, "laps") and self.args.laps:
543549
self.logger.debug("Trying to authenticate using LAPS")
544-
username[0], secret[0], domain[0], ntlm_hash = laps_search(self, username, secret, cred_type, domain)
550+
username[0], secret[0], domain[0] = laps_search(self, username, secret, cred_type, domain, self.dns_server)
545551
cred_type = ["plaintext"]
546552
if not (username[0] or secret[0] or domain[0]):
547553
return False

nxc/modules/add-computer.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,8 +102,9 @@ def do_samr_add(self, context):
102102
None
103103
"""
104104
string_binding = epm.hept_map(self.__host, samr.MSRPC_UUID_SAMR, protocol="ncacn_np")
105+
string_binding = string_binding.replace(self.__host, self.__kdcHost) if self.__kdcHost is not None else string_binding
105106

106-
rpc_transport = transport.DCERPCTransportFactory(string_binding.replace(self.__host, self.__kdcHost))
107+
rpc_transport = transport.DCERPCTransportFactory(string_binding)
107108
rpc_transport.setRemoteHost(self.__host)
108109

109110
if hasattr(rpc_transport, "set_credentials"):

nxc/modules/bitlocker.py

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
import re
2+
from impacket.dcerpc.v5.dcom import wmi
3+
from impacket.dcerpc.v5.dtypes import NULL
4+
from impacket.dcerpc.v5.dcomrt import DCOMConnection
5+
from impacket.dcerpc.v5.rpcrt import RPC_C_AUTHN_LEVEL_PKT_PRIVACY
6+
7+
class NXCModule:
8+
name = "bitlocker"
9+
description = "Enumerating BitLocker Status on target(s) If it is enabled or disabled."
10+
supported_protocols = ["smb", "wmi"]
11+
opsec_safe = True
12+
multiple_hosts = True
13+
14+
def __init__(self, context=None, module_options=None):
15+
self.context = context
16+
self.module_options = module_options
17+
18+
def options(self, context, module_options):
19+
"""
20+
USAGE:
21+
22+
NetExec smb <IP> -u <username> -p <password> -M bitlocker
23+
NetExec wmi <IP> -u <username> -p <password> -M bitlocker (Better option to use on real life.)
24+
"""
25+
26+
def on_admin_login(self, context, connection):
27+
if context.protocol == "smb":
28+
bitlocker_smb = BitLockerSMB(context, connection)
29+
bitlocker_smb.check_bitlocker_status()
30+
elif context.protocol == "wmi":
31+
bitlocker_wmi = BitLockerWMI(context, connection)
32+
bitlocker_wmi.check_bitlocker_status()
33+
34+
35+
class BitLockerSMB:
36+
def __init__(self, context, connection):
37+
self.context = context
38+
self.connection = connection
39+
40+
def check_bitlocker_status(self):
41+
# PowerShell command to check BitLocker volumes status.
42+
check_bitlocker_command_str = 'powershell.exe "Get-BitLockerVolume | Select-Object MountPoint, EncryptionMethod, ProtectionStatus"'
43+
44+
try:
45+
# Executing the PowerShell command to get BitLocker volumes status.
46+
check_bitlocker_command_str_output = self.connection.execute(check_bitlocker_command_str, True)
47+
48+
if "'Get-BitLockerVolume' is not recognized" in check_bitlocker_command_str_output:
49+
self.context.log.fail("BitLockerVolume not found on target.")
50+
return
51+
52+
# Splitting the output into lines.
53+
lines = str(check_bitlocker_command_str_output).splitlines()
54+
data_lines = [line for line in lines if re.match(r"\w:", line)]
55+
56+
for line in data_lines:
57+
# Checking every line for starting with drive
58+
if line[1] == ":":
59+
parts = line.split()
60+
MountPoint, EncryptionMethod, protection_status = parts[0], parts[1], parts[2]
61+
62+
# Checking if BitLocker is enabled.
63+
if protection_status == "On":
64+
self.context.log.highlight(f"BitLocker is enabled on drive {MountPoint} (Encryption Method: {EncryptionMethod})")
65+
else:
66+
self.context.log.highlight(f"BitLocker is disabled on drive {MountPoint}")
67+
except Exception as e:
68+
self.context.log.exception(f"Exception occurred: {e}")
69+
70+
71+
class BitLockerWMI:
72+
def __init__(self, context, connection):
73+
self.context = context
74+
self.connection = connection
75+
76+
def check_bitlocker_status(self):
77+
try:
78+
# Create a DCOM connection
79+
dcom_conn = DCOMConnection(
80+
self.connection.host,
81+
self.connection.username,
82+
self.connection.password,
83+
self.connection.domain,
84+
self.connection.lmhash,
85+
self.connection.nthash,
86+
oxidResolver=True,
87+
doKerberos=self.connection.kerberos,
88+
kdcHost=self.connection.kdcHost)
89+
90+
try:
91+
# CoCreateInstanceEx for WMI login
92+
i_interface = dcom_conn.CoCreateInstanceEx(wmi.CLSID_WbemLevel1Login, wmi.IID_IWbemLevel1Login)
93+
iWbemLevel1Login = wmi.IWbemLevel1Login(i_interface)
94+
95+
# Specify the namespace for BitLocker
96+
bitlockerNamespace = "root\\CIMv2\\Security\\MicrosoftVolumeEncryption"
97+
98+
# NTLM login for WMI
99+
iWbemServices = iWbemLevel1Login.NTLMLogin(bitlockerNamespace, NULL, NULL)
100+
101+
# Set authentication level
102+
iWbemServices.get_dce_rpc().set_auth_level(RPC_C_AUTHN_LEVEL_PKT_PRIVACY)
103+
104+
# Query to get BitLocker status
105+
classQuery = "SELECT DriveLetter, ProtectionStatus, EncryptionMethod FROM Win32_EncryptableVolume"
106+
iEnumWbemClassObject = iWbemServices.ExecQuery(classQuery)
107+
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"}
108+
109+
try:
110+
while True:
111+
iWbemClassObject = iEnumWbemClassObject.Next(0xffffffff, 1)
112+
encryptionMethod = int(iWbemClassObject[0].EncryptionMethod)
113+
if iWbemClassObject[0].ProtectionStatus == 1:
114+
self.context.log.highlight(f"BitLocker is enabled on drive {iWbemClassObject[0].DriveLetter} (Encryption Method: {encryptionTypeMapping.get(encryptionMethod, 'Unknown')})")
115+
else:
116+
if encryptionMethod == 0: # Should be 0 if disabled
117+
self.context.log.highlight(f"BitLocker is disabled on drive {iWbemClassObject[0].DriveLetter}")
118+
except Exception:
119+
pass # Using pass because if try to log or printing, getting "WMI Session Error: code: 0x1 - WBEM_S_FALSE"
120+
121+
# Release resources
122+
iWbemLevel1Login.RemRelease()
123+
iWbemServices.RemRelease()
124+
dcom_conn.disconnect()
125+
except Exception as e:
126+
if "WBEM_E_INVALID_NAMESPACE" in str(e):
127+
self.context.log.fail("BitLockerNamespace not found on target.")
128+
dcom_conn.disconnect()
129+
except Exception as e:
130+
self.context.log.error(f"Error occurred during BitLocker check: {e}")
131+
dcom_conn.disconnect()

nxc/modules/enum_av.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -358,6 +358,27 @@ def LsarLookupNames(self, dce, policyHandle, service):
358358
{"name": "sophoslivequery_*", "processes": [""]}
359359
]
360360
},
361+
{
362+
"name": "Trellix Endpoint Detection and Response (EDR)",
363+
"services": [
364+
{"name": "McAfee Endpoint Security Platform Service", "description": "Trellix Core Service"},
365+
{"name": "mfemactl", "description": "Trellix Management Service"},
366+
{"name": "mfemms", "description": "McAfee Management Service"},
367+
{"name": "mfefire", "description": "Trellix Firewall Core Service"},
368+
{"name": "masvc", "description": "Trellix Agent Service"},
369+
{"name": "macmnsvc", "description": "Trellix Agent Common Service"},
370+
{"name": "mfetp", "description": "Trellix Endpoint Threat Prevention Service"},
371+
{"name": "mfewc", "description": "Trellix Endpoint Security Web Control Service"},
372+
{"name": "mfeaack", "description": "Trellix Anti-Malware Core Service"}
373+
],
374+
"pipes": [
375+
{"name": "TrellixEDR_Pipe_*", "processes": ["McAfeeEDR.exe"]},
376+
{"name": "mfemactl_*", "processes": ["mfemactl.exe"]},
377+
{"name": "mfefire_*", "processes": ["mfefire.exe"]},
378+
{"name": "McAfeeAgent_Pipe_*", "processes": ["McAfeeAgent.exe"]},
379+
{"name": "mfetp_*", "processes": ["mfetp.exe"]}
380+
]
381+
},
361382
{
362383
"name": "Trend Micro Endpoint Security",
363384
"services": [

nxc/modules/handlekatz.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import sys
88

99
from nxc.helpers.bloodhound import add_user_bh
10-
import pypykatz
10+
from pypykatz.pypykatz import pypykatz
1111

1212

1313
class NXCModule:

nxc/modules/hyperv-host.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
from impacket.dcerpc.v5.rpcrt import DCERPCException
2+
from impacket.dcerpc.v5 import rrp
3+
from impacket.examples.secretsdump import RemoteOperations
4+
5+
6+
class NXCModule:
7+
"""Module by @joaovarelas"""
8+
9+
name = "hyperv-host"
10+
description = "Performs a registry query on the VM to lookup its HyperV Host"
11+
supported_protocols = ["smb"]
12+
opsec_safe = True
13+
multiple_hosts = True
14+
15+
def __init__(self, context=None, module_options=None):
16+
self.context = context
17+
self.module_options = module_options
18+
19+
def options(self, context, module_options):
20+
""""""
21+
22+
def on_admin_login(self, context, connection):
23+
self.context = context
24+
25+
path = "SOFTWARE\Microsoft\Virtual Machine\Guest\Parameters"
26+
key = "HostName"
27+
28+
try:
29+
remote_ops = RemoteOperations(connection.conn, False)
30+
remote_ops.enableRegistry()
31+
32+
ans = rrp.hOpenLocalMachine(remote_ops._RemoteOperations__rrp)
33+
reg_handle = ans["phKey"]
34+
35+
# Query
36+
try:
37+
ans = rrp.hBaseRegOpenKey(remote_ops._RemoteOperations__rrp, reg_handle, path)
38+
key_handle = ans["phkResult"]
39+
40+
data_type, reg_value = rrp.hBaseRegQueryValue(remote_ops._RemoteOperations__rrp, key_handle, key)
41+
self.context.log.highlight(f"{key}: {reg_value}")
42+
43+
rrp.hBaseRegCloseKey(remote_ops._RemoteOperations__rrp, key_handle)
44+
45+
except DCERPCException as e:
46+
self.context.log.debug(f"Registry key {path}\\{key} does not exist: {e}")
47+
48+
except DCERPCException as e:
49+
self.context.log.fail(f"DCERPC Error while querying registry: {e}")
50+
except Exception as e:
51+
self.context.log.fail(f"Error while querying registry: {e}")
52+
finally:
53+
remote_ops.finish()

nxc/modules/ioxidresolver.py

Lines changed: 26 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
# Credit to https://airbus-cyber-security.com/fr/the-oxid-resolver-part-1-remote-enumeration-of-network-interfaces-without-any-authentication/
22
# Airbus CERT
33
# module by @mpgn_x64
4+
# updated by @NeffIsBack
45

56
from ipaddress import ip_address
67
from impacket.dcerpc.v5 import transport
7-
from impacket.dcerpc.v5.rpcrt import RPC_C_AUTHN_LEVEL_NONE
8+
from impacket.dcerpc.v5.rpcrt import RPC_C_AUTHN_LEVEL_NONE, DCERPCException
89
from impacket.dcerpc.v5.dcomrt import IObjectExporter
910

1011

@@ -13,31 +14,31 @@ class NXCModule:
1314
description = "This module helps you to identify hosts that have additional active interfaces"
1415
supported_protocols = ["smb", "wmi"]
1516
opsec_safe = True
16-
multiple_hosts = False
17+
multiple_hosts = True
1718

1819
def options(self, context, module_options):
19-
""" """
20+
"""No module options"""
2021

2122
def on_login(self, context, connection):
22-
authLevel = RPC_C_AUTHN_LEVEL_NONE
23-
24-
stringBinding = r"ncacn_ip_tcp:%s" % connection.host
25-
rpctransport = transport.DCERPCTransportFactory(stringBinding)
26-
rpctransport.setRemoteHost(connection.host)
27-
28-
portmap = rpctransport.get_dce_rpc()
29-
portmap.set_auth_level(authLevel)
30-
portmap.connect()
31-
32-
objExporter = IObjectExporter(portmap)
33-
bindings = objExporter.ServerAlive2()
34-
35-
context.log.debug("[*] Retrieving network interface of " + connection.host)
36-
37-
for binding in bindings:
38-
NetworkAddr = binding["aNetworkAddr"]
39-
try:
40-
ip_address(NetworkAddr[:-1])
41-
context.log.highlight("Address: " + NetworkAddr)
42-
except Exception as e:
43-
context.log.debug(e)
23+
try:
24+
rpctransport = transport.DCERPCTransportFactory(f"ncacn_ip_tcp:{connection.host}")
25+
rpctransport.setRemoteHost(connection.host)
26+
27+
portmap = rpctransport.get_dce_rpc()
28+
portmap.set_auth_level(RPC_C_AUTHN_LEVEL_NONE)
29+
portmap.connect()
30+
31+
objExporter = IObjectExporter(portmap)
32+
bindings = objExporter.ServerAlive2()
33+
34+
context.log.debug(f"Retrieving network interface of {connection.host}")
35+
36+
for binding in bindings:
37+
NetworkAddr = binding["aNetworkAddr"]
38+
try:
39+
ip_address(NetworkAddr[:-1])
40+
context.log.highlight(f"Address: {NetworkAddr}")
41+
except Exception as e:
42+
context.log.debug(e)
43+
except DCERPCException as e:
44+
context.log.error(f"DCERPCException error: {e}")

nxc/modules/laps.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,12 +45,12 @@ def on_login(self, context, connection):
4545
values = {str(attr["type"]).lower(): attr["vals"][0] for attr in computer["attributes"]}
4646
if "mslaps-encryptedpassword" in values:
4747
msMCSAdmPwd = values["mslaps-encryptedpassword"]
48-
d = LAPSv2Extract(bytes(msMCSAdmPwd), connection.username if connection.username else "", connection.password if connection.password else "", connection.domain, connection.nthash if connection.nthash else "", connection.kerberos, connection.kdcHost, 339)
48+
d = LAPSv2Extract(bytes(msMCSAdmPwd), connection.username if connection.username else "", connection.password if connection.password else "", connection.domain, connection.nthash if connection.nthash else "", connection.kerberos, connection.kdcHost, 339, connection.dns_server)
4949
try:
5050
data = d.run()
5151
except Exception as e:
52-
self.logger.fail(str(e))
53-
return
52+
context.log.fail(str(e))
53+
continue
5454
r = json.loads(data)
5555
laps_computers.append((str(values["samaccountname"]), r["n"], str(r["p"])))
5656
elif "mslaps-password" in values:

0 commit comments

Comments
 (0)