Created
August 25, 2018 15:00
-
-
Save domanchi/afad8baa17f945cef2a1e1a8b225a25a to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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