class DebugServerManager(ServerManager): def __init__(self, config, queue_sync, queue_out, targetPort): ServerManager.__init__(self, config, queue_sync, queue_out, targetPort) self.dbg = None self.crashEvent = None self.proc = None self.p = None def _startServer(self): # create child via ptrace debugger # API: createChild(arguments[], no_stdout, env=None) logging.debug("START: " + str( serverutils.getInvokeTargetArgs(self.config, self.targetPort + 1000))) self.pid = createChild( serverutils.getInvokeTargetArgs(self.config, self.targetPort), False, # no_stdout None, ) # Attach to the process with ptrace and let it run self.dbg = PtraceDebugger() self.proc = self.dbg.addProcess(self.pid, True) self.proc.cont() time.sleep(1) # i dont think this works here... # FIXME event = self.dbg.waitProcessEvent(blocking=False) if event is not None and type(event) == ProcessExit: logging.error("Started server, but it already exited: " + str(event)) return False return True def _stopServer(self): try: self.dbg.quit() os.kill(self.pid, signal.SIGTERM) except: # is already dead... pass def _waitForCrash(self): #subprocess.call("echo AAA1; ls -l /proc/" + str(self.pid), shell=True) while True: logging.info("DebugServer: Waiting for process event") event = self.dbg.waitProcessEvent() logging.info("DebugServer: Got event: " + str(event)) # If this is a process exit we need to check if it was abnormal if type(event) == ProcessExit: if event.signum is None or event.exitcode == 0: # Clear the event since this was a normal exit event = None # If this is a signal we need to check if we're ignoring it elif type(event) == ProcessSignal: if event.signum == signal.SIGCHLD: # Ignore these signals and continue waiting continue elif event.signum == signal.SIGTERM: # server cannot be started, return event = None self.queue_sync.put(("err", event.signum)) break if event is not None and event.signum != 15: logging.info("DebugServer: Event Result: Crash") #subprocess.call("echo AAA2; ls -l /proc/" + str(self.pid), shell=True) self.crashEvent = event return True else: logging.info("DebugServer: Event Result: No crash") self.crashEvent = None return False def _getCrashDetails(self): event = self.crashEvent # Get the address where the crash occurred faultAddress = 0 try: faultAddress = event.process.getInstrPointer() except Exception as e: # process already dead, hmm print(("GetCrashDetails exception: " + str(e))) # Find the module that contains this address # Now we need to turn the address into an offset. This way when the process # is loaded again if the module is loaded at another address, due to ASLR, # the offset will be the same and we can correctly detect those as the same # crash module = None faultOffset = 0 try: for mapping in event.process.readMappings(): if faultAddress >= mapping.start and faultAddress < mapping.end: module = mapping.pathname faultOffset = faultAddress - mapping.start break except Exception as error: print("getCrashDetails Exception: " + str(error)) # it always has a an exception... pass # Apparently the address didn't fall within a mapping if module is None: module = "Unknown" faultOffset = faultAddress # Get the signal sig = event.signum # Get the details of the crash details = None details = "" stackAddr = 0 stackPtr = 0 backtraceFrames = None pRegisters = None try: if event._analyze() is not None: details = event._analyze().text # more data stackAddr = self.proc.findStack() stackPtr = self.proc.getStackPointer() # convert backtrace backtrace = self.proc.getBacktrace() backtraceFrames = [] for frame in backtrace.frames: backtraceFrames.append(str(frame)) # convert registers from ctype to python registers = self.proc.getregs() pRegisters = {} for field_name, field_type in registers._fields_: regName = str(field_name) regValue = str(getattr(registers, field_name)) pRegisters[regName] = regValue except Exception as e: # process already dead, hmm print(("GetCrashDetails exception: " + str(e))) vCrashData = verifycrashdata.VerifyCrashData( faultAddress=faultAddress, faultOffset=faultOffset, module=module, sig=sig, details=details, stackPointer=stackPtr, stackAddr=str(stackAddr), backtrace=backtraceFrames, registers=pRegisters, ) asanOutput = serverutils.getAsanOutput(self.config, self.pid) vCrashData.setTemp(asanOutput) return vCrashData
def doFuzz(config, setupEnvironment=_setupEnvironment, chooseInput=_chooseInput, generateSeed=_generateSeed, runFuzzer=_runFuzzer, runTarget=_runTarget, checkForCrash=_checkForCrash, handleOutcome=_handleOutcome): seed = 0 count = 0 haveOutcome = False outcome = None done = False # Step 1: Setup environment setupEnvironment(config) print "Running fuzzer:", config["fuzzer"] sys.stdout.write("%8d: " % (0)) sys.stdout.flush() while not done: # Step 2: Choose an input inFile = chooseInput(config) # We're done if no input is returned if inFile is None: print "\nNo more inputs, exiting." break # Step 3: Generate a seed seed = generateSeed(config) # Generate a name for the output file outExt = os.path.splitext(inFile)[1] outFile = os.path.join(os.getcwd(), str(seed) + outExt) # Step 4: Run fuzzer runFuzzer(config, inFile, seed, outFile, count) # Step 5: Run the target pid = runTarget(config, outFile) ####################################################################### # This is where the magic happens. We monitor the process to determine # if it has crashed # Attach to the process with ptrace dbg = PtraceDebugger() proc = dbg.addProcess(pid, True) proc.cont() # Calculate the maximum time the target will be allowed to run endTime = time.time() + config["target_timeout"] outcome = None while True: try: # Check if there is an event pending for the target applicaiton # This will return immediately with either an event or None if # there is no event. We do this so we can kill the target after # it reaches the timeout event = dbg.waitProcessEvent(blocking=False) # Check if the process exited if type(event) == ProcessExit: # Step 6: Check for crash outcome = checkForCrash(config, event) # The target application exited so we're done here break elif type(event) == ProcessSignal: # SIGCHLD simply notifies the parent that one of it's # children processes has exited or changed (exec another # process). It's not a bug so we tell the process to # continue and we loop again to get the next event if event.signum == SIGCHLD: event.process.cont() continue outcome = checkForCrash(config, event) break except KeyboardInterrupt: done = True break # Check if the process has reached the timeout if time.time() >= endTime: break else: # Give the CPU some timeslices to run other things time.sleep(0.1) # Step 7: Handle any crashes if outcome is not None: handleOutcome(config, outcome, inFile, seed, outFile, count) haveOutcome = True # Done with the process proc.terminate() # Delete the output try: os.remove(outFile) except: print "Failed to remove file %s!" % outFile # Update the counter and display the visual feedback count += 1 if count % 2 == 0: if haveOutcome: sys.stdout.write("!") haveOutcome = False else: sys.stdout.write(".") sys.stdout.flush() if count % 100 == 0: sys.stdout.write("\n%8d: " % count) sys.stdout.flush()