def __init__(self, opts): """ :opts: { pcap: (boolean) Whether to save packets in pcap format, udp: (boolean) Whether to use UDP proxy (default: TCP), program: (list) Program and arguments, jobs: (int) Number of jobs to run concurrently, timeout: (int) Seconds to give process before killing it, output_directory: (string) Directory to place found bugs, input_arguments: (string) Arguments for the input module, input_module: (string) Module to use for input generation, fuzzing_module: (string) Module to use for fuzzing the network data, prerocessing_module: (string) Module to use for preprocessing data, instrumentation_module: (string) Module to use for instrumentation } """ for opt, value in opts.items(): setattr(self, opt, value) Fuzzer.__init__(self, opts) self._tlock = threading.Lock() self._set_operating_mode() self._validate_arguments() if self.udp: self.proxy = UdpProxy(self.log) else: self.proxy = TcpProxy(self.log) self.proxy.address = self.address self.proxy.listen_port = self.listen_port self.proxy.forward_port = self.forward_port self.proxy.data_handler = self.data_handler
class NetworkFuzzer(Fuzzer): """ Network fuzzer with capability to work either as transparent proxy or also as process manager by starting services and their input automatically. All data that needs to be handled should go through the proxy which will then pass it along channels to the recipient. The packets that pass through the proxy will be saved individually as into a packet backlog per session. When input is self generated, the proxy should use the default port. Input modules using sockets send all traffic to the default proxy port. MODE_TRANSPARENT: Transparent mode only initializes proxy and data handling modules (preprocessing and fuzzing). By default the proxy will listen on port 6000 and forward everything to the specified port. MODE_GENERATE: Generator mode will use the input module to generate input that is then forwarded to the recipient. MODE_SERVICE: Service mode is MODE_TRANSPARENT + one managed service to which the data is forwarded through the proxy. The managed service is handled by the ProcessManagement object and can only handle one service at a time in this mode. MODE_CONTROLLED: Controlled mode is the combination of MODE_SERVICE and MODE_GENERATE with the added benefit that by using randomized ports (%p), it is possible run dozens or hundreds of instances concurrently with the ProcessManagement object. """ MODE_TRANSPARENT = False MODE_GENERATE = False MODE_SERVICE = False MODE_CONTROLLED = False def __init__(self, opts): """ :opts: { pcap: (boolean) Whether to save packets in pcap format, udp: (boolean) Whether to use UDP proxy (default: TCP), program: (list) Program and arguments, jobs: (int) Number of jobs to run concurrently, timeout: (int) Seconds to give process before killing it, output_directory: (string) Directory to place found bugs, input_arguments: (string) Arguments for the input module, input_module: (string) Module to use for input generation, fuzzing_module: (string) Module to use for fuzzing the network data, prerocessing_module: (string) Module to use for preprocessing data, instrumentation_module: (string) Module to use for instrumentation } """ for opt, value in opts.items(): setattr(self, opt, value) Fuzzer.__init__(self, opts) self._tlock = threading.Lock() self._set_operating_mode() self._validate_arguments() if self.udp: self.proxy = UdpProxy(self.log) else: self.proxy = TcpProxy(self.log) self.proxy.address = self.address self.proxy.listen_port = self.listen_port self.proxy.forward_port = self.forward_port self.proxy.data_handler = self.data_handler def _validate_arguments(self): """ Validate command line arguments. """ if not self.forward_port and not self.MODE_CONTROLLED: self.log.error("No forward port specified.") self._exit() elif not self.MODE_CONTROLLED: self.forward_port = int(self.forward_port) if not self.listen_port: self.listen_port = 6000 else: self.listen_port = int(self.listen_port) if not self.address: self.address = "localhost" if self.MODE_CONTROLLED or self.MODE_SERVICE: if not self.jobs: self.jobs = 1 def _exit(self): self.proxy.running = False if not self.MODE_TRANSPARENT or not self.MODE_GENERATE: self.process_management.running = False def _set_operating_mode(self): """ Sets operating mode according to given arguments. """ if (self.input_module or self.input_arguments) and self.program: self.log.info("Running on controlled mode") self.MODE_CONTROLLED = True elif self.input_module or self.input_arguments: self.log.info("Running on input generation mode") self.MODE_GENERATE = True elif self.program: self.log.info("Running on service mode") self.MODE_SERVICE = True else: self.log.info("Running on transparent mode") self.MODE_TRANSPARENT = True def _new_process_handler(self, process): """ Creates new Process instance that ProcessManagement can monitor. In controlled mode NetworkFuzzer will use the post_start_handler to start each input generator for each process """ self.log.debug("new_process_handler called") if not self.timeout: self.timeout = 0 process.time_left = int(self.timeout) if process.time_left == 0: process.timeout = False # For saving network session to file # file_type = "raw" # if self.pcap: # file_type = "pcap" # process.session = "/tmp/mal-%d.%s" % (process.index, file_type) # Instead of session files, use packet_queue list # to log all packets, and if necessary, write them to file. process.packet_queue = [] # Redirect stderr to per process log file process.stderr.close() process.stderr_file = "/tmp/mal-%d.stderr" % process.index process.stderr = open(process.stderr_file, "w") # NetworkFuzzer will assume "%p" to # be listening port for the service and use # bind("", 0) to bind to any free port. if "%p" in self.program[0]: process.port = self._random_port() with self._tlock: self.log.debug("Starting new service on port %d" % process.port) self.proxy.service_ports.insert(0, process.port) process.program = self.program[0].replace("%p", str(process.port)) process.program = process.program.split() else: process.port = self.forward_port process.program = self.program[0].split() # On controlled mode, we are responsible # for the input generation if self.MODE_CONTROLLED: process.post_start_handler = self._post_start_handler return process def _random_port(self): return random.randint(10000, 65000) def _post_start_handler(self, process): """ Wait until the service has stopped initializing itself, and then start the input generator. """ self.log.debug("post_start_handler() called") if "%p" in self.input_arguments: self.input_arguments = self.input_arguments.replace("%p", str(self.proxy.listen_port)) self.log.debug("Starting input generator") self.log.debug(self.input_arguments) time.sleep(1) self.inputer.generate(self.input_arguments) def _end_process_handler(self, process): self.log.debug("Ending process pid: %d" % process.pid) try: os.remove("/tmp/mal-%d.stderr" % process.index) self.log.debug("Removed /tmp/mal-%d.stderr" % process.index) except: self.log.error("Could not remove file") def _test_connection(self, port): """ Tries connecting to the service to determine whether it is ready or not. Returns true if the connection was established. """ with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as test_socket: try: test_socket.connect((self.proxy.address, port)) return True except: pass def _save_packet(self, data, index): if self.pcap: self._packet_to_pcap(data, "/tmp/mal-%d.pcap" % index) else: self._packet_to_raw(data, "/tmp/mal-%s.raw" % index) def _packet_to_pcap(self, packet, pcap_file): pass def _packet_to_raw(self, packet, raw_file): with open(raw_file, "ab") as f: f.write(b"===========================") f.write(packet) def _data_handler(self, index, data, processed, malformed): # self._save_packet(data) # self._save_packet(malformed_data, index) # Temporary comment return malformed def input_generator(self): """ This function is only called when input is specifically generated in self.MODE_GENERATE. It calls the input module to generate input and if input is returned, forwards it to the remote host. Otherwise, it is expected that the input module creates the necessary input on its own. """ data = self.inputer.generate(self.input_arguments) if data: self._send_data(self.data_handler(bytes(data), 0)) def _send_data(self, data): """ This function simply forwards data to remote host. It is merely for expected behaviour with the defalt input module. """ address = (self.address, self.listen_port) if self.udp: sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) else: sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.connect(address) if self.udp: while data: sock.sendto(self.data_handler(data[:65535], 0), address) data = data[65535:] else: sock.send(self.data_handler(data, 0)) sock.close() def start(self): self.proxy.start() if self.MODE_GENERATE: self.scheduled_functions.append(self.input_generator) self.main_loop()