Skip to content

Commit 6c482cd

Browse files
authored
Merge pull request Pennyw0rth#866 from rtpt-romankarwacik/simple_efs_activator
Remove efsr_spray module, superceded by simply using EPM map on the EFS interface
2 parents cc01381 + e428bcc commit 6c482cd

2 files changed

Lines changed: 18 additions & 117 deletions

File tree

nxc/modules/coerce_plus.py

Lines changed: 16 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
from impacket import uuid
21
from impacket.dcerpc.v5 import transport, rprn, even, epm
32
from impacket.dcerpc.v5.ndr import NDRCALL, NDRSTRUCT, NDRPOINTER, NDRUniConformantArray, NDRPOINTERNULL
43
from impacket.dcerpc.v5.dtypes import LPBYTE, USHORT, LPWSTR, DWORD, ULONG, NULL, WSTR, LONG, BOOL, PCHAR, RPC_SID
54
from impacket.dcerpc.v5.rpcrt import RPC_C_AUTHN_GSS_NEGOTIATE, RPC_C_AUTHN_LEVEL_PKT_PRIVACY
65

76
from impacket.uuid import uuidtup_to_bin
7+
import contextlib
88

99

1010
class NXCModule:
@@ -211,6 +211,15 @@ def on_login(self, context, connection):
211211
context.log.error("Invalid method, please check the method name.")
212212
return
213213

214+
@staticmethod
215+
def get_dynamic_endpoint(interface: bytes, target: str, timeout: int = 5) -> str:
216+
string_binding = rf"ncacn_ip_tcp:{target}[135]"
217+
rpctransport = transport.DCERPCTransportFactory(string_binding)
218+
rpctransport.set_connect_timeout(timeout)
219+
dce = rpctransport.get_dce_rpc()
220+
dce.connect()
221+
return epm.hept_map(target, interface, protocol="ncacn_ip_tcp", dce=dce)
222+
214223

215224
class ShadowCoerceTrigger:
216225
def __init__(self, context):
@@ -528,6 +537,11 @@ def connect(self, username, password, domain, lmhash, nthash, aesKey, target, do
528537
},
529538
}
530539

540+
# activates EFS
541+
# https://specterops.io/blog/2025/08/19/will-webclient-start/
542+
with contextlib.suppress(Exception):
543+
NXCModule.get_dynamic_endpoint(uuidtup_to_bin(("df1941c5-fe89-4e79-bf10-463657acf44d", "0.0")), target, timeout=1)
544+
531545
rpctransport = transport.DCERPCTransportFactory(binding_params[pipe]["stringBinding"])
532546
rpctransport.set_dport(445)
533547

@@ -755,27 +769,6 @@ class PrinterBugTrigger:
755769
def __init__(self, context):
756770
self.context = context
757771

758-
def get_dynamic_endpoint(self, interface: bytes, target: str, timeout: int = 5) -> str:
759-
string_binding = rf"ncacn_ip_tcp:{target}[135]"
760-
rpctransport = transport.DCERPCTransportFactory(string_binding)
761-
rpctransport.set_connect_timeout(timeout)
762-
dce = rpctransport.get_dce_rpc()
763-
self.context.log.debug(f"Trying to resolve dynamic endpoint {uuid.bin_to_string(interface)!r}")
764-
try:
765-
dce.connect()
766-
except Exception as e:
767-
self.context.log.warning(f"Failed to connect to endpoint mapper: {e}")
768-
raise e
769-
try:
770-
endpoint = epm.hept_map(target, interface, protocol="ncacn_ip_tcp", dce=dce)
771-
self.context.log.debug(
772-
f"Resolved dynamic endpoint {uuid.bin_to_string(interface)!r} to {endpoint!r}"
773-
)
774-
return endpoint
775-
except Exception as e:
776-
self.context.log.debug(f"Failed to resolve dynamic endpoint {uuid.bin_to_string(interface)!r}")
777-
raise e
778-
779772
def connect(self, username, password, domain, lmhash, nthash, aesKey, target, doKerberos, dcHost, pipe):
780773
binding_params = {
781774
"spoolss": {
@@ -784,7 +777,7 @@ def connect(self, username, password, domain, lmhash, nthash, aesKey, target, do
784777
"port": 445
785778
},
786779
"[dcerpc]": {
787-
"stringBinding": self.get_dynamic_endpoint(uuidtup_to_bin(("12345678-1234-abcd-ef00-0123456789ab", "1.0")), target),
780+
"stringBinding": NXCModule.get_dynamic_endpoint(uuidtup_to_bin(("12345678-1234-abcd-ef00-0123456789ab", "1.0")), target),
788781
"MSRPC_UUID_RPRN": ("12345678-1234-abcd-ef00-0123456789ab", "1.0"),
789782
"port": None
790783
}

nxc/modules/efsr_spray.py

Lines changed: 2 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,9 @@
1-
import ntpath
2-
from nxc.helpers.misc import gen_random_string
31
from nxc.context import Context
4-
from impacket.smb3structs import FILE_SHARE_WRITE, FILE_SHARE_DELETE, FILE_ATTRIBUTE_ENCRYPTED
5-
from impacket.smbconnection import SessionError, SMBConnection
6-
7-
8-
def get_error_string(exception):
9-
if hasattr(exception, "getErrorString"):
10-
try:
11-
es = exception.getErrorString()
12-
except KeyError:
13-
return f"Could not get nt error code {exception.getErrorCode()} from impacket: {exception}"
14-
if type(es) is tuple:
15-
return es[0]
16-
else:
17-
return es
18-
else:
19-
return str(exception)
202

213

224
class NXCModule:
23-
"""EFSR Spray Module
24-
Module by @rtpt-romankarwacik
25-
"""
26-
275
name = "efsr_spray"
28-
description = "Tries to activate the EFSR service by creating a file with the encryption attribute on some available share."
6+
description = "[REMOVED] Tries to activate the EFSR service by creating a file with the encryption attribute on some available share."
297
supported_protocols = ["smb"]
308
excluded_shares = ["SYSVOL"]
319

@@ -35,76 +13,6 @@ def options(self, context: Context, module_options: dict[str, str]):
3513
SHARE_NAME If set, ONLY this share will be used
3614
EXCLUDED_SHARES List of share names which will not be used, seperated by comma
3715
"""
38-
self.file_name = module_options.get("FILE_NAME", ntpath.normpath("\\" + gen_random_string() + ".txt"))
39-
self.share_name = module_options.get("SHARE_NAME")
40-
if module_options.get("EXCLUDED_SHARES"):
41-
self.excluded_shares += module_options.get("EXCLUDED_SHARES", "").split(",")
4216

4317
def on_login(self, context: Context, connection):
44-
conn: SMBConnection = connection.conn # Because typing is broken due to smb being a folder and a file >:(
45-
46-
try:
47-
shares = conn.listShares()
48-
except SessionError as e:
49-
error = get_error_string(e)
50-
context.log.fail(f"Error enumerating shares: {error}", color="magenta")
51-
return
52-
53-
# Check if named pipe is already available
54-
try:
55-
named_pipe_names = [f.get_shortname() for f in conn.listPath("IPC$", "*")]
56-
if "efsrpc" in named_pipe_names:
57-
context.log.highlight("efsrpc named pipe is already available!")
58-
# if it is already activated we just skip this computer
59-
return
60-
except SessionError as e:
61-
error = get_error_string(e)
62-
context.log.fail(f"Error enumerating named pipes: {error}", color="magenta")
63-
return
64-
65-
# Write an encrypted file on the share root.
66-
# This will likely fail with STATUS_ACCESS_DENIED if we do not have the permission to create encrypted files,
67-
# but this does not matter as the service will be activated nevertheless if we have WRITE or MODIFY access
68-
for share in shares:
69-
share_name = share["shi1_netname"][:-1]
70-
if self.share_name is not None and self.share_name != share_name:
71-
continue
72-
73-
if share_name in self.excluded_shares:
74-
continue
75-
76-
try:
77-
context.log.debug(f"Connecting to share {share_name}...")
78-
tid = conn.connectTree(share_name)
79-
except SessionError as e:
80-
context.log.debug(f"Could not connect to share {share_name}: {e}")
81-
continue
82-
try:
83-
context.log.debug(f"Creating file in {share_name}...")
84-
fid = conn.createFile(tid, self.file_name,
85-
desiredAccess=FILE_SHARE_WRITE,
86-
shareMode=FILE_SHARE_DELETE,
87-
fileAttributes=FILE_ATTRIBUTE_ENCRYPTED)
88-
conn.closeFile(tid, fid)
89-
try:
90-
# this can happen when we have special permissions to create encrypted files
91-
conn.deleteFile(share_name, self.file_name)
92-
except SessionError as e:
93-
error = get_error_string(e)
94-
if error == "STATUS_OBJECT_NAME_NOT_FOUND":
95-
pass
96-
context.log.fail(f"Error DELETING created temp file {self.file_name} on share {share_name}: {error}")
97-
except SessionError as e:
98-
context.log.debug(f"Error writing encrypted file on share {share_name}: {get_error_string(e)} (This does not necessarily mean that the attack failed!)")
99-
100-
try:
101-
tid = conn.connectTree("IPC$")
102-
conn.waitNamedPipe(tid, "efsrpc", 10)
103-
context.log.highlight("Successfully activated efsrpc named pipe!")
104-
except SessionError as e:
105-
error = get_error_string(e)
106-
if error == "STATUS_OBJECT_NAME_NOT_FOUND":
107-
context.log.debug("efsrpc pipe was not activated.")
108-
else:
109-
context.log.fail(f"Error waiting for named pipe: {error}", color="magenta")
110-
return
18+
context.log.fail('[REMOVED] This module has been made obsolete and EFS will be activated automatically by "coerce_plus"')

0 commit comments

Comments
 (0)