Hide keyboard shortcuts

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#!/usr/bin/env python 

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

3# 

4# SECUREAUTH LABS. Copyright (C) 2021 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# SOCKS proxy server/client 

12# 

13# A simple SOCKS server that proxy connection to relayed connections 

14# 

15# Author: 

16# Alberto Solino (@agsolino) 

17# 

18# ToDo: 

19# [ ] Handle better the SOCKS specification (RFC1928), e.g. BIND 

20# [ ] Port handlers should be dynamically subscribed, and coded in another place. This will help coding 

21# proxies for different protocols (e.g. MSSQL) 

22# 

23from __future__ import division 

24from __future__ import print_function 

25import socketserver 

26import socket 

27import time 

28import logging 

29from queue import Queue 

30from struct import unpack, pack 

31from threading import Timer, Thread 

32 

33from impacket import LOG 

34from impacket.dcerpc.v5.enum import Enum 

35from impacket.structure import Structure 

36 

37# Amount of seconds each socks plugin keep alive function will be called 

38# It is up to each plugin to send the keep alive to the target or not in every hit. 

39# In some cases (e.g. SMB) it is not needed to send a keep alive every 30 secs. 

40KEEP_ALIVE_TIMER = 30.0 

41 

42class enumItems(Enum): 

43 NO_AUTHENTICATION = 0 

44 GSSAPI = 1 

45 USER_PASS = 2 

46 UNACCEPTABLE = 0xFF 

47 

48class replyField(Enum): 

49 SUCCEEDED = 0 

50 SOCKS_FAILURE = 1 

51 NOT_ALLOWED = 2 

52 NETWORK_UNREACHABLE = 3 

53 HOST_UNREACHABLE = 4 

54 CONNECTION_REFUSED = 5 

55 TTL_EXPIRED = 6 

56 COMMAND_NOT_SUPPORTED = 7 

57 ADDRESS_NOT_SUPPORTED = 8 

58 

59class ATYP(Enum): 

60 IPv4 = 1 

61 DOMAINNAME = 3 

62 IPv6 = 4 

63 

64class SOCKS5_GREETINGS(Structure): 

65 structure = ( 

66 ('VER','B=5'), 

67 #('NMETHODS','B=0'), 

68 ('METHODS','B*B'), 

69 ) 

70 

71 

72class SOCKS5_GREETINGS_BACK(Structure): 

73 structure = ( 

74 ('VER','B=5'), 

75 ('METHODS','B=0'), 

76 ) 

77 

78class SOCKS5_REQUEST(Structure): 

79 structure = ( 

80 ('VER','B=5'), 

81 ('CMD','B=0'), 

82 ('RSV','B=0'), 

83 ('ATYP','B=0'), 

84 ('PAYLOAD',':'), 

85 ) 

86 

87class SOCKS5_REPLY(Structure): 

88 structure = ( 

89 ('VER','B=5'), 

90 ('REP','B=5'), 

91 ('RSV','B=0'), 

92 ('ATYP','B=1'), 

93 ('PAYLOAD',':="AAAAA"'), 

94 ) 

95 

96class SOCKS4_REQUEST(Structure): 

97 structure = ( 

98 ('VER','B=4'), 

99 ('CMD','B=0'), 

100 ('PORT','>H=0'), 

101 ('ADDR','4s="'), 

102 ('PAYLOAD',':'), 

103 ) 

104 

105class SOCKS4_REPLY(Structure): 

106 structure = ( 

107 ('VER','B=0'), 

108 ('REP','B=0x5A'), 

109 ('RSV','<H=0'), 

110 ('RSV','<L=0'), 

111 ) 

112 

113activeConnections = Queue() 

114 

115# Taken from https://stackoverflow.com/questions/474528/what-is-the-best-way-to-repeatedly-execute-a-function-every-x-seconds-in-python 

116# Thanks https://stackoverflow.com/users/624066/mestrelion 

117class RepeatedTimer(object): 

118 def __init__(self, interval, function, *args, **kwargs): 

119 self._timer = None 

120 self.interval = interval 

121 self.function = function 

122 self.args = args 

123 self.kwargs = kwargs 

124 self.is_running = False 

125 self.next_call = time.time() 

126 self.start() 

127 

128 def _run(self): 

129 self.is_running = False 

130 self.start() 

