Skip to content

Instantly share code, notes, and snippets.

@domanchi
Created August 25, 2018 15:00
Show Gist options
  • Save domanchi/afad8baa17f945cef2a1e1a8b225a25a to your computer and use it in GitHub Desktop.
Save domanchi/afad8baa17f945cef2a1e1a8b225a25a to your computer and use it in GitHub Desktop.
#!/bin/python3.6
import email
import logging
import os
import select
from io import StringIO
from socketserver import StreamRequestHandler
from socketserver import TCPServer
from socketserver import ThreadingMixIn
from .socks5_server import open_socket_to_server
def main():
PORT = int(os.environ.get('PORT', 5000))
with ThreadingTCPServer(('127.0.0.1', PORT,), TCPProxy) as proxy:
proxy.serve_forever()
class ThreadingTCPServer(ThreadingMixIn, TCPServer):
pass
class SocksProxyMixin:
@staticmethod
def proxy_data(client, remote):
"""
:type client: socket
:type remote: socket
"""
while True:
# Wait until client or remote is available for read
r, _, _ = 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
class TCPProxy(SocksProxyMixin, StreamRequestHandler):
def handle(self):
logging.info('Accepting TCP connection from %s', self.client_address)
# Spawning off connection per host
data = self.connection.recv(4096)
query, headers = data.split('\r\n', 1)
headers = dict(
email.message_from_file(
StringIO(headers.decode('utf-8'))
).items()
)
socks5_server = open_socket_to_server(headers['Host'])
socks5_server.send(data)
self.proxy_data(
self.connection,
socks5_server,
)
if __name__ == '__main__':
main()
import logging
import socket
import struct
SERVER = 'socks.server'
PORT = 1234
USERNAME = 'redacted'
PASSWORD = 'redacted'
def open_socket_to_server(destination):
"""
:type destination: str
"""
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.connect((SERVER, PORT,))
_initialize_connection(server)
status = _authenticate_with_server(server)
assert status == 0
address, port = _send_connect_request(server, destination)
logging.info('IP: %s:%s', address, port)
return server, address, port
def _initialize_connection(server):
"""Sends a Client Hello.
:type remote: socket
:param remote: connection to Socks5 server
"""
server.send(
struct.pack(
'!BBB',
# Version we use (Socks 5)
5,
# Length of methods argument
1,
# According to RFC 1928, this means that it supports the
# username/password field
2,
)
)
version, method_chosen = struct.unpack('!BB', remote.recv(2))
assert version == 5
assert method_chosen == 2
def _authenticate_with_server(server):
"""Authenticates with the Socks5 server.
:type remote: socket
:param remote: connection to Socks5 server
"""
server.send(
struct.pack(
'!BB{}sB{}s'.format(
len(USERNAME),
len(PASSWORD),
),
# Version of authentication protocol
# https://tools.ietf.org/html/rfc1929
1,
len(USERNAME),
USERNAME,
len(PASSWORD),
PASSWORD,
),
)
version, status = struct.unpack('!BB', remote.recv(2))
return status
def _send_connect_request(remote, destination):
"""Gets a server to connect to, to reach destination address.
:type remote: socket
:param remote: connection to Socks5 server
:type destination: str
:param destination: server you want to query
"""
try:
socket.inet_aton(destination)
address_type = 1 # IPv4 address
except OSError:
address_type = 3 # domain name
server.send(
struct.pack(
'!5B{}sH'.format(len(destination)),
# Version we use
5,
# cmd: CONNECT
1,
# reserved byte
0,
# Indication that we're sending a domain name
address_type,
len(destination),
destination.encode(),
# Assuming we're connecting to HTTP servers (port 80)
80,
)
)
version, reply, _, address_type = struct.unpack('!4B', remote.recv(4))
if reply > 0:
_handle_connection_error(reply)
if address_type > 1:
raise NotImplementedError('TODO: Have yet to implement handling of non IPv4 addresses')
address = socket.inet_ntoa(remote.recv(4))
port = struct.unpack('!H', remote.recv(2))[0]
return address, port
def _handle_connection_error(error_code):
if error_code == 3:
raise ValueError('Network unreachable.')
raise ValueError(
'Something went wrong when connecting to SOCKS proxy! Error: {}'.format(reply)
)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment