class PykdTarget(ServerTarget): ''' PykdTarget will run an application for each fuzzed payloads. To use PykdTarget your need install PYkd and Windbg first. Document link https://pykd.codeplex.com/documentation. ''' def __init__(self, name, process_path, process_args=[], break_points=[], handler=None, logger=None, timeout=3): ''' :param name: name of the object. :param process_path: path to the target executable. :param process_args: arguments to pass to the process. :param break_points: break points to set. :param handler: pykd event handler. :param logger: logger for this object (default: None) :param timeout: seconds to wait for the process before kill (default: 3) :example: :: PykdTarget( name='PykdTarget', process_path="/tmp/myApp", process_args=['-a', '-c'] handler=MyEventHandler, break_points=[], timeout=2) Will run ``/tmp/myApp -a -c mutational_data`` using pykd for evey mutation with timeout of 2 seconds ''' super(PykdTarget, self).__init__(name, logger) assert (process_path) assert (os.path.exists(process_path)) self._process_path = process_path self._process_name = os.path.basename(process_path) self._process_args = process_args self.process_data = None self._break_points = break_points self._handler = handler self._timeout = timeout self._process = None self._pid = None self._system_pid = None self.is_crash = threading.Event() self.crash_dump_finished = threading.Event() def _get_exe_status(self): try: status = str(pykd.getExecutionStatus()) return status except Exception as err: self.logger.debug( "Can't get process execution status because of: %s" % err) return None def _wait_break(self, timeout=2): ''' :param timeout: timeout in seconds. (default: 2) :return: True if Server is in break status, False otherwise ''' end_time = time.time() + timeout delay = 0.0005 # 500 us -> initial delay of 1 ms while True: remaining = end_time - time.time() if remaining <= 0: return False time.sleep(delay) try: status = self._get_exe_status() if status == 'Break': self.logger.debug('Process is in break status') return True except Exception as err: self.logger.error( "Received exception when wait process break: %s" % err) continue def _get_correct_process_id(self): while self._pid == 0xffffffff: try: self._pid = pykd.getCurrentProcessId() except Exception as err: self.logger.debug( "Can't get correct process id because of: %s" % err) continue def _debug_server(self): ''' debugger thread ''' self._system_pid = None self.logger.info('Init pykd environment') pykd.initialize() try: # Start a new process for debugging argv = [self._process_path ] + self._process_args + self.process_data argv = ' '.join(argv) self.logger.debug('Debugger starting server: %s' % argv) try: self.logger.info('Start running program with cmd:"%s"' % argv) self.report.add('cmd', argv) self._pid = pykd.startProcess(argv) self._get_correct_process_id() self.logger.debug('Process started. pykd_pid=%d' % self._pid) self._process = pykd.getCurrentProcess() self.logger.debug('Process is %s' % hex(self._process)) except WindowsError: self.logger.error('debug_server received exception', traceback.fmt_exc()) # Get Process System ID self._wait_break() while self._system_pid is None: try: self._system_pid = pykd.getProcessSystemID(self._pid) self.logger.info('process system_id=%d' % self._system_pid) except Exception as err: self.logger.debug("Get system id fail because of: %s" % err) continue # Set break points if self._wait_break(): self.logger.info( "Server is in break status setting break points") for bp in self._break_points: pykd.setBp(bp) self.logger.info("Start register event handle") # This will register our handle handler = self._handler(self) self.logger.debug('Handler object is : %s' % handler) self.logger.info('Go !!!!!') pykd.go() except: self.logger.error('Got an exception in _debug_server') self.logger.error(traceback.format_exc()) def _start_server_thread(self): ''' start the server thread ''' self._server_thread = FuncThread(self._debug_server) self._server_thread.start() def _kill_all_processes(self): ''' kill all processes with the same name :return: True if all matching processes were killed properly, False otherwise ''' if self._process: try: status = self._get_exe_status() self.logger.debug("Current status is %s start kill process" % status) if status == 'Break': self.logger.info( "Process is in break status, kill process now") pykd.killAllProcesses() self._pid = None self._process = None else: self.logger.info("Break in process, kill process now") pykd.breakin() self._wait_break() # TODO: need find a way to replace time.sleep time.sleep(0.05) pykd.killAllProcesses() self._pid = None self._process = None except: self.logger.error('failed to kill process [%s]' % traceback.format_exc()) return False return True def teardown(self): self._stop_process() self._pid = None self._process = None super(PykdTarget, self).teardown() def pre_test(self, test_number): super(PykdTarget, self).pre_test(test_number) self.is_crash.clear() self.crash_dump_finished.clear() self._stop_process() def post_test(self, test_num): if self.is_crash.wait(self._timeout): self.crash_dump_finished.wait(self._timeout) self._stop_process() # for each test case we need deinitialize the pykd, other wise event handle will not work. pykd.deinitialize() super(PykdTarget, self).post_test(test_num) def _send_to_target(self, payload): ''' :param payload: payload to send (might be file path) ''' self.logger.debug('send called') self.process_data = [payload] self._start_server_thread() def _stop_process(self): ''' Stop the process (if running) ''' return self._kill_all_processes() def _restart(self): ''' restart the process ''' self._stop_process() self._server_thread.join(1) self._start_server_thread()
class WinAppDbgTarget(ServerTarget): def __init__(self, name, process_path, process_args=[], sql_crash_db='sqlite:///crashes.sqlite', logger=None): ''' :param name: name of the object :param process_path: path to the target executable :param process_args: arguments to pass to the process :param attach: try to attach if process path :param sql_crash_db: sql alchemy connection string to crash db (default:sqlite:///crashes.sqlite) :param logger: logger for this object (default: None) ''' super(WinAppDbgTarget, self).__init__(name, logger) assert (process_path) assert (os.path.exists(process_path)) self._process_path = process_path self._process_name = os.path.basename(process_path) self._process_args = process_args self._process = None self._sql_crash_db = sql_crash_db self._crash_event_complete = threading.Event() self._server_is_up = threading.Event() self._crash_event_complete.set() self._debug = Debug(lambda x: _my_event_handler(self, x), bKillOnExit=True) def _debug_server(self): ''' debugger thread ''' try: self._process = None # Start a new process for debugging. argv = [self._process_path] + self._process_args self.logger.debug('debugger starting server: %s' % argv) try: self._process = self._debug.execv(argv, bFollow=True) except WindowsError: self.logger.error('debug_server received exception', traceback.fmt_exc()) self._pid = self._process.get_pid() self.logger.info('process started. pid=%d' % self._pid) # Wait for the debugee to finish. self._server_is_up.set() self._debug.loop() except: self.logger.error('Got an exception in _debug_server') self.logger.error(traceback.format_exc()) # Stop the debugger. finally: self._debug.stop() self._process = None self._pid = -1 self._crash_event_complete.set() def _start_server_thread(self): ''' start the server thread ''' self._server_is_up.clear() self.server_thread = FuncThread(self._debug_server) self.server_thread.start() self.logger.info('waiting for server to be up') self._server_is_up.wait() self.logger.info('server should be up') def _kill_all_processes(self): ''' kill all processes with the same name :return: True if all matching processes were killed properly, False otherwise ''' res = True # Lookup the currently running processes. self._debug.system.scan_processes() # For all processes that match the requested filename... for (process, name) in self._debug.system.find_processes_by_filename( self._process_name): process_pid = process.get_pid() self.logger.info('found process %s (%d) - trying to kill it' % (name, process_pid)) try: process.kill() self.logger.info('successfully killed %s (%d)' % (name, process_pid)) except: self.logger.error('failed to kill %s (%d) [%s]' % (name, process_pid, traceback.format_exc())) res = False return res def teardown(self): self._stop_process() self._process = None super(WinAppDbgTarget, self).teardown() def pre_test(self, test_number): # we need kill all process before fuzzing super(WinAppDbgTarget, self).pre_test(test_number) self._stop_process() def post_test(self, test_num): self.logger.debug('in') time.sleep(1) self.logger.debug('after sleep') res = self._crash_event_complete.wait() self.logger.debug('after wait') if not res: self.report.failed('incomplete crash detected') super(WinAppDbgTarget, self).post_test(test_num) self.logger.debug('out') def _send_to_target(self, data): ''' this is the key off windbgtarget :param data: data is the file path ''' self.logger.info('send called') ''' starting ''' self._process_args = [data] # this may need implement self._start_server_thread() def _stop_process(self): ''' Stop the process (if running) ''' return self._kill_all_processes() def _stop_process_old(self): ''' :return: True if process was killed, False otherwise ''' if self._is_victim_alive(): self._process.kill() time.sleep(0.5) if self._is_victim_alive(): self._process.kill() time.sleep(0.5) if self._is_victim_alive(): raise Exception('Failed to kill client process') self._debug.stop() return True else: self._debug.stop() return False def _restart(self): ''' restart the process ''' self._stop_process() self.server_thread.join(1) time.sleep(3) self._server_is_up.clear() self._start_server_thread() def _is_victim_alive(self): ''' check if process running ''' if self._process: self.logger.debug('process pid: %d' % self._pid) is_alive = self._process.is_alive() is_debugee_started = self._debug.is_debugee_started(self._pid) self.logger.debug('is_alive = %s' % is_alive) self.logger.debug('is_debugee_started = %s' % is_debugee_started) return (is_alive and is_debugee_started) else: self.logger.debug('_process is None') return False
class PykdTarget(ServerTarget): ''' PykdTarget will run an application for each fuzzed payloads. To use PykdTarget your need install PYkd and Windbg first. Document link https://pykd.codeplex.com/documentation. ''' def __init__(self, name, process_path, process_args=[], break_points=[], handler=None, logger=None, timeout=3): ''' :param name: name of the object. :param process_path: path to the target executable. :param process_args: arguments to pass to the process. :param break_points: break points to set. :param handler: pykd event handler. :param logger: logger for this object (default: None) :param timeout: seconds to wait for the process before kill (default: 3) :example: :: PykdTarget( name='PykdTarget', process_path="/tmp/myApp", process_args=['-a', '-c'] handler=MyEventHandler, break_points=[], timeout=2) Will run ``/tmp/myApp -a -c mutational_data`` using pykd for evey mutation with timeout of 2 seconds ''' super(PykdTarget, self).__init__(name, logger) assert(process_path) assert(os.path.exists(process_path)) self._process_path = process_path self._process_name = os.path.basename(process_path) self._process_args = process_args self.process_data = None self._break_points = break_points self._handler = handler self._timeout = timeout self._process = None self._pid = None self._system_pid = None self.is_crash = threading.Event() self.crash_dump_finished = threading.Event() def _get_exe_status(self): try: status = str(pykd.getExecutionStatus()) return status except Exception as err: self.logger.debug("Can't get process execution status because of: %s" % err) return None def _wait_break(self, timeout=2): ''' :param timeout: timeout in seconds. (default: 2) :return: True if Server is in break status, False otherwise ''' end_time = time.time() + timeout delay = 0.0005 # 500 us -> initial delay of 1 ms while True: remaining = end_time - time.time() if remaining <= 0: return False time.sleep(delay) try: status = self._get_exe_status() if status == 'Break': self.logger.debug('Process is in break status') return True except Exception as err: self.logger.error("Received exception when wait process break: %s" % err) continue def _get_correct_process_id(self): while self._pid == 0xffffffff: try: self._pid = pykd.getCurrentProcessId() except Exception as err: self.logger.debug("Can't get correct process id because of: %s" % err) continue def _debug_server(self): ''' debugger thread ''' self._system_pid = None self.logger.info('Init pykd environment') pykd.initialize() try: # Start a new process for debugging argv = [self._process_path] + self._process_args + self.process_data argv = ' '.join(argv) self.logger.debug('Debugger starting server: %s' % argv) try: self.logger.info('Start running program with cmd:"%s"' % argv) self.report.add('cmd', argv) self._pid = pykd.startProcess(argv) self._get_correct_process_id() self.logger.debug('Process started. pykd_pid=%d' % self._pid) self._process = pykd.getCurrentProcess() self.logger.debug('Process is %s' % hex(self._process)) except WindowsError: self.logger.error('debug_server received exception', traceback.fmt_exc()) # Get Process System ID self._wait_break() while self._system_pid is None: try: self._system_pid = pykd.getProcessSystemID(self._pid) self.logger.info('process system_id=%d' % self._system_pid) except Exception as err: self.logger.debug("Get system id fail because of: %s" % err) continue # Set break points if self._wait_break(): self.logger.info("Server is in break status setting break points") for bp in self._break_points: pykd.setBp(bp) self.logger.info("Start register event handle") # This will register our handle handler = self._handler(self) self.logger.debug('Handler object is : %s' % handler) self.logger.info('Go !!!!!') pykd.go() except: self.logger.error('Got an exception in _debug_server') self.logger.error(traceback.format_exc()) def _start_server_thread(self): ''' start the server thread ''' self._server_thread = FuncThread(self._debug_server) self._server_thread.start() def _kill_all_processes(self): ''' kill all processes with the same name :return: True if all matching processes were killed properly, False otherwise ''' if self._process: try: status = self._get_exe_status() self.logger.debug("Current status is %s start kill process" % status) if status == 'Break': self.logger.info("Process is in break status, kill process now") pykd.killAllProcesses() self._pid = None self._process = None else: self.logger.info("Break in process, kill process now") pykd.breakin() self._wait_break() # TODO: need find a way to replace time.sleep time.sleep(0.05) pykd.killAllProcesses() self._pid = None self._process = None except: self.logger.error('failed to kill process [%s]' % traceback.format_exc()) return False return True def teardown(self): self._stop_process() self._pid = None self._process = None super(PykdTarget, self).teardown() def pre_test(self, test_number): super(PykdTarget, self).pre_test(test_number) self.is_crash.clear() self.crash_dump_finished.clear() self._stop_process() def post_test(self, test_num): if self.is_crash.wait(self._timeout): self.crash_dump_finished.wait(self._timeout) self._stop_process() # for each test case we need deinitialize the pykd, other wise event handle will not work. pykd.deinitialize() super(PykdTarget, self).post_test(test_num) def _send_to_target(self, payload): ''' :param payload: payload to send (might be file path) ''' self.logger.debug('send called') self.process_data = [payload] self._start_server_thread() def _stop_process(self): ''' Stop the process (if running) ''' return self._kill_all_processes() def _restart(self): ''' restart the process ''' self._stop_process() self._server_thread.join(1) self._start_server_thread()
class WinAppDbgController(BaseController): ''' WinAppDbgController controls a server process by starting it on setup making sure it stays up. It uses winappdbg to attach to the target processes. ''' def __init__(self, name, process_path, process_args=[], sql_crash_db='sqlite:///crashes.sqlite', logger=None): ''' :param name: name of the object :param process_path: path to the target executable :param process_args: arguments to pass to the process :param attach: try to attach if process path :param sql_crash_db: sql alchemy connection string to crash db (default:sqlite:///crashes.sqlite) :param logger: logger for this object (default: None) ''' super(WinAppDbgController, self).__init__(name, logger) assert(process_path) assert(os.path.exists(process_path)) self._process_path = process_path self._process_name = os.path.basename(process_path) self._process_args = process_args self._process = None self._sql_crash_db = sql_crash_db self._crash_event_complete = threading.Event() self._server_is_up = threading.Event() self._crash_event_complete.set() self._debug = Debug(lambda x: _my_event_handler(self, x), bKillOnExit=True) def _debug_server(self): ''' debugger thread ''' try: self._process = None # Start a new process for debugging. argv = [self._process_path] + self._process_args self.logger.debug('debugger starting server: %s' % argv) try: self._process = self._debug.execv(argv, bFollow=True) except WindowsError: self.logger.error('debug_server received exception', traceback.fmt_exc()) self._pid = self._process.get_pid() self.logger.info('process started. pid=%d' % self._pid) # Wait for the debugee to finish. self._server_is_up.set() self._debug.loop() except: self.logger.error('Got an exception in _debug_server') self.logger.error(traceback.format_exc()) # Stop the debugger. finally: self._debug.stop() self._process = None self._pid = -1 self._crash_event_complete.set() def _start_server_thread(self): ''' start the server thread ''' self._server_is_up.clear() self.server_thread = FuncThread(self._debug_server) self.server_thread.start() self.logger.info('waiting for server to be up') self._server_is_up.wait() self.logger.info('server should be up') def _kill_all_processes(self): ''' kill all processes with the same name :return: True if all matching processes were killed properly, False otherwise ''' res = True # Lookup the currently running processes. self._debug.system.scan_processes() # For all processes that match the requested filename... for (process, name) in self._debug.system.find_processes_by_filename(self._process_name): process_pid = process.get_pid() self.logger.info('found process %s (%d) - trying to kill it' % (name, process_pid)) try: process.kill() self.logger.info('successfully killed %s (%d)' % (name, process_pid)) except: self.logger.error('failed to kill %s (%d) [%s]' % (name, process_pid, traceback.format_exc())) res = False return res def setup(self): ''' Called at the beginning of a fuzzing session. Will start the server up. ''' self._stop_process() self._start_server_thread() def teardown(self): self._stop_process() self._process = None super(WinAppDbgController, self).teardown() def pre_test(self, test_number): super(WinAppDbgController, self).pre_test(test_number) if not self._is_victim_alive(): self.logger.error('victim is dead, restarting...') # self.report.failed('server is down during pre_test - failure it probably from previous test (%d)' % (test_number-1)) self._restart() self._crash_event_complete.set() else: self.logger.debug('victim is alive (pid=%d)' % self._pid) def post_test(self): self.logger.debug('in') time.sleep(1) self.logger.debug('after sleep') res = self._crash_event_complete.wait() self.logger.debug('after wait') if not res: self.report.failed('incomplete crash detected') super(WinAppDbgController, self).post_test() self.logger.debug('out') def _stop_process(self): ''' Stop the process (if running) ''' return self._kill_all_processes() def _stop_process_old(self): ''' :return: True if process was killed, False otherwise ''' if self._is_victim_alive(): self._process.kill() time.sleep(0.5) if self._is_victim_alive(): self._process.kill() time.sleep(0.5) if self._is_victim_alive(): raise Exception('Failed to kill client process') self._debug.stop() return True else: self._debug.stop() return False def _restart(self): ''' restart the process ''' self._stop_process() self.server_thread.join(1) time.sleep(3) self._server_is_up.clear() self._start_server_thread() def _is_victim_alive(self): ''' check if process running ''' if self._process: self.logger.debug('process pid: %d' % self._pid) is_alive = self._process.is_alive() is_debugee_started = self._debug.is_debugee_started(self._pid) self.logger.debug('is_alive = %s' % is_alive) self.logger.debug('is_debugee_started = %s' % is_debugee_started) return (is_alive and is_debugee_started) else: self.logger.debug('_process is None') return False