131 self.function(*self.args, **self.kwargs) 

132 

133 def start(self): 

134 if not self.is_running: 

135 self.next_call += self.interval 

136 self._timer = Timer(self.next_call - time.time(), self._run) 

137 self._timer.start() 

138 self.is_running = True 

139 

140 def stop(self): 

141 self._timer.cancel() 

142 self.is_running = False 

143 

144# Base class for Relay Socks Servers for different protocols (SMB, MSSQL, etc) 

145# Besides using this base class you need to define one global variable when 

146# writing a plugin for socksplugins: 

147# PLUGIN_CLASS = "<name of the class for the plugin>" 

148class SocksRelay: 

149 PLUGIN_NAME = 'Base Plugin' 

150 # The plugin scheme, for automatic registration with relay servers 

151 # Should be specified in full caps, e.g. LDAP, HTTPS 

152 PLUGIN_SCHEME = '' 

153 

154 def __init__(self, targetHost, targetPort, socksSocket, activeRelays): 

155 self.targetHost = targetHost 

156 self.targetPort = targetPort 

157 self.socksSocket = socksSocket 

158 self.sessionData = activeRelays['data'] 

159 self.username = None 

160 self.clientConnection = None 

161 self.activeRelays = activeRelays 

162 

163 def initConnection(self): 

164 # Here we do whatever is necessary to leave the relay ready for processing incoming connections 

165 raise RuntimeError('Virtual Function') 

166 

167 def skipAuthentication(self): 

168 # Charged of bypassing any authentication attempt from the client 

169 raise RuntimeError('Virtual Function') 

170 

171 def tunnelConnection(self): 

172 # Charged of tunneling the rest of the connection 

173 raise RuntimeError('Virtual Function') 

174 

175 @staticmethod 

176 def getProtocolPort(self): 

177 # Should return the port this relay works against 

178 raise RuntimeError('Virtual Function') 

179 

180 

181def keepAliveTimer(server): 

182 LOG.debug('KeepAlive Timer reached. Updating connections') 

183 

184 for target in list(server.activeRelays.keys()): 

185 for port in list(server.activeRelays[target].keys()): 

186 # Now cycle through the users 

187 for user in list(server.activeRelays[target][port].keys()): 

188 if user != 'data' and user != 'scheme': 

189 # Let's call the keepAlive method for the handler to keep the connection alive 

190 if server.activeRelays[target][port][user]['inUse'] is False: 

191 LOG.debug('Calling keepAlive() for %s@%s:%s' % (user, target, port)) 

192 try: 

193 server.activeRelays[target][port][user]['protocolClient'].keepAlive() 

194 except Exception as e: 

195 LOG.debug("Exception:",exc_info=True) 

196 LOG.debug('SOCKS: %s' % str(e)) 

197 if str(e).find('Broken pipe') >= 0 or str(e).find('reset by peer') >=0 or \ 

198 str(e).find('Invalid argument') >= 0 or str(e).find('Server not connected') >=0: 

199 # Connection died, taking out of the active list 

200 del (server.activeRelays[target][port][user]) 

201 if len(list(server.activeRelays[target][port].keys())) == 1: 

202 del (server.activeRelays[target][port]) 

203 LOG.debug('Removing active relay for %s@%s:%s' % (user, target, port)) 

204 else: 

205 LOG.debug('Skipping %s@%s:%s since it\'s being used at the moment' % (user, target, port)) 

206 

207def activeConnectionsWatcher(server): 

208 while True: 

209 # This call blocks until there is data, so it doesn't loop endlessly 

210 target, port, scheme, userName, client, data = activeConnections.get() 

211 # ToDo: Careful. Dicts are not thread safe right? 

212 if (target in server.activeRelays) is not True: 

213 server.activeRelays[target] = {} 

214 if (port in server.activeRelays[target]) is not True: 

215 server.activeRelays[target][port] = {} 

216 

217 if (userName in server.activeRelays[target][port]) is not True: 

218 LOG.info('SOCKS: Adding %s@%s(%s) to active SOCKS connection. Enjoy' % (userName, target, port)) 

219 server.activeRelays[target][port][userName] = {} 

220 # This is the protocolClient. Needed because we need to access the killConnection from time to time. 

221 # Inside this instance, you have the session attribute pointing to the relayed session. 

222 server.activeRelays[target][port][userName]['protocolClient'] = client 

