""" Start IDA Pro in autonomous mode to export images of function graphs. Example usage: start_ida_export_fimages.py -f start_ida_export_fimages.py test.exe imgs -f 0x401000,0x402F90 """ import os import imp import sys import hashlib import logging import subprocess import argparse try: imp.find_module("graphviz") from graphviz import Source graphviz_found = True except ImportError: graphviz_found = False IDA32_PATH = "C:\\Program Files\\IDA Pro 7.3\\ida.exe" IDA64_PATH = "C:\\Program Files\\IDA Pro 7.3\\ida64.exe" # expected in same directory as this file EXPORT_SCRIPT_PATH = os.path.abspath("_export_fimages.py") logger = logging.getLogger(__name__) def export_fimages(file_path, out_dir, functions, manual=False): """ Export images of function graphs. :param file_path: file to analyze :param out_dir: output directory :param functions: list of strings of hex formatted fvas :param manual: non-autonomous mode :return: True on success, False otherwise """ if not graphviz_found: logger.warning("please install graphviz to export images") return False if not os.path.exists(out_dir): os.mkdir(out_dir) script_args = [os.path.abspath(out_dir)] + functions call_ida_script(EXPORT_SCRIPT_PATH, script_args, file_path, manual) img_count = 0 for root, dirs, files in os.walk(out_dir): for file in files: if not file.endswith(".dot"): continue try: s = Source.from_file(file, directory=out_dir) s.render(file, directory=out_dir, format="png", cleanup=True) img_count += 1 except BaseException: logger.warning("graphviz error rendering file") if img_count > 0: logger.info('exported %d function graph images to "%s"', img_count, os.path.abspath(out_dir)) return True else: logger.warning("failed to export function graph images") return False def call_ida_script(script_path, script_args, sample_path, manual): logger.info("processing %s (MD5: %s)", sample_path, get_md5_hexdigest(sample_path)) # TODO detect 64-bit binaries if os.path.splitext(sample_path)[-1] == ".i64": IDA_PATH = IDA64_PATH else: IDA_PATH = IDA32_PATH args = [IDA_PATH, "-A", "-S%s %s" % (script_path, " ".join(script_args)), sample_path] if manual: args.remove("-A") logger.debug('calling "%s"' % " ".join(args)) if subprocess.call(args) == 0: return True else: return False def get_md5_hexdigest(sample_path): m = hashlib.md5() with open(sample_path, "rb") as f: m.update(f.read()) return m.hexdigest() def main(): parser = argparse.ArgumentParser( description="Launch IDA Pro in autonomous mode to export images of function graphs" ) parser.add_argument("file_path", type=str, help="File to export from") parser.add_argument("out_dir", type=str, help="Export target directory") parser.add_argument("-f", "--functions", action="store", help="Comma separated list of functions to export") parser.add_argument("-m", "--manual", action="store_true", help="Manual mode: show IDA dialog boxes") parser.add_argument("-v", "--verbose", action="store_true", help="Enable verbose output") args = parser.parse_args(args=sys.argv[1:]) if args.verbose: logging.basicConfig(level=logging.DEBUG) logging.getLogger().setLevel(logging.DEBUG) else: logging.basicConfig(level=logging.INFO) logging.getLogger().setLevel(logging.INFO) if not os.path.isfile(args.file_path): logger.warning("%s is not a file", args.file_path) return -1 functions = args.functions.split(",") export_fimages(args.file_path, args.out_dir, functions, args.manual) return 0 if __name__ == "__main__": sys.exit(main())