import argparse import os import re from yara import * class YACFormat(object): @staticmethod def write(filename, data): raise NotImplementedError class YACFormatBinary(YACFormat): STR = "binary" EXT = "yac" @staticmethod def write(filename, data): with open(filename, "wb") as f: f.write(data) class YACFormatCHeader(YACFormat): STR = "c_header" EXT = "h" MODE = "w" @staticmethod def write(filename, data): with open(filename, "w") as f: f.write("#ifndef FILE_{}_H\n#define FILE_{}_H\n\n#include \n\n".format(os.path.splitext(os.path.basename(filename))[0], os.path.splitext(os.path.basename(filename))[0])) f.write("{} FILE_{}[{}] = {{\n\t".format("uint8_t", os.path.splitext(os.path.basename(filename))[0], len(data))) i = 0 for d in data: i += 1 f.write("{}{}".format(d, ",\n\t" if (i % 16 == 0) else ", ")) f.write("\n};\n\n#endif\n") DICT_STR_YACFORMAT = { YACFormatBinary.STR : YACFormatBinary, YACFormatCHeader.STR : YACFormatCHeader } def dir_path(string): logger.debug("{}: string = {}".format("dir_path", string)) if os.path.isdir(string) or re.match(r"(^\/|^\.\/|^\.\.\/|^[^/])[^:*?\"<>|\r\n]*\.(yac|h)$", string): return string else: raise TypeError("no valid path") def walk(args): logger = logging.getLogger(__name__) logger.info("Walking files ...") files = [os.path.abspath(os.path.join(dp, f)) for dp, dn, filenames in os.walk(args["input_directory"]) for f in filenames] logger.debug("Files: {}".format(files)) logger.info("Number of files found: {}".format(len(files))) if args["output"].endswith(YACFormatBinary.EXT) or args["output"].endswith(YACFormatCHeader.EXT): yd = YaraDatabase() for file in files: if file.endswith(".json"): logger.info("Compiling file {}".format(file)) yd.add_file(file) data = yd.compile( args["store_identifier_entry"], args["store_identifier_signature"], args["store_index_map_entries"], args["store_index_map_signatures"], args["store_index_map_string_blocks"], args["store_hash"]) args["format"].write(args["output"], data) else: for file in files: if file.endswith(".json"): logger.info("Compiling file {}".format(file)) yd = YaraDatabase() yd.add_file(file) data = yd.compile( args["store_identifier_entry"], args["store_identifier_signature"], args["store_index_map_entries"], args["store_index_map_signatures"], args["store_index_map_string_blocks"], args["store_hash"]) args["format"].write(os.path.join(args["output"], os.path.splitext(os.path.basename(file))[0] + ".yac"), data) if __name__ == "__main__": parser = argparse.ArgumentParser(description='Compile single or multiple yara files') parser.add_argument('-i', '--input-directory', nargs='?', default='.', type=dir_path, help='Input directory (default: %(default)s)') parser.add_argument('-o', '--output', nargs='?', default='.', type=dir_path, help='Output file or directory (default: %(default)s)') parser.add_argument('-f', '--input-file', nargs='?', default='.', type=dir_path, help='Input file (default: %(default)s)') parser.add_argument('--format', nargs='?', default=YACFormatBinary.STR, action='store', choices=[YACFormatBinary.STR, YACFormatCHeader.STR], help='Output file format (default: %(default)s)') parser.add_argument('-v', '--verbose', action="count", default=0, help="Verbosity level") parser.add_argument('--store-identifier-entry', action='store_true', help='Store identifier for entry elements (default: %(default)s)') parser.add_argument('--store-identifier-signature', action='store_true', help='Store identifier for signature elements (default: %(default)s)') parser.add_argument('--store-index-map-entries', action='store_true', help='Store index map for entries (default: %(default)s)') parser.add_argument('--store-index-map-signatures', action='store_true', help='Store identifier for signature elements (default: %(default)s)') parser.add_argument('--store-index-map-string-blocks', action='store_true', help='Store identifier for string blocks (default: %(default)s)') parser.add_argument('--store-hash', action='store_true', help='Store hash for database (default: %(default)s)') args = parser.parse_args() if args.verbose == 0: log_level = logging.WARNING elif args.verbose == 1: log_level = logging.INFO elif args.verbose >= 2: log_level = logging.DEBUG logging.basicConfig(stream=sys.stdout, level=log_level) logger = logging.getLogger(__name__) args = { "input_directory": args.input_directory, "output": args.output, "input_file": args.input_file, "format": DICT_STR_YACFORMAT[args.format], "verbosity": args.verbose, "store_identifier_entry": args.store_identifier_entry, "store_identifier_signature": args.store_identifier_signature, "store_index_map_entries": args.store_index_map_entries, "store_index_map_signatures": args.store_index_map_signatures, "store_index_map_string_blocks": args.store_index_map_string_blocks, "store_hash": args.store_hash } logger.debug("args = {}".format(args)) walk(args)