223 server.activeRelays[target][port][userName]['inUse'] = False 

224 server.activeRelays[target][port][userName]['data'] = data 

225 # Just for the CHALLENGE data, we're storing this general 

226 server.activeRelays[target][port]['data'] = data 

227 # Let's store the protocol scheme, needed be used later when trying to find the right socks relay server to use 

228 server.activeRelays[target][port]['scheme'] = scheme 

229 

230 # Default values in case somebody asks while we're getting the data 

231 server.activeRelays[target][port][userName]['isAdmin'] = 'N/A' 

232 # Do we have admin access in this connection? 

233 try: 

234 LOG.debug("Checking admin status for user %s" % str(userName)) 

235 isAdmin = client.isAdmin() 

236 server.activeRelays[target][port][userName]['isAdmin'] = isAdmin 

237 except Exception as e: 

238 # Method not implemented 

239 server.activeRelays[target][port][userName]['isAdmin'] = 'N/A' 

240 pass 

241 LOG.debug("isAdmin returned: %s" % server.activeRelays[target][port][userName]['isAdmin']) 

242 else: 

243 LOG.info('Relay connection for %s at %s(%d) already exists. Discarding' % (userName, target, port)) 

244 client.killConnection() 

245 

246def webService(server): 

247 from flask import Flask, jsonify 

248 

249 app = Flask(__name__) 

250 

251 log = logging.getLogger('werkzeug') 

252 log.setLevel(logging.ERROR) 

253 

254 @app.route('/') 

255 def index(): 

256 print(server.activeRelays) 

257 return "Relays available: %s!" % (len(server.activeRelays)) 

258 

259 @app.route('/ntlmrelayx/api/v1.0/relays', methods=['GET']) 

260 def get_relays(): 

261 relays = [] 

262 for target in server.activeRelays: 

263 for port in server.activeRelays[target]: 

264 for user in server.activeRelays[target][port]: 

265 if user != 'data' and user != 'scheme': 

266 protocol = server.activeRelays[target][port]['scheme'] 

267 isAdmin = server.activeRelays[target][port][user]['isAdmin'] 

268 relays.append([protocol, target, user, isAdmin, str(port)]) 

269 return jsonify(relays) 

270 

271 @app.route('/ntlmrelayx/api/v1.0/relays', methods=['GET']) 

272 def get_info(relay): 

273 pass 

274 

275 app.run(host='0.0.0.0', port=9090) 

276 

277class SocksRequestHandler(socketserver.BaseRequestHandler): 

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

279 self.__socksServer = server 

280 self.__ip, self.__port = client_address 

281 self.__connSocket= request 

282 self.__socksVersion = 5 

283 self.targetHost = None 

284 self.targetPort = None 

285 self.__NBSession= None 

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

287 

288 def sendReplyError(self, error = replyField.CONNECTION_REFUSED): 

289 

290 if self.__socksVersion == 5: 

291 reply = SOCKS5_REPLY() 

292 reply['REP'] = error.value 

293 else: 

294 reply = SOCKS4_REPLY() 

295 if error.value != 0: 

296 reply['REP'] = 0x5B 

297 return self.__connSocket.sendall(reply.getData()) 

298 

299 def handle(self): 

300 LOG.debug("SOCKS: New Connection from %s(%s)" % (self.__ip, self.__port)) 

301 

302 data = self.__connSocket.recv(8192) 

303 grettings = SOCKS5_GREETINGS_BACK(data) 

304 self.__socksVersion = grettings['VER'] 

305 

306 if self.__socksVersion == 5: 

307 # We need to answer back with a no authentication response. We're not dealing with auth for now 

308 self.__connSocket.sendall(SOCKS5_GREETINGS_BACK().getData()) 

309 data = self.__connSocket.recv(8192) 

310 request = SOCKS5_REQUEST(data) 

311 else: 

312 # We're in version 4, we just received the request 

313 request = SOCKS4_REQUEST(data) 

314 

315 # Let's process the request to extract the target to connect. 

316 # SOCKS5 

317 if self.__socksVersion == 5: 

318 if request['ATYP'] == ATYP.IPv4.value: 

319 self.targetHost = socket.inet_ntoa(request['PAYLOAD'][:4]) 

320 self.targetPort = unpack('>H',request['PAYLOAD'][4:])[0] 

