def loadPayloadFilter(self): if self.project.payload_filter == None: return True if not os.path.exists(self.project.payload_filter): log.warn("Payload filter (file: '%s') does not exist!" % self.project.payload_filter) return False saved_sys_path = sys.path try: payload_filter_file_without_ext = os.path.splitext( self.project.payload_filter)[0] payload_filter_module_path = os.path.dirname( payload_filter_file_without_ext) payload_filter_module_name = os.path.basename( payload_filter_file_without_ext) sys.path.insert(0, payload_filter_module_path) payload_filter_module = __import__(payload_filter_module_name) self.payload_filter_function = payload_filter_module.payload_filter_function sys.path = saved_sys_path except Exception as e: sys.path = saved_sys_path log.warn("loadPayloadFilter: " + str(e)) log.debug("Full Stack Trace:\n" + traceback.format_exc()) return False sys.path = saved_sys_path return True
def checkAndCreateSubfolders(self): """ Check whether alls necessary subdirectories exist in the project folder. Create them if necessary. """ if not os.path.exists(self.project_dir): log.warn("Project directory '%s' does not exist." % self.project_dir) return False if not os.path.exists(self.debug_dir): os.mkdir(self.debug_dir) if os.path.exists(self.debug_dir + "/history"): log.debug("Deleting old Debug file: " + self.debug_dir + "/history") os.remove(self.debug_dir + "/history") #if not os.path.exists(self.coverage_dir): # os.mkdir(self.coverage_dir) if not os.path.exists(self.crash_dir): os.mkdir(self.crash_dir) if not os.path.exists(self.corpus_dir): os.mkdir(self.corpus_dir) if not os.path.exists(self.corpus_trash_dir): os.mkdir(self.corpus_trash_dir) return True
def fuzz(args, fuzzer): #TODO (maybe? save the old history file.. do we need this?) # history_file = self.project_dir + "/frida_fuzzer.history" # if os.path.exists(history_file): # backup_file = self.project_dir + time.strftime("/%Y%m%d_%H%M%S_frida_fuzzer.history") # shutil.move(history_file, backup_file) # log.info("Found old history file. Moving it to %s" % backup_file) if fuzzer.buildCorpus(): log.debug("Corpus: " + str(fuzzer.corpus)) fuzzer.fuzzerLoop()
def doReplay(self): """ This function replays the last Session. This function will later implement also probes to test when the process is crashing """ log.info("Starting the full Replay") with open(self.project.project_dir + "/frida_fuzzer.history") as fp: for line in fp: pkt_file, seed = line.split("|") try: fuzz_pkt = self.getMutatedPayload(pkt_file, int(seed.strip())) if fuzz_pkt == None: continue if self.project.debug_mode: open(self.project.debug_dir + "/history", "a").write("file: {} seed: {} \n{}\n".format( pkt_file, seed, fuzz_pkt, )) coverage = self.getCoverageOfPayload(fuzz_pkt) log.info("Current iteration: " + time.strftime("%Y-%m-%d %H:%M:%S") + " [seed=%d] [file=%s]" % (int(seed.strip()), pkt_file)) except (frida.TransportError, frida.InvalidOperationError, frida.core.RPCException) as e: log.finish_update("doReplay: Got a frida error: " + str(e)) log.debug("Full Stack Trace:\n" + traceback.format_exc()) log.finish_update("Current iteration: " + time.strftime("%Y-%m-%d %H:%M:%S") + " [seed=%d] [file=%s]" % (int(seed.strip()), pkt_file)) log.finish_update("Server Crashed! Lets narrow it down") #crash_file = self.crash_dir + time.strftime("/%Y%m%d_%H%M%S_crash") #with open(crash_file, "wb") as f: # f.write(fuzz_pkt) #log.info("Payload is written to " + crash_file) return False if coverage == None: log.warn("No coverage was generated for [%d] %s!" % (seed, pkt_file)) log.warn("Replay did not crash the Server!") return False
def runPayloadFilterFunction(self, fuzz_pkt): """ Returns a filtered version of the fuzz payload (as bytes object) or None if the payload should not be used by the fuzzer. The payload is first passed through the user-provided payload filter (if specified). The filter may modify the payload before returning or decide to not return any payload (None) in which case the fuzzer should skip the payload. """ if self.payload_filter_function != None: try: fuzz_pkt = self.payload_filter_function(fuzz_pkt) except Exception as e: log.warn("The payload filter '%s' caused an exception: %s" % (self.project.payload_filter, str(e))) log.debug("Full Stack Trace:\n" + traceback.format_exc()) if not isinstance(fuzz_pkt, bytes) and fuzz_pkt != None: log.warn( "The payload filter '%s' returned unsupported type: '%s'." % (self.project.payload_filter, str(type(fuzz_pkt)))) return None return fuzz_pkt
def doIteration(self, seed=None, corpus=None): if seed == None: seed = self.project.seed if corpus == None: corpus = self.corpus start_time = time.time() for pkt_file in corpus: log.update("[seed=%d] " % seed + time.strftime("%Y-%m-%d %H:%M:%S") + " %s" % pkt_file) fuzz_pkt = self.getMutatedPayload(pkt_file, seed) if fuzz_pkt == None: continue # Writing History file for replaying open(self.project.project_dir + "/frida_fuzzer.history", "a").write(str(pkt_file) + "|" + str(seed) + "\n") try: coverage = self.getCoverageOfPayload(fuzz_pkt) except (frida.TransportError, frida.InvalidOperationError, frida.core.RPCException) as e: log.warn("doIteration: Got a frida error: " + str(e)) log.debug("Full Stack Trace:\n" + traceback.format_exc()) log.info("Current iteration: " + time.strftime("%Y-%m-%d %H:%M:%S") + " [seed=%d] [file=%s]" % (seed, pkt_file)) crash_file = self.project.crash_dir + time.strftime( "/%Y%m%d_%H%M%S_crash") with open( crash_file + "_" + str(self.active_target.process_pid), "wb") as f: f.write(fuzz_pkt) log.info("Payload is written to " + crash_file) self.project.crashes += 1 return False if coverage == None: log.warn("No coverage was generated for [%d] %s!" % (seed, pkt_file)) continue if not coverage.issubset(self.accumulated_coverage): # New basic blocks covered! log.info("Found new path: [%d] %s" % (seed, pkt_file)) newfile = open( self.project.corpus_dir + "/" + str(seed) + "_" + pkt_file.split("/")[-1], "wb") newfile.write(fuzz_pkt) newfile.close() cov_file = self.project.coverage_dir + "/" + str( seed) + "_" + pkt_file.split("/")[-1] write_drcov_file(self.active_target.modules, coverage, cov_file) write_drcov_file( self.active_target.modules, coverage.difference(self.accumulated_coverage), cov_file + "_diff") self.project.last_new_path = seed self.accumulated_coverage = self.accumulated_coverage.union( coverage) write_drcov_file( self.active_target.modules, self.accumulated_coverage, self.project.coverage_dir + "/accumulated_coverage.drcov") self.total_executions += 1 end_time = time.time() speed = len(corpus) / (end_time - start_time) avg_speed = self.total_executions / (end_time - self.start_time) log.finish_update( "[seed=%d] speed=[%3d exec/sec (avg: %d)] coverage=[%d bblocks] corpus=[%d files] " "last new path: [%d] crashes: [%d]" % (seed, speed, avg_speed, len(self.accumulated_coverage), len(corpus), self.project.last_new_path, self.project.crashes)) return True
def getCoverageOfPayload(self, payload, timeout=0.04, retry=0): """ Sends of the payload and checks the returned coverage. If the payload_filter was specified by the user, the payload will first be passed through it. All targets will then be checked for coverage. The function only succeeds if just one target has produced a coverage. Important: Frida appears to have a bug sometimes in collecting traces with the stalker.. no idea how to fix this yet.. hence we do a retry. This can however screw up the replay functionality and should be fixed in the future. Arguments: payload {bytes} -- payload which shall be sent to the target Keyword Arguments: timeout {float} -- [description] (default: {0.1}) retry {int} -- [description] (default: {5}) Returns: {set} -- set of basic blocks covered by the payload """ payload = self.runPayloadFilterFunction(payload) if payload == None: return set() cov = None cnt = 0 while cnt <= retry: # Clear coverage info in all targets: for target in self.targets: target.frida_script.exports.clearcoverage() # Send payload if self.project.fuzz_in_process: self.sendFuzzPayloadInProcess(payload) else: self.sendFuzzPayload(payload) # Wait for timeout seconds for any of the stalkers to get attached target, stalker_attached, stalker_finished = self.waitForCoverage( timeout) if target != None: # Found a target that has attached their stalker. Wait for the stalker # to finish and then extract the coverage. # Wait for 1 second <- maybe this should be adjusted / configurable ? start = time.time() while not stalker_finished and (time.time() - start) < 1: stalker_attached, stalker_finished = target.frida_script.exports.checkstalker( ) if not stalker_finished: log.info( "getCoverageOfPayload: Stalker did not finish after 1 second!" ) break cov = target.frida_script.exports.getcoverage() if cov != None and len(cov) > 0: break else: # None of the targets' function was hit. next try.. cnt += 1 if cov == None or len(cov) == 0: log.debug("getCoverageOfPayload: got nothing!") return set() return parse_coverage(cov, self.active_target.watched_modules)