def upstream_recv(self, data): if self.encryptor: buf = Buffer() ldata = len(data) buf.write(self.encryptor.encrypt(struct.pack('<I', ldata))) _, nw = data.write_to(buf, modificator=self.encryptor.encrypt, n=ldata) d = self.update_encryptor() buf.write(d) buf.write_to(self.downstream) else: data.write_to(self.up_buffer)
class EC4Transport(BasePupyTransport): __slots__ = ('encryptor', 'decryptor', 'up_buffer') privkey = None pubkey = None def __init__(self, *args, **kwargs): super(EC4Transport, self).__init__(*args, **kwargs) if not self.pubkey and not self.privkey: raise ValueError('Public or Private key required for EC4') if self.pubkey: self.encoder = ECPV(curve='brainpoolP384r1', public_key=self.pubkey, hash=SHA384) else: self.encoder = ECPV(curve='brainpoolP384r1', private_key=self.privkey, hash=SHA384) self.encryptor = None self.decryptor = None self.up_buffer = Buffer() def kex(self, data): if len(data) < 2: return False length, = struct.unpack_from('H', data.peek(2)) if len(data) < 2 + length: return False request = data.read(2 + length) if self.privkey: try: response, key = self.encoder.process_kex_request(request[2:], 0, key_size=128) except ValueError as e: raise EOFError(str(e)) # Add jitter, tinyec is quite horrible time.sleep(random.random()) self.downstream.write(struct.pack('H', len(response)) + response) else: try: key = self.encoder.process_kex_response(request[2:], 0, key_size=128) except ValueError as e: raise EOFError(str(e)) self.encryptor = RC4(key=key[0]) self.decryptor = RC4(key=key[1]) # https://wikileaks.org/ciav7p1/cms/files/NOD%20Cryptographic%20Requirements%20v1.1%20TOP%20SECRET.pdf # Okay... self.encryptor.encrypt('\x00' * 3072) self.decryptor.decrypt('\x00' * 3072) return True def downstream_recv(self, data): if self.encryptor: data.write_to(self.upstream, modificator=self.decryptor.decrypt) elif self.kex(data): if self.up_buffer: self.up_buffer.write_to(self.downstream, modificator=self.encryptor.encrypt) self.up_buffer = None if len(data): data.write_to(self.upstream, modificator=self.decryptor.decrypt) def upstream_recv(self, data): if self.encryptor: data.write_to(self.downstream, modificator=self.encryptor.encrypt) else: data.write_to(self.up_buffer)
class PupySocketStream(SocketStream): def __init__(self, sock, transport_class, transport_kwargs): super(PupySocketStream, self).__init__(sock) self.MAX_IO_CHUNK = 32000 self.KEEP_ALIVE_REQUIRED = False self.compress = True #buffers for transport self.upstream = Buffer( transport_func=addGetPeer(("127.0.0.1", 443)), shared=True ) if sock is None: peername = '127.0.0.1', 0 elif type(sock) is tuple: peername = sock[0], sock[1] else: peername = sock.getpeername() self.downstream = Buffer( on_write=self._upstream_recv, transport_func=addGetPeer(peername), shared=True ) self.upstream_lock = threading.Lock() self.downstream_lock = threading.Lock() self.transport = transport_class(self, **transport_kwargs) #buffers for streams self.buf_in = Buffer() self.buf_out = Buffer() self.on_connect() def on_connect(self): self.transport.on_connect() self._upstream_recv() def _read(self): try: buf = self.sock.recv(self.MAX_IO_CHUNK) if __debug__: logger.debug('stream: read={}'.format(len(buf) if buf else None)) except socket.timeout: return except socket.error: ex = sys.exc_info()[1] if get_exc_errno(ex) in (errno.EAGAIN, errno.EWOULDBLOCK): # windows just has to be a b**ch # edit: some politeness please ;) return self.close() raise EOFError(ex) if not buf: self.close() raise EOFError("connection closed by peer") self.buf_in.write(buf) # The root of evil def poll(self, timeout): if self.closed: raise EOFError('polling on already closed connection') result = ( len(self.upstream)>0 or self.sock_poll(timeout) ) return result def sock_poll(self, timeout): with self.downstream_lock: to_close = None to_read = None while not (to_close or to_read or self.closed): try: to_read, _, to_close = select([self.sock], [], [self.sock], timeout) except select_error as r: if not r.args[0] == errno.EINTR: to_close = True continue break if to_close: raise EOFError('sock_poll error') if to_read: self._read() self.transport.downstream_recv(self.buf_in) return True else: return False def _upstream_recv(self): """ called as a callback on the downstream.write """ if len(self.downstream)>0: if __debug__: logger.debug('stream: send={}'.format(len(self.downstream))) self.downstream.write_to(super(PupySocketStream, self)) def waitfor(self, count): if __debug__: logger.debug('stream: waitfor={}'.format(count)) try: while len(self.upstream)<count: if not self.sock_poll(None) and self.closed: return None return self.upstream except (EOFError, socket.error): self.close() raise except Exception as e: logger.debug(traceback.format_exc()) self.close() raise def read(self, count): promise = self.waitfor(count) if promise: return promise.read(count) def insert(self, data): with self.upstream_lock: self.buf_out.insert(data) def flush(self): self.buf_out.flush() def write(self, data, notify=True): if __debug__: logger.debug('stream: write={} / n={}'.format( len(data) if data else None, notify)) try: with self.upstream_lock: self.buf_out.write(data, notify) del data if notify: self.transport.upstream_recv(self.buf_out) #The write will be done by the _upstream_recv callback on the downstream buffer except (EOFError, socket.error): self.close() raise except Exception as e: logger.debug(traceback.format_exc()) self.close() raise
class ECMTransport(BasePupyTransport): __slots__ = ('encryptor', 'decryptor', 'up_buffer', 'dec_buffer', 'nonce', 'key', 'chunk_len', 'need_validation', 'encoder') privkey = None pubkey = None def __init__(self, *args, **kwargs): super(ECMTransport, self).__init__(*args, **kwargs) if not self.pubkey and not self.privkey: raise ValueError('Public or Private key required for ECM') if self.pubkey: self.encoder = ECPV(curve='brainpoolP384r1', public_key=self.pubkey, hash=SHA384) else: self.encoder = ECPV(curve='brainpoolP384r1', public_key=self.privkey, hash=SHA384) self.encryptor = None self.decryptor = None self.up_buffer = Buffer() self.dec_buffer = Buffer() self.nonce = None self.key = None self.chunk_len = 0 self.need_validation = False def update_encryptor(self): vblock = self.encryptor.digest() h = SHA3_224.new() h.update(self.key[0]) h.update(vblock) self.encryptor = AES.new(key=self.key[0], mode=AES.MODE_GCM, nonce=h.digest()) return vblock def update_decryptor(self, vblock): self.decryptor.verify(vblock) h = SHA3_224.new() h.update(self.key[1]) h.update(vblock) self.decryptor = AES.new(key=self.key[1], mode=AES.MODE_GCM, nonce=h.digest()) def kex(self, data): if len(data) < 4: return False reqlen, noncelen = struct.unpack_from('<HH', data.peek(4)) if len(data) < 4 + reqlen + noncelen: return False data.drain(4) request = data.read(reqlen) remote_nonce = data.read(noncelen) if self.privkey: response, key = self.encoder.process_kex_request(request, 0) # Add jitter, tinyec is quite horrible time.sleep(random.random()) nonce = get_random_bytes(16) self.downstream.write( struct.pack('<HH', len(response), len(nonce)) + response + nonce) else: key = self.encoder.process_kex_response(request, 0) nonce = self.nonce eh = SHA3_224.new() eh.update(key[0]) eh.update(remote_nonce) ek = eh.digest()[:16] dh = SHA3_224.new() dh.update(key[1]) dh.update(nonce) dk = dh.digest()[:16] self.key = (ek, dk) self.encryptor = AES.new(key=self.key[0], mode=AES.MODE_GCM, nonce=nonce) self.decryptor = AES.new(key=self.key[1], mode=AES.MODE_GCM, nonce=remote_nonce) return True def downstream_recv(self, data): if self.decryptor: while len(data): if not self.chunk_len: if self.need_validation: if len(data) < 16: return vblock = data.read(16) self.update_decryptor(vblock) self.need_validation = False self.dec_buffer.write_to(self.upstream) if len(data) < 4: break raw_chunk_len = self.decryptor.decrypt(data.read(4)) self.chunk_len, = struct.unpack('<I', raw_chunk_len) if not len(data): break nr, nw = data.write_to(self.dec_buffer, modificator=self.decryptor.decrypt, n=self.chunk_len) self.chunk_len -= nr if not self.chunk_len: self.need_validation = True elif self.kex(data): if len(self.up_buffer): self.upstream_recv(self.up_buffer) if len(data): self.downstream_recv(data) def upstream_recv(self, data): if self.encryptor: buf = Buffer() ldata = len(data) buf.write(self.encryptor.encrypt(struct.pack('<I', ldata))) _, nw = data.write_to(buf, modificator=self.encryptor.encrypt, n=ldata) d = self.update_encryptor() buf.write(d) buf.write_to(self.downstream) else: data.write_to(self.up_buffer)
class PupyHTTPWrapperServer(BasePupyTransport): path = '/index.php?d=' allowed_methods = ('GET') server = None headers = { 'Content-Type': 'text/html; charset=utf-8', 'Server': 'Apache', 'Connection': 'close', } __slots__ = ('parser', 'is_http', 'body', 'downstream_buffer', 'well_known', 'omit', 'probe_len') def __init__(self, *args, **kwargs): super(PupyHTTPWrapperServer, self).__init__(*args, **kwargs) self.parser = HttpParser() self.is_http = None self.body = [] self.downstream_buffer = Buffer() self.well_known = ('GET', 'POST', 'OPTIONS', 'HEAD', 'PUT', 'DELETE') self.omit = tuple('{} {}'.format(x, y) for x in self.well_known for y in (self.path, '/ws/', 'ws/')) self.probe_len = max(len(x) for x in self.omit) def _http_response(self, code, status, headers=None, datasize=None, content=None): headers = {} headers.update(self.headers) if headers: headers.update(headers) if datasize: headers.update({ 'Content-Length': datasize, 'Content-Type': 'application/octet-stream', }) data = '\r\n'.join([ 'HTTP/1.1 {} {}'.format(code, status), '\r\n'.join([ '{}: {}'.format(key, value) for key, value in headers.iteritems() ]) ]) + '\r\n\r\n' self.downstream.write(data) def _handle_file(self, filepath): try: with open(filepath) as infile: size = stat(filepath).st_size self._http_response(200, 'OK', datasize=size) while True: data = infile.read(65535) if data: self.downstream.write(data) else: break except: self._http_response(404, 'Not found', 'Not found') def _handle_not_found(self): self._http_response(404, 'Not found', 'Not found') def _handle_http(self, data): self.parser.execute(data, len(data)) if self.parser.is_headers_complete(): try: if not self.parser.get_method() in self.allowed_methods: self._http_response(405, 'Method Not Allowed') else: urlpath = self.parser.get_path() urlpath = [ x.strip() for x in urlpath.split('/') if (x and not str(x) in ('.', '..')) ] root = self.server.config.get_folder('wwwroot') secret = self.server.config.getboolean('httpd', 'secret') log = self.server.config.getboolean('httpd', 'log') if secret: wwwsecret = self.server.config.get('randoms', 'wwwsecret', random=5) if not (urlpath and urlpath[0] == wwwsecret): self._handle_not_found() if log: self.server.info( '{}: GET {} | SECRET = {}'.format( '{}:{}'.format(*self.downstream. transport.peer[:2]), urlpath, wwwsecret), error=True) return urlpath = urlpath[1:] urlpath = path.sep.join([ self.server.config.get('randoms', x, new=False) or x for x in urlpath ]) if not urlpath: urlpath = 'index.html' filepath = path.join(root, urlpath) if path.exists(filepath): self._handle_file(filepath) if log: message = urlpath if filepath in self.server.served_content: message = message + ' <' + self.server.served_content[ filepath] + '>' self.server.info('{}: GET /{}'.format( '{}:{}'.format( *self.downstream.transport.peer[:2]), message)) else: self._handle_not_found() if log: self.server.info('{}: GET {}'.format( '{}:{}'.format( *self.downstream.transport.peer[:2]), urlpath), error=True) finally: self.close() def downstream_recv(self, data): header = data.peek(self.probe_len) if __debug__: logger.debug('Recv: len=%d // header = %s', len(data), header) if self.server and self.is_http is None: self.is_http = header.startswith(self.well_known) and \ not header.startswith(self.omit) if __debug__: logger.debug('Http: %s', self.is_http) if self.is_http: self._handle_http(data.read()) else: if __debug__: logger.debug('Write to upstream: len=%d, handler=%s', len(data), self.upstream.on_write_f) data.write_to(self.upstream) if self.downstream_buffer: if __debug__: logger.debug( 'Flush buffer to downstream: len=%d, handler=%s', len(self.downstream_buffer), self.downstream.on_write_f) self.downstream_buffer.write_to(self.downstream) if __debug__: logger.debug('Release transport') raise ReleaseChainedTransport() def upstream_recv(self, data): if __debug__: logger.debug('Send intent: len=%d', len(data)) if self.is_http is None: data.write_to(self.downstream_buffer) if __debug__: logger.debug('HTTP? Append to pending buffer: total len=%d', len(self.downstream_buffer)) elif not self.is_http: if __debug__: logger.debug('Non-HTTP: Direct pass (handler=%s)', self.downstream.on_write_f) if self.downstream_buffer: self.downstream_buffer.write_to(self.downstream) data.write_to(self.downstream) else: if __debug__: logger.debug('HTTP: Omit data') pass
class PupyWebSocketServer(PupyWebSocketTransport): __slots__ = ( 'user_agent', 'path', 'offset', 'upgraded_buf', 'missing_bytes', 'upgraded', 'decoded', 'mask', ) def __init__(self, *args, **kwargs): PupyWebSocketTransport.__init__(self, *args, **kwargs) self.upgraded = False self.user_agent = kwargs.pop('user-agent') self.path = kwargs.pop('path') self.mask = True self.offset = 0 self.missing_bytes = 0 self.decoded = Buffer() self.upgraded_buf = Buffer() if __debug__: logger.debug('WS Server, path=%s, user-agent=%s', self.path, self.user_agent) def calculate_response_key(self, key): GUID = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11' hsh = sha1(key.encode() + GUID.encode()) response_key = base64.b64encode(hsh.digest()).strip() return response_key.decode('ASCII') def bad_request(self, msg): if __debug__: logger.debug(msg) self.downstream.write(error_response) self.close() def upstream_recv(self, data): """ Encoding server -> client messages Messsages shouldn't be masked """ try: if self.upgraded: add_ws_encapsulation(data, self.downstream) else: add_ws_encapsulation(data, self.upgraded_buf) except Exception as e: raise EOFError(str(e)) def downstream_recv(self, data): """ Decoding client -> server messages Message should be masked coming from client """ if not self.upgraded: if __debug__: logger.debug('WS: Wait for upgrade requet') d = data.peek() # Handle HTTP GET requests, strip websocket keys, verify UA etc if not d.startswith('GET '): self.bad_request('Invalid HTTP method or data ({})'.format( repr(d))) if '\r\n\r\n' not in d: if __debug__: logger.debug('Short read, incomplete header') return _, path, _ = d.split(' ', 2) if path != self.path: self.bad_request('Path does not match ({} != {})!'.format( path, self.path)) return wskey = None key = re.search( r'\n[sS]ec-[wW]eb[sS]ocket-[kK]ey[\s]*:[\s]*(.*)\r\n', d) if key: wskey = key.group(1) else: if __debug__: logger.debug('Unable to get WebSocketKey') if self.user_agent: ua = re.search(r'\n[uU]ser-[aA]gent:[\s]*(.*)\r\n', d) if ua: ua = ua.group(1) else: self.bad_request('No User-Agent provided') return if ua != self.user_agent: self.bad_request( 'Bad User-Agent provided. May be counter-intel ({} != {})' .format(ua, self.user_agent)) return payload = 'HTTP/1.1 101 Switching Protocols\r\n' payload += 'Upgrade: websocket\r\n' payload += 'Connection: Upgrade\r\n' if wskey: payload += 'Sec-WebSocket-Accept: %s\r\n' % ( self.calculate_response_key(wskey)) payload += '\r\n' data.drain(d.index('\r\n\r\n') + 4) if __debug__: logger.debug('Flush upgrade response') self.downstream.write(payload) if self.upgraded_buf: if __debug__: logger.debug('Flush buffer %d', len(self.upgraded_buf)) self.upgraded_buf.write_to(self.downstream) self.upgraded = True while data: msg_len, self.offset, self.missing_bytes, self.mask = remove_ws_encapsulation( data, self.upstream, self.decoded, self.offset, self.missing_bytes, self.mask) if __debug__: logger.debug('Parsed: %d, offset: %d, missing: %d, left: %d', msg_len, self.offset, self.missing_bytes, len(data)) if not msg_len: break
class Obfs3Transport(BaseTransport): """ Obfs3Transport implements the obfs3 protocol. """ __slots__ = ('state', 'dh', 'shared_secret', 'scanned_padding', 'last_padding_chunk', 'other_magic_value', 'send_crypto', 'recv_crypto', 'queued_data', 'send_keytype', 'recv_keytype', 'send_magic_const', 'recv_magic_const', 'we_are_initiator') def __init__(self, *args, **kwargs): """Initialize the obfs3 pluggable transport.""" super(Obfs3Transport, self).__init__(*args, **kwargs) # Our state. self.state = ST_WAIT_FOR_KEY # Uniform-DH object self.dh = obfs3_dh.UniformDH() # DH shared secret self.shared_secret = None # Bytes of padding scanned so far. self.scanned_padding = 0 # Last padding bytes scanned. self.last_padding_chunk = '' # Magic value that the other party is going to send # (initialized after deriving shared secret) self.other_magic_value = None # Crypto to encrypt outgoing data. self.send_crypto = None # Crypto to decrypt incoming data. self.recv_crypto = None # Buffer for the first data, Tor is trying to send but can't right now # because we have to handle the DH handshake first. self.queued_data = Buffer() # Attributes below are filled by classes that inherit Obfs3Transport. self.send_keytype = None self.recv_keytype = None self.send_magic_const = None self.recv_magic_const = None self.we_are_initiator = None def circuitConnected(self): """ Do the obfs3 handshake: PUBKEY | WR(PADLEN) """ padding_length = random.randint(0, MAX_PADDING / 2) public_key = self.dh.get_public() handshake_message = public_key + get_random(padding_length) if __debug__: logger.debug( "obfs3 handshake: %s queued %d bytes (padding_length: %d) (public key: %s).", "initiator" if self.we_are_initiator else "responder", len(handshake_message), padding_length, repr(public_key)) self.downstream.write(handshake_message) def receivedUpstream(self, data): """ Got data from upstream. We need to obfuscated and proxy them downstream. """ if self.state != ST_OPEN: if __debug__: logger.debug( "Got upstream data before doing handshake [STATE=%d]. Caching.", self.state) data.write_to(self.queued_data) return if __debug__: logger.debug("obfs3 receivedUpstream: Transmitting %d bytes.", len(data)) if self.queued_data: if __debug__: logger.debug("Flush %d bytes of queued data (???) ", len(self.queued_data)) self.queued_data.write_to(self.downstream, modificator=self.send_crypto.encrypt) # Proxy encrypted message. data.write_to(self.downstream, modificator=self.send_crypto.encrypt) def receivedDownstream(self, data): """ Got data from downstream. We need to de-obfuscate them and proxy them upstream. """ if self.state == ST_WAIT_FOR_KEY: # Looking for the other peer's pubkey if __debug__: logger.debug("Wait for key") self._read_handshake(data) if self.state == ST_WAIT_FOR_HANDSHAKE: # Doing the exp mod if __debug__: logger.debug("Wait for handshake") return if self.state == ST_SEARCHING_MAGIC: # Looking for the magic string if __debug__: logger.debug("Search magic") if self._scan_for_magic(data) and self.queued_data: if __debug__: logger.debug('Flush queued data: %d', len(self.queued_data)) self.queued_data.write_to(self.downstream, modificator=self.send_crypto.encrypt) if self.state == ST_OPEN: # Handshake is done. Just decrypt and read application data. if __debug__: logger.debug( "obfs3 receivedDownstream: Processing %d bytes of application data." % (len(data))) if not data: return data.write_to(self.upstream, modificator=self.recv_crypto.encrypt) def _read_handshake(self, data): """ Read handshake message, parse the other peer's public key and schedule the key exchange for execution outside of the event loop. """ if len(data) < PUBKEY_LEN: if __debug__: logger.debug("Read handshake - short read") return # Get the public key from the handshake message, do the DH and # get the shared secret. other_pubkey = data.read(PUBKEY_LEN) if __debug__: logger.debug("Other pubkey: %s", repr(other_pubkey)) self.state = ST_WAIT_FOR_HANDSHAKE try: kex = self.dh.get_secret(other_pubkey) self._read_handshake_post_dh(kex, data) except Exception, e: if __debug__: logger.debug('DH Exception: %s', e) self._uniform_dh_errback(e, other_pubkey)