Skip to content

Commit c24c299

Browse files
authored
Merge pull request Pennyw0rth#918 from azoxlpf/fix/ntlm_reflection-transport-error
catch BrokenPipe and transport errors to prevent session crash
2 parents 9ab9989 + 502236f commit c24c299

4 files changed

Lines changed: 54 additions & 81 deletions

File tree

nxc/modules/backup_operator.py

Lines changed: 9 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import time
1+
import contextlib
22
import os
33
import datetime
44

@@ -30,18 +30,19 @@ def on_login(self, context, connection):
3030
connection.args.share = "SYSVOL"
3131
# enable remote registry
3232
context.log.display("Triggering RemoteRegistry to start through named pipe...")
33-
self.trigger_winreg(connection.conn, context)
33+
connection.trigger_winreg()
3434
rpc = transport.DCERPCTransportFactory(r"ncacn_np:445[\pipe\winreg]")
3535
rpc.set_smb_connection(connection.conn)
3636
if connection.kerberos:
3737
rpc.set_kerberos(connection.kerberos, kdcHost=connection.kdcHost)
3838
dce = rpc.get_dce_rpc()
3939
if connection.kerberos:
4040
dce.set_auth_type(RPC_C_AUTHN_GSS_NEGOTIATE)
41-
dce.connect()
42-
dce.bind(rrp.MSRPC_UUID_RRP)
4341

4442
try:
43+
dce.connect()
44+
dce.bind(rrp.MSRPC_UUID_RRP)
45+
4546
for hive in ["HKLM\\SAM", "HKLM\\SYSTEM", "HKLM\\SECURITY"]:
4647
hRootKey, subKey = self._strip_root_key(dce, hive)
4748
outputFileName = f"\\\\{connection.host}\\SYSVOL\\{subKey}"
@@ -54,9 +55,11 @@ def on_login(self, context, connection):
5455
context.log.fail(f"Couldn't save {hive}: {e} on path {outputFileName}")
5556
return
5657
except (Exception, KeyboardInterrupt) as e:
57-
context.log.fail(str(e))
58+
context.log.fail(f"Unexpected error: {e}")
59+
return
5860
finally:
59-
dce.disconnect()
61+
with contextlib.suppress(Exception):
62+
dce.disconnect()
6063

6164
# copy remote file to local
6265
log_path = os.path.expanduser(f"{NXC_PATH}/logs/{connection.hostname}_{connection.host}_{datetime.datetime.now().strftime('%Y-%m-%d_%H%M%S')}.".replace(":", "-"))
@@ -115,29 +118,3 @@ def parse_sam(secret):
115118
context.log.display("netexec smb dc_ip -u user -p pass -x \"del C:\\Windows\\sysvol\\sysvol\\SECURITY && del C:\\Windows\\sysvol\\sysvol\\SAM && del C:\\Windows\\sysvol\\sysvol\\SYSTEM\"") # noqa: Q003
116119
else:
117120
context.log.display("Successfully deleted dump files !")
118-
119-
def trigger_winreg(self, connection, context):
120-
# Original idea from https://twitter.com/splinter_code/status/1715876413474025704
121-
# Basically triggers the RemoteRegistry to start without admin privs
122-
tid = connection.connectTree("IPC$")
123-
try:
124-
connection.openFile(
125-
tid,
126-
r"\winreg",
127-
0x12019F,
128-
creationOption=0x40,
129-
fileAttributes=0x80,
130-
)
131-
except SessionError as e:
132-
# STATUS_PIPE_NOT_AVAILABLE error is expected
133-
context.log.debug(str(e))
134-
# Give remote registry time to start
135-
time.sleep(1)
136-
137-
def _strip_root_key(self, dce, key_name):
138-
# Let's strip the root key
139-
key_name.split("\\")[0]
140-
sub_key = "\\".join(key_name.split("\\")[1:])
141-
ans = rrp.hOpenLocalMachine(dce)
142-
h_root_key = ans["phKey"]
143-
return h_root_key, sub_key

nxc/modules/ntlm_reflection.py

Lines changed: 5 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
1-
import time
21
from impacket.dcerpc.v5 import transport, rrp
32
from impacket.dcerpc.v5.rpcrt import RPC_C_AUTHN_GSS_NEGOTIATE
43
from impacket.smbconnection import SessionError
54
from nxc.helpers.misc import CATEGORY
5+
from impacket.nmb import NetBIOSError
66

77

88
class NXCModule:
99
# https://www.synacktiv.com/en/publications/ntlm-reflection-is-dead-long-live-ntlm-reflection-an-in-depth-analysis-of-cve-2025
10+
# Modified by azoxlpf to handle BrokenPipe/transport errors gracefully
1011
name = "ntlm_reflection"
1112
description = "Attempt to check if the OS is vulnerable to CVE-2025-33073 (NTLM Reflection attack)"
1213
supported_protocols = ["smb"]
@@ -48,7 +49,7 @@ def on_login(self, context, connection):
4849
self.context = context
4950
self.connection = connection
5051
if not connection.conn.isSigningRequired(): # Not vulnerable if SMB signing is enabled
51-
self.trigger_winreg(connection.conn, context)
52+
connection.trigger_winreg()
5253
rpc = transport.DCERPCTransportFactory(r"ncacn_np:445[\pipe\winreg]")
5354
rpc.set_smb_connection(connection.conn)
5455
if connection.kerberos:
@@ -76,21 +77,5 @@ def on_login(self, context, connection):
7677
self.context.log.info(f"RemoteRegistry is probably deactivated: {e}")
7778
else:
7879
self.context.log.debug(f"Unexpected error: {e}")
79-
80-
def trigger_winreg(self, connection, context):
81-
# Original idea from https://twitter.com/splinter_code/status/1715876413474025704
82-
# Basically triggers the RemoteRegistry to start without admin privs
83-
tid = connection.connectTree("IPC$")
84-
try:
85-
connection.openFile(
86-
tid,
87-
r"\winreg",
88-
0x12019F,
89-
creationOption=0x40,
90-
fileAttributes=0x80,
91-
)
92-
except SessionError as e:
93-
# STATUS_PIPE_NOT_AVAILABLE error is expected
94-
context.log.debug(str(e))
95-
# Give remote registry time to start
96-
time.sleep(1)
80+
except (BrokenPipeError, ConnectionResetError, NetBIOSError, OSError) as e:
81+
context.log.debug(f"ntlm_reflection: DCERPC transport error: {e.__class__.__name__}: {e}")