321 elif request['ATYP'] == ATYP.DOMAINNAME.value: 

322 hostLength = unpack('!B',request['PAYLOAD'][0])[0] 

323 self.targetHost = request['PAYLOAD'][1:hostLength+1] 

324 self.targetPort = unpack('>H',request['PAYLOAD'][hostLength+1:])[0] 

325 else: 

326 LOG.error('No support for IPv6 yet!') 

327 # SOCKS4 

328 else: 

329 self.targetPort = request['PORT'] 

330 

331 # SOCKS4a 

332 if request['ADDR'][:3] == "\x00\x00\x00" and request['ADDR'][3] != "\x00": 

333 nullBytePos = request['PAYLOAD'].find("\x00") 

334 

335 if nullBytePos == -1: 

336 LOG.error('Error while reading SOCKS4a header!') 

337 else: 

338 self.targetHost = request['PAYLOAD'].split('\0', 1)[1][:-1] 

339 else: 

340 self.targetHost = socket.inet_ntoa(request['ADDR']) 

341 

342 LOG.debug('SOCKS: Target is %s(%s)' % (self.targetHost, self.targetPort)) 

343 

344 if self.targetPort != 53: 

345 # Do we have an active connection for the target host/port asked? 

346 # Still don't know the username, but it's a start 

347 if self.targetHost in self.__socksServer.activeRelays: 

348 if (self.targetPort in self.__socksServer.activeRelays[self.targetHost]) is not True: 

349 LOG.error('SOCKS: Don\'t have a relay for %s(%s)' % (self.targetHost, self.targetPort)) 

350 self.sendReplyError(replyField.CONNECTION_REFUSED) 

351 return 

352 else: 

353 LOG.error('SOCKS: Don\'t have a relay for %s(%s)' % (self.targetHost, self.targetPort)) 

354 self.sendReplyError(replyField.CONNECTION_REFUSED) 

355 return 

356 

357 # Now let's get into the loops 

358 if self.targetPort == 53: 

359 # Somebody wanting a DNS request. Should we handle this? 

360 s = socket.socket() 

361 try: 

362 LOG.debug('SOCKS: Connecting to %s(%s)' %(self.targetHost, self.targetPort)) 

363 s.connect((self.targetHost, self.targetPort)) 

364 except Exception as e: 

365 LOG.debug("Exception:", exc_info=True) 

366 LOG.error('SOCKS: %s' %str(e)) 

367 self.sendReplyError(replyField.CONNECTION_REFUSED) 

368 return 

369 

370 if self.__socksVersion == 5: 

371 reply = SOCKS5_REPLY() 

372 reply['REP'] = replyField.SUCCEEDED.value 

373 addr, port = s.getsockname() 

374 reply['PAYLOAD'] = socket.inet_aton(addr) + pack('>H', port) 

375 else: 

376 reply = SOCKS4_REPLY() 

377 

378 self.__connSocket.sendall(reply.getData()) 

379 

380 while True: 

381 try: 

382 data = self.__connSocket.recv(8192) 

383 if data == b'': 

384 break 

385 s.sendall(data) 

386 data = s.recv(8192) 

387 self.__connSocket.sendall(data) 

388 except Exception as e: 

389 LOG.debug("Exception:", exc_info=True) 

390 LOG.error('SOCKS: %s', str(e)) 

391 

392 # Let's look if there's a relayed connection for our host/port 

393 scheme = None 

394 if self.targetHost in self.__socksServer.activeRelays: 

395 if self.targetPort in self.__socksServer.activeRelays[self.targetHost]: 

396 scheme = self.__socksServer.activeRelays[self.targetHost][self.targetPort]['scheme'] 

397 

398 if scheme is not None: 

399 LOG.debug('Handler for port %s found %s' % (self.targetPort, self.__socksServer.socksPlugins[scheme])) 

400 relay = self.__socksServer.socksPlugins[scheme](self.targetHost, self.targetPort, self.__connSocket, 

401 self.__socksServer.activeRelays[self.targetHost][self.targetPort]) 

402 

403 try: 

404 relay.initConnection() 

405 

406 # Let's answer back saying we've got the connection. Data is fake 

407 if self.__socksVersion == 5: 

408 reply = SOCKS5_REPLY() 

409 reply['REP'] = replyField.SUCCEEDED.value 

