# MIT License # Copyright (c) 2017 Balazs Bucsay # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import sys if "TCP_generic.py" in sys.argv[0]: print("[-] Instead of poking around just try: python xfltreat.py --help") sys.exit(-1) import socket import time import select import os import struct import threading #local files import Stateful_module import encryption import client import common class TCP_generic_thread(Stateful_module.Stateful_thread): def __init__(self, threadID, serverorclient, tunnel, packetselector, comms_socket, client_addr, authentication, encryption_module, verbosity, config, module_name): super(TCP_generic_thread, self).__init__() threading.Thread.__init__(self) self._stop = False self.threadID = threadID self.tunnel_r = None self.tunnel_w = tunnel self.packetselector = packetselector self.comms_socket = comms_socket self.client_addr = client_addr self.authentication = authentication self.verbosity = verbosity self.serverorclient = serverorclient self.config = config self.module_name = module_name self.check_result = None self.timeout = 3.0 self.partial_message = "" self.module_short = "TCP" self.client = None self.authenticated = False self.encryption = encryption.Encryption_details() self.encryption.set_module(encryption_module) return def communication_initialization(self): return def send(self, channel_type, message, additional_data): if channel_type == common.CONTROL_CHANNEL_BYTE: transformed_message = self.transform(self.encryption, common.CONTROL_CHANNEL_BYTE+message, 1) else: transformed_message = self.transform(self.encryption, common.DATA_CHANNEL_BYTE+message, 1) common.internal_print("{0} sent: {1}".format(self.module_short, len(transformed_message)), 0, self.verbosity, common.DEBUG) # WORKAROUND?! # Windows: It looks like when the buffer fills up the OS does not do # congestion control, instead throws and exception/returns with # WSAEWOULDBLOCK which means that we need to try it again later. # So we sleep 100ms and hope that the buffer has more space for us. # If it does then it sends the data, otherwise tries it in an infinite # loop... while True: try: return self.comms_socket.send(struct.pack(">H", len(transformed_message))+transformed_message) except socket.error as se: if se.args[0] == 10035: # WSAEWOULDBLOCK time.sleep(0.1) pass else: raise def recv(self): messages = [] message = self.partial_message + self.comms_socket.recv(4096) if len(message) == len(self.partial_message): self._stop = True if len(message) < 2: return messages while True: length = struct.unpack(">H", message[0:2])[0]+2 if len(message) >= length: messages.append(self.transform(self.encryption, message[2:length], 0)) common.internal_print("{0} read: {1}".format(self.module_short, len(messages[len(messages)-1])), 0, self.verbosity, common.DEBUG) self.partial_message = "" message = message[length:] else: self.partial_message = message break if len(message) < 2: self.partial_message = message break return messages def cleanup(self): try: self.comms_socket.close() except: pass if self.serverorclient: self.packetselector.delete_client(self.client) def communication_win(self, is_check): import win32event import win32file import win32api import pywintypes import winerror # event for the socket hEvent_sock = win32event.CreateEvent(None, 0, 0, None) win32file.WSAEventSelect(self.comms_socket, hEvent_sock, win32file.FD_READ) # event, overlapped struct for the pipe or tunnel hEvent_pipe = win32event.CreateEvent(None, 0, 0, None) # for reading from the pipe overlapped_pipe = pywintypes.OVERLAPPED() overlapped_pipe.hEvent = hEvent_pipe # buffer for the packets message_readfile = win32file.AllocateReadBuffer(4096) # showing if we already async reading or not not_reading_already = True first_run = True while not self._stop: try: if not self.tunnel_r: # user is not authenticated yet, so there is no pipe # only checking the socket for data rc = win32event.WaitForSingleObject(hEvent_sock, int(self.timeout*1000)) if rc == winerror.WAIT_TIMEOUT: # timed out, just rerun and wait continue else: # client mode so we have the socket and tunnel as well # or the client authenticated and the pipe was created if first_run or not_reading_already: # no ReadFile was called before or finished, so we # are calling it again hr, _ = win32file.ReadFile(self.tunnel_r, message_readfile, overlapped_pipe) not_reading_already = first_run = False if (hr == winerror.ERROR_IO_PENDING): # well, this was an async read, so we need to wait # until it happens rc = win32event.WaitForMultipleObjects([hEvent_sock, hEvent_pipe], 0, int(self.timeout*1000)) if rc == winerror.WAIT_TIMEOUT: # timed out, just rerun and wait continue else: if hr != 0: common.internal_print("{0} ReadFile failed: {1}".format(self.module_short, hr), -1) raise if rc < 0x80: # STATUS_ABANDONED_WAIT_0 if rc == 0: # socket got signalled not_reading_already = False messages = self.recv() for message in messages: # looping through the messages from socket if len(message) == 0: # this could happen when the socket died or # partial message was read. continue if common.is_control_channel(message[0:1]): # parse control messages if self.controlchannel.handle_control_messages(self, message[len(common.CONTROL_CHANNEL_BYTE):], None): continue else: self.stop() break if self.authenticated: try: # write packet to the tunnel self.packet_writer(message[len(common.CONTROL_CHANNEL_BYTE):]) except OSError as e: print(e) # wut? except Exception as e: if e.args[0] == 995: common.internal_print("Interface disappered, exiting thread: {0}".format(e), -1) self.stop() continue if rc == 1: # pipe/tunnel got signalled not_reading_already = True if (overlapped_pipe.InternalHigh < 4) or (message_readfile[0:1] != "\x45"): #Only care about IPv4 # too small which should not happen or not IPv4, so we just drop it. continue # reading out the packet from the buffer and discarding the rest readytogo = message_readfile[0:overlapped_pipe.InternalHigh] self.send(common.DATA_CHANNEL_BYTE, readytogo, None) except win32api.error as e: common.internal_print("TCP Exception: {0}".format(e), -1) self.cleanup() return True def communication_unix(self, is_check): rlist = [self.comms_socket] wlist = [] xlist = [] while not self._stop: if self.tunnel_r: rlist = [self.tunnel_r, self.comms_socket] try: readable, writable, exceptional = select.select(rlist, wlist, xlist, self.timeout) except select.error, e: break if self._stop: self.comms_socket.close() break try: for s in readable: if (s is self.tunnel_r) and not self._stop: message = self.packet_reader(self.tunnel_r, True, self.serverorclient) while True: if (len(message) < 4) or (message[0:1] != "\x45"): #Only care about IPv4 break packetlen = struct.unpack(">H", message[2:4])[0] # IP Total length if packetlen > len(message): message += self.packet_reader(self.tunnel_r, False, self.serverorclient) readytogo = message[0:packetlen] message = message[packetlen:] self.send(common.DATA_CHANNEL_BYTE, readytogo, None) if (s is self.comms_socket) and not self._stop: messages = self.recv() for message in messages: if len(message) == 0: continue if common.is_control_channel(message[0:1]): if self.controlchannel.handle_control_messages(self, message[len(common.CONTROL_CHANNEL_BYTE):], None): continue else: self.stop() break if self.authenticated: try: self.packet_writer(message[len(common.CONTROL_CHANNEL_BYTE):]) except OSError as e: print(e) # wut? except Exception as e: print(e) except (socket.error, OSError, IOError): if self.serverorclient: common.internal_print("Client lost. Closing down thread.", -1) self.cleanup() return if not self.serverorclient: common.internal_print("Server lost. Closing connection.", -1) self.comms_socket.close() break except: print("another error") raise self.cleanup() return True class TCP_generic(Stateful_module.Stateful_module): module_name = "TCP generic" module_configname = "TCP_generic" module_description = """Generic TCP module that can listen on any port. This module lacks of any encryption or encoding, which comes to the interface goes to the socket back and forth. Nothing special. """ module_os_support = common.OS_LINUX | common.OS_MACOSX | common.OS_WINDOWS | common.OS_FREEBSD def __init__(self): super(TCP_generic, self).__init__() self.server_socket = None return def stop(self): self._stop = True if self.threads: for t in self.threads: t.stop() # not so nice solution to get rid of the block of listen() # unfortunately close() does not help on the block try: server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) serverbind = self.config.get("Global", "serverbind") if serverbind == "0.0.0.0": # windows does not like to connect to 0.0.0.0 serverbind = "127.0.0.1" server_socket.connect((serverbind, int(self.config.get(self.get_module_configname(), "serverport")))) except: pass return def sanity_check(self): if not self.config.has_option(self.get_module_configname(), "serverport"): common.internal_print("'serverport' option is missing from '{0}' section".format(self.get_module_configname()), -1) return False try: convert = int(self.config.get(self.get_module_configname(), "serverport")) except: common.internal_print("'serverport' is not an integer in '{0}' section".format(self.get_module_configname()), -1) return False return True def serve(self): client_socket = server_socket = None self.threads = [] threadsnum = 0 common.internal_print("Starting module: {0} on {1}:{2}".format(self.get_module_name(), self.config.get("Global", "serverbind"), int(self.config.get(self.get_module_configname(), "serverport")))) server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) try: server_socket.bind((self.config.get("Global", "serverbind"), int(self.config.get(self.get_module_configname(), "serverport")))) while not self._stop: server_socket.listen(1) #?? 1 ?? client_socket, client_addr = server_socket.accept() common.internal_print(("Client connected: {0}".format(client_addr)), 0, self.verbosity, common.DEBUG) threadsnum = threadsnum + 1 thread = TCP_generic_thread(threadsnum, 1, self.tunnel, self.packetselector, client_socket, client_addr, self.authentication, self.encryption_module, self.verbosity, self.config, self.get_module_name()) thread.start() self.threads.append(thread) if self._stop: self.stop() except socket.error as exception: # [Errno 98] Address already in use if ((self.os_type == common.OS_LINUX) and (exception.args[0] == 98)) or ((self.os_type == common.OS_MACOSX) and (exception.args[0] == 48)): common.internal_print("Starting failed, port is in use: {0} on {1}:{2}".format(self.get_module_name(), self.config.get("Global", "serverbind"), int(self.config.get(self.get_module_configname(), "serverport"))), -1) else: raise self.cleanup(server_socket) return def connect(self): try: common.internal_print("Starting client: {0}".format(self.get_module_name())) client_fake_thread = None server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server_socket.settimeout(3) server_socket.connect((self.config.get("Global", "remoteserverip"), int(self.config.get(self.get_module_configname(), "serverport")))) client_fake_thread = TCP_generic_thread(0, 0, self.tunnel, None, server_socket, None, self.authentication, self.encryption_module, self.verbosity, self.config, self.get_module_name()) client_fake_thread.do_hello() client_fake_thread.communication(False) except KeyboardInterrupt: if client_fake_thread: client_fake_thread.do_logoff() self.cleanup(server_socket) raise except socket.error as e: common.internal_print("Connection error: {0}".format(self.get_module_name()), -1) self.cleanup(server_socket) raise self.cleanup(server_socket) return def check(self): try: common.internal_print("Checking module on server: {0}".format(self.get_module_name())) server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server_socket.settimeout(3) server_socket.connect((self.config.get("Global", "remoteserverip"), int(self.config.get(self.get_module_configname(), "serverport")))) client_fake_thread = TCP_generic_thread(0, 0, None, None, server_socket, None, self.authentication, self.encryption_module, self.verbosity, self.config, self.get_module_name()) client_fake_thread.do_check() client_fake_thread.communication(True) self.cleanup(server_socket) except socket.timeout: common.internal_print("Checking failed: {0}".format(self.get_module_name()), -1) self.cleanup(server_socket) except socket.error as exception: if exception.args[0] == 111: common.internal_print("Checking failed: {0}".format(self.get_module_name()), -1) else: common.internal_print("Connection error: {0}".format(self.get_module_name()), -1) self.cleanup(server_socket) return def cleanup(self, socket): common.internal_print("Shutting down module: {0}".format(self.get_module_name())) socket.close() return