nxc/modules/sccm-recon6.py

Lines changed: 14 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11

2-
import time
2+
import contextlib
33
from impacket.dcerpc.v5 import transport, rrp
44
from impacket.dcerpc.v5.rpcrt import RPC_C_AUTHN_GSS_NEGOTIATE
55
from impacket.dcerpc.v5.rpcrt import DCERPCException
6-
from impacket.smbconnection import SessionError
76
from nxc.helpers.misc import CATEGORY
87
from impacket.smbconnection import SMBConnection
98

@@ -23,7 +22,7 @@ def on_login(self, context, connection):
2322
self.context = context
2423
self.connection = connection
2524

26-
self.trigger_winreg(connection.conn, context)
25+
connection.trigger_winreg()
2726

2827
rpc = transport.DCERPCTransportFactory(r"ncacn_np:445[\pipe\winreg]")
2928
rpc.set_smb_connection(connection.conn)
@@ -33,14 +32,15 @@ def on_login(self, context, connection):
3332
dce = rpc.get_dce_rpc()
3433
if connection.kerberos:
3534
dce.set_auth_type(RPC_C_AUTHN_GSS_NEGOTIATE)
36-
dce.connect()
37-
dce.bind(rrp.MSRPC_UUID_RRP)
38-
39-
# Open HKEY_LOCAL_MACHINE
40-
ans = rrp.hOpenLocalMachine(dce)
41-
hRootKey = ans["phKey"]
4235

4336
try:
37+
dce.connect()
38+
dce.bind(rrp.MSRPC_UUID_RRP)
39+
40+
# Open HKEY_LOCAL_MACHINE
41+
ans = rrp.hOpenLocalMachine(dce)
42+
hRootKey = ans["phKey"]
43+
4444
self.EnumerateSS(dce, hRootKey)
4545
try:
4646
self.EnumerateDB(dce, hRootKey)
@@ -51,8 +51,11 @@ def on_login(self, context, connection):
5151
self.context.log.info(f"Probably not a primary site server or a distribution point: {e}")
5252
else:
5353
self.context.log.fail(f"Unexpected error: {e}")
54-
55-
dce.disconnect()
54+
except Exception as e:
55+
self.context.log.fail(f"Unexpected error: {e}")
56+
finally:
57+
with contextlib.suppress(Exception):
58+
dce.disconnect()
5659

5760
def EnumerateSS(self, dce, hRootKey):
5861
# Open the target registry key
@@ -130,21 +133,3 @@ def EnumerateDB(self, dce, hRootKey):
130133
self.context.log.display(f" SMB signing: {new_conn.isSigningRequired()}")
131134
else:
132135
self.context.log.highlight(f" SMB signing: {new_conn.isSigningRequired()} - TAKEOVER-2")
133-
134-
def trigger_winreg(self, connection, context):
135-
# Original idea from https://twitter.com/splinter_code/status/1715876413474025704
136-
# Basically triggers the RemoteRegistry to start without admin privs
137-
tid = connection.connectTree("IPC$")
138-
try:
139-
connection.openFile(
140-
tid,
141-
r"\winreg",
142-
0x12019F,
143-
creationOption=0x40,
144-
fileAttributes=0x80,
145-
)
146-
except SessionError as e:
147-
# STATUS_PIPE_NOT_AVAILABLE error is expected
148-
context.log.debug(str(e))
149-
# Give remote registry time to start
150-
time.sleep(1)

nxc/protocols/smb.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -735,6 +735,32 @@ def _is_port_open(self, port, timeout=1):
735735
self.logger.debug(f"Error checking port {port} on {self.host}: {e}")
736736
return False
737737

738+
def trigger_winreg(self):
739+
# Original idea from https://twitter.com/splinter_code/status/1715876413474025704
740+
# Basically triggers the RemoteRegistry to start without admin privs
741+
try:
742+
tid = self.conn.connectTree("IPC$")
743+
try:
744+
self.conn.openFile(
745+
tid,
746+
r"\winreg",
747+
0x12019F,
748+
creationOption=0x40,
749+
fileAttributes=0x80,
750+
)
751+
except SessionError as e:
752+
# STATUS_PIPE_NOT_AVAILABLE error is expected
753+
if "STATUS_PIPE_NOT_AVAILABLE" not in str(e):
754+
raise
755+
else:
756+
self.logger.debug(f"Received expected error while triggering winreg: {e}")
757+
# Give remote registry time to start
758+
sleep(1)
759+
return True
760+
except (SessionError, BrokenPipeError, ConnectionResetError, NetBIOSError, OSError) as e:
761+
self.logger.debug(f"Received unexpected error while triggering winreg: {e}")
762+
return False
763+
738764
@requires_admin
739765
def execute(self, payload=None, get_output=False, methods=None) -> str:
740766
"""

0 commit comments

Comments
 (0)