410 addr, port = self.__connSocket.getsockname() 

411 reply['PAYLOAD'] = socket.inet_aton(addr) + pack('>H', port) 

412 else: 

413 reply = SOCKS4_REPLY() 

414 

415 self.__connSocket.sendall(reply.getData()) 

416 

417 if relay.skipAuthentication() is not True: 

418 # Something didn't go right 

419 # Close the socket 

420 self.__connSocket.close() 

421 return 

422 

423 # Ok, so we have a valid connection to play with. Let's lock it while we use it so the Timer doesn't send a 

424 # keep alive to this one. 

425 self.__socksServer.activeRelays[self.targetHost][self.targetPort][relay.username]['inUse'] = True 

426 

427 relay.tunnelConnection() 

428 except Exception as e: 

429 LOG.debug("Exception:", exc_info=True) 

430 LOG.debug('SOCKS: %s' % str(e)) 

431 if str(e).find('Broken pipe') >= 0 or str(e).find('reset by peer') >=0 or \ 

432 str(e).find('Invalid argument') >= 0: 

433 # Connection died, taking out of the active list 

434 del(self.__socksServer.activeRelays[self.targetHost][self.targetPort][relay.username]) 

435 if len(list(self.__socksServer.activeRelays[self.targetHost][self.targetPort].keys())) == 1: 

436 del(self.__socksServer.activeRelays[self.targetHost][self.targetPort]) 

437 LOG.debug('Removing active relay for %s@%s:%s' % (relay.username, self.targetHost, self.targetPort)) 

438 self.sendReplyError(replyField.CONNECTION_REFUSED) 

439 return 

440 pass 

441 

442 # Freeing up this connection 

443 if relay.username is not None: 

444 self.__socksServer.activeRelays[self.targetHost][self.targetPort][relay.username]['inUse'] = False 

445 else: 

446 LOG.error('SOCKS: I don\'t have a handler for this port') 

447 

448 LOG.debug('SOCKS: Shutting down connection') 

449 try: 

450 self.sendReplyError(replyField.CONNECTION_REFUSED) 

451 except Exception as e: 

452 LOG.debug('SOCKS END: %s' % str(e)) 

453 

454 

455class SOCKS(socketserver.ThreadingMixIn, socketserver.TCPServer): 

456 def __init__(self, server_address=('0.0.0.0', 1080), handler_class=SocksRequestHandler): 

457 LOG.info('SOCKS proxy started. Listening at port %d', server_address[1] ) 

458 

459 self.activeRelays = {} 

460 self.socksPlugins = {} 

461 self.restAPI = None 

462 self.activeConnectionsWatcher = None 

463 self.supportedSchemes = [] 

464 socketserver.TCPServer.allow_reuse_address = True 

465 socketserver.TCPServer.__init__(self, server_address, handler_class) 

466 

467 # Let's register the socksplugins plugins we have 

468 from impacket.examples.ntlmrelayx.servers.socksplugins import SOCKS_RELAYS 

469 

470 for relay in SOCKS_RELAYS: 

471 LOG.info('%s loaded..' % relay.PLUGIN_NAME) 

472 self.socksPlugins[relay.PLUGIN_SCHEME] = relay 

473 self.supportedSchemes.append(relay.PLUGIN_SCHEME) 

474 

475 # Let's create a timer to keep the connections up. 

476 self.__timer = RepeatedTimer(KEEP_ALIVE_TIMER, keepAliveTimer, self) 

477 

478 # Let's start our RESTful API 

479 self.restAPI = Thread(target=webService, args=(self, )) 

480 self.restAPI.daemon = True 

481 self.restAPI.start() 

482 

483 # Let's start out worker for active connections 

484 self.activeConnectionsWatcher = Thread(target=activeConnectionsWatcher, args=(self, )) 

485 self.activeConnectionsWatcher.daemon = True 

486 self.activeConnectionsWatcher.start() 

487 

488 def shutdown(self): 

489 self.__timer.stop() 

490 del self.restAPI 

491 del self.activeConnectionsWatcher 

492 return socketserver.TCPServer.shutdown(self) 

493 

494if __name__ == '__main__': 494 ↛ 495line 494 didn't jump to line 495, because the condition on line 494 was never true

495 from impacket.examples import logger 

496 logger.init() 

497 s = SOCKS() 

498 s.serve_forever()