def start_analysis(self, config_str=None): """ Creates new temporary dir. File structure: input files: init.ini, cfain.marshal output files: out.ini, cfaout.marshal """ if config_str: self.current_config = AnalyzerConfig.load_from_str(config_str) binary_filepath = self.guess_filepath() if not binary_filepath: bc_log.error( "File %s does not exit. Please fix path in configuration.", self.current_config.binary_filepath) return bc_log.debug("Using %s as source binary path", binary_filepath) self.current_config.binary_filepath = binary_filepath path = tempfile.mkdtemp(suffix='bincat') # instance variable: we don't want the garbage collector to delete the # *Analyzer instance, killing an unlucky QProcess in the process try: self.analyzer = self.new_analyzer(path, self.analysis_finish_cb) except AnalyzerUnavailable as e: bc_log.error("Analyzer is unavailable", exc_info=True) return bc_log.info("Current analyzer path: %s", path) # Update overrides self.current_config.update_overrides(self.overrides) analysis_method = self.current_config.analysis_method if analysis_method in ("forward_cfa", "backward"): if self.last_cfaout_marshal is None: bc_log.error("No marshalled CFA has been recorded - run a " "forward analysis first.") return with open(self.analyzer.cfainfname, 'wb') as f: f.write(self.last_cfaout_marshal) # Set correct file names # Note: bincat_native expects a filename for in_marshalled_cfa_file - # may not exist if analysis mode is forward_binary self.current_config.set_cfa_options('true', self.analyzer.cfainfname, self.analyzer.cfaoutfname) bc_log.debug("Generating .no files...") headers_filenames = self.current_config.headers_files.split(',') # compile .c files for libs, if there are any bc_log.debug("Initial header files: %r", headers_filenames) new_headers_filenames = [] for f in headers_filenames: f = f.strip() if not f: continue if f.endswith('.c'): # generate .npk from .c file if not os.path.isfile(f): bc_log.warning( "header file %s could not be found, continuing", f) continue new_npk_fname = f[:-2] + '.no' headers_filenames.remove(f) if not os.path.isfile(new_npk_fname): # compile self.analyzer.generate_tnpk(fname=f, destfname=new_npk_fname) if not os.path.isfile(new_npk_fname): bc_log.warning( ".no file containing type data for the headers " "file %s could not be generated, continuing", f) continue f = new_npk_fname # Relative paths are copied elif f.endswith('.no') and os.path.isfile(f): if f[0] != os.path.sep: temp_npk_fname = os.path.join(path, os.path.basename(f)) shutil.copyfile(f, temp_npk_fname) f = temp_npk_fname else: bc_log.warning( "Header file %s does not exist or does not match expected " "extensions (.c, .no), ignoring.", f) continue new_headers_filenames.append(f) headers_filenames = new_headers_filenames # generate npk file for the binary being analyzed (unless it has # already been generated) if not any([s.endswith('pre-processed.no') for s in headers_filenames]): npk_filename = self.analyzer.generate_tnpk() if not npk_filename: bc_log.warning( ".no file containing type data for the file being " "analyzed could not be generated, continuing. The " "ida-generated header could be invalid.") else: headers_filenames.append(npk_filename) bc_log.debug("Final npk files: %r" % headers_filenames) self.current_config.headers_files = ','.join(headers_filenames) self.current_config.write(self.analyzer.initfname) self.analyzer.run()
def run(self): if not self.reachable_server: return if 'requests' not in sys.modules: bc_log.error("python module 'requests' could not be imported, " "so remote BinCAT cannot be used.") return # create temporary AnalyzerConfig to replace referenced file names with # sha256 of their contents with open(self.initfname, 'rb') as f: temp_config = AnalyzerConfig.load_from_str(f.read()) # patch filepath - set filepath to sha256, upload file sha256 = self.sha256_digest(temp_config.binary_filepath) if not self.upload_file(temp_config.binary_filepath, sha256): return temp_config.binary_filepath = sha256 # patch [imports] headers - replace with sha256, upload files files = temp_config.headers_files.split(',') h_shalist = [] for f in files: if not f: continue try: f_sha256 = self.sha256_digest(f) h_shalist.append('"%s"' % f_sha256) except IOError as e: bc_log.error("Could not open file %s" % f, exc_info=1) return if not self.upload_file(f, f_sha256): bc_log.error("Could not upload file %s" % f, exc_info=1) return temp_config.headers_files = ','.join(h_shalist) # patch in_marshalled_cfa_file - replace with file contents sha256 if os.path.exists(self.cfainfname): cfa_sha256 = self.sha256_digest(self.cfainfname) temp_config.in_marshalled_cfa_file = '"%s"' % cfa_sha256 else: temp_config.in_marshalled_cfa_file = '"no-input-file"' temp_config.out_marshalled_cfa_file = '"cfaout.marshal"' # write patched config file init_ini_str = str(temp_config) # --- Run analysis run_res = requests.post(self.server_url + "/analyze", files={'init.ini': ('init.ini', init_ini_str)}) if run_res.status_code != 200: bc_log.error("Error while uploading analysis configuration file " "to BinCAT analysis server (%r)" % run_res.text) return files = run_res.json() if files["errorcode"]: bc_log.error("Error while analyzing file. Bincat output is:\n" "----------------\n%s\n----------------" % self.download_file(files["stdout.txt"])) if not files["out.ini"]: # try to parse out.ini if it exists return with open(self.outfname, 'wb') as outfp: outfp.write(self.download_file(files["out.ini"])) analyzer_log_contents = self.download_file(files["analyzer.log"]) with open(self.logfname, 'wb') as logfp: logfp.write(analyzer_log_contents) bc_log.info("---- stdout+stderr ----------------") bc_log.info(self.download_file(files["stdout.txt"])) bc_log.info("---- logfile ---------------") log_lines = analyzer_log_contents.split('\n') log_lines = dedup_loglines(log_lines, max=100) if len(log_lines) > 100: bc_log.info( "---- Only the last 100 log lines (deduped) are displayed here ---" ) bc_log.info("---- See full log in %s ---" % self.logfname) for line in log_lines: bc_log.info(line.rstrip()) bc_log.info("----------------------------") if "cfaout.marshal" in files: # might be absent (ex. when analysis failed, or not requested) with open(self.cfaoutfname, 'wb') as cfaoutfp: cfaoutfp.write(self.download_file(files["cfaout.marshal"])) self.finish_cb(self.outfname, self.logfname, self.cfaoutfname)