Skip to content

Instantly share code, notes, and snippets.

@gteissier
Created December 31, 2019 16:01
Show Gist options
  • Save gteissier/9e323bc377cb8af754c4f4a1dca12f68 to your computer and use it in GitHub Desktop.
Save gteissier/9e323bc377cb8af754c4f4a1dca12f68 to your computer and use it in GitHub Desktop.
SOCKSv5 server to SAPNI tunneller: you can use proxy chains to tunnelize your favorite tools now
#!/usr/bin/env python3
import logging
import select
import socket
import struct
from socketserver import ThreadingMixIn, TCPServer, StreamRequestHandler
logging.basicConfig(level=logging.DEBUG)
SOCKS_VERSION = 5
class ThreadingTCPServer(ThreadingMixIn, TCPServer):
pass
# !!!!!!!!!!!
# !!!!!!!!!!!
SAP_ROUTER = (b'0.0.0.0', 3299)
# !!!!!!!!!!!
# !!!!!!!!!!!
def prepend_length(msg):
return struct.pack('!I', len(msg)) + msg
def pack_connect(where):
msg = b'NI_ROUTE\x00'
msg += struct.pack(b'!BBBB', 2, 0x27, 2, 1)
route_chain = b'%s\x00%d\x00\x00' % (SAP_ROUTER[0], SAP_ROUTER[1])
current_offset = len(route_chain)
route_chain += b'%s\x00%d\x00\x00' % (where[0].encode(), where[1])
total_length = len(route_chain)
msg += b'\x00\x00' + struct.pack(b'!BII', 1, total_length, current_offset)
msg += route_chain
msg = struct.pack('!I', len(msg)) + msg
return msg
class SAPNISocket(socket.socket):
def connect(self, where):
super().connect(SAP_ROUTER)
msg = pack_connect(where)
self.send(msg)
buf = self.recv(4)
if len(buf) != 4: return b''
chunksize = struct.unpack('!I', buf)[0]
received = b''
while len(received) < chunksize:
data = super().recv(chunksize-len(received))
if len(data) == 0: break
received += data
assert(received == b'NI_PONG\x00')
class SocksProxy(StreamRequestHandler):
def handle(self):
logging.info('Accepting connection from %s:%s' % self.client_address)
# greeting header
# read and unpack 2 bytes from a client
header = self.connection.recv(2)
version, nmethods = struct.unpack("!BB", header)
# socks 5
assert version == SOCKS_VERSION
assert nmethods > 0
# get available methods
methods = self.get_available_methods(nmethods)
# send welcome message
self.connection.sendall(struct.pack("!BB", SOCKS_VERSION, 0))
# request
version, cmd, _, address_type = struct.unpack("!BBBB", self.connection.recv(4))
assert version == SOCKS_VERSION
if address_type == 1: # IPv4
address = socket.inet_ntoa(self.connection.recv(4))
elif address_type == 3: # Domain name
domain_length = ord(self.connection.recv(1)[0])
address = self.connection.recv(domain_length)
port = struct.unpack('!H', self.connection.recv(2))[0]
# reply
try:
if cmd == 1: # CONNECT
remote = SAPNISocket(socket.AF_INET, socket.SOCK_STREAM)
remote.connect((address, port))
bind_address = remote.getsockname()
logging.info('Connected to %s %s' % (address, port))
else:
self.server.close_request(self.request)
addr = struct.unpack("!I", socket.inet_aton(bind_address[0]))[0]
port = bind_address[1]
reply = struct.pack("!BBBBIH", SOCKS_VERSION, 0, 0, address_type,
addr, port)
except Exception as err:
logging.error(err)
# return connection refused error
reply = self.generate_failed_reply(address_type, 5)
self.connection.sendall(reply)
# establish data exchange
if reply[1] == 0 and cmd == 1:
self.exchange_loop(self.connection, remote)
self.server.close_request(self.request)
def get_available_methods(self, n):
methods = []
for i in range(n):
methods.append(ord(self.connection.recv(1)))
return methods
def generate_failed_reply(self, address_type, error_number):
return struct.pack("!BBBBIH", SOCKS_VERSION, error_number, 0, address_type, 0, 0)
def exchange_loop(self, client, remote):
while True:
# wait until client or remote is available for read
r, w, e = select.select([client, remote], [], [])
if client in r:
data = client.recv(4096)
if remote.send(data) <= 0:
break
if remote in r:
data = remote.recv(4096)
if client.send(data) <= 0:
break
if __name__ == '__main__':
with ThreadingTCPServer(('127.0.0.1', 9011), SocksProxy) as server:
server.serve_forever()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment