""" generate_hashes.py: SindriKit compile-time hash generator Usage: generate_hashes.py Reads the structured hash manifest (config/hashes.ini) and emits a C header of pre-computed compile-time hash macros. Supports DJB2 and FNV1A. Anti-Analysis Feature: ---------------------- If the script is invoked with `ON` as the 4th argument, it generates a randomized 32-bit GLOBAL_SEED. Otherwise, it uses a deterministic constant. By randomizing the seed at compile-time, we ensure the resulting API hashes cannot be reversed using standard static analysis rainbow tables. Manifest format --------------- [module::ntdll.dll]: section: hash generated for module + every API below it NtAllocateVirtualMemory LdrLoadDll [extras]: standalone strings; no module hash generated # comments and blank lines are ignored everywhere """ import os import random import re import sys GLOBAL_SEED = 0x5381 def djb2(s: str) -> int: h = GLOBAL_SEED for c in s: h = ((h << 5) + h + ord(c)) & 0xFFFFFFFF return h def fnv1a(s: str) -> int: h = GLOBAL_SEED for c in s: h = (h ^ ord(c)) & 0xFFFFFFFF h = (h * 16777619) & 0xFFFFFFFF return h ALGORITHMS = {"DJB2": djb2, "FNV1A": fnv1a} def to_macro(name: str) -> str: """Convert an API or module name to a valid C macro suffix.""" return re.sub(r"[^A-Za-z0-9]", "_", name).upper() def parse_manifest(path: str): """ Parse hashes.ini and return a list of (group_label, module_name_or_None, [api, ...]). For [module::X] sections, module_name is X (hash generated for the module too). For [extras], module_name is None (no module hash). """ groups = [] current_label = None current_module = None current_apis = [] MODULE_RE = re.compile(r"^\[module::(.+)\]$", re.IGNORECASE) EXTRAS_RE = re.compile(r"^\[extras\]$", re.IGNORECASE) with open(path, "r", encoding="utf-8") as fh: for raw in fh: line = raw.strip() if not line or line.startswith("#"): continue m = MODULE_RE.match(line) if m: if current_label is not None: groups.append((current_label, current_module, current_apis)) current_label = m.group(1) current_module = m.group(1) current_apis = [] continue if EXTRAS_RE.match(line): if current_label is not None: groups.append((current_label, current_module, current_apis)) current_label = "extras" current_module = None current_apis = [] continue if current_label is None: sys.exit(f"[error] Entry '{line}' appears before any section header.") current_apis.append(line) if current_label is not None: groups.append((current_label, current_module, current_apis)) return groups def emit_header(groups, hash_func, algo: str, out_path: str): os.makedirs(os.path.dirname(out_path), exist_ok=True) lines = [ "// AUTO-GENERATED by scripts/generate_hashes.py (DO NOT EDIT)", f"// Algorithm: {algo}", "", "#ifndef SND_GENERATED_HASHES_H", "#define SND_GENERATED_HASHES_H", "", "/**", " * @def SND_HASH_SEED", " * @brief Randomized compile-time seed to thwart static hash analysis.", " * @note Generated uniquely per build to defeat rainbow table lookups.", " */", f"#define SND_HASH_SEED 0x{GLOBAL_SEED:08X}U", "", ] for label, module_name, apis in groups: rule = "// " + ("-" * 74) lines.append(rule) lines.append(f"// {label}") lines.append(rule) if module_name is not None: macro = f"SND_HASH_{to_macro(module_name)}" h = hash_func(module_name.lower()) lines.append(f"#define {macro:<55} 0x{h:08X}U // module") for api in apis: macro = f"SND_HASH_{to_macro(api)}" h = hash_func(api) lines.append(f"#define {macro:<55} 0x{h:08X}U") lines.append("") lines += ["#endif // SND_GENERATED_HASHES_H", ""] new_content = "\n".join(lines) if os.path.exists(out_path): with open(out_path, "r", encoding="utf-8") as fh: if fh.read() == new_content: return # Skip writing to preserve timestamp with open(out_path, "w", encoding="utf-8") as fh: fh.write(new_content) if __name__ == "__main__": if len(sys.argv) not in (4, 5): print("Usage: generate_hashes.py [RANDOMIZE=OFF|ON]") sys.exit(1) in_path, out_path, algo = sys.argv[1], sys.argv[2], sys.argv[3].upper() if len(sys.argv) == 5 and sys.argv[4].upper() == "ON": GLOBAL_SEED = random.randint(0x10000000, 0xFFFFFFFF) if algo not in ALGORITHMS: sys.exit( f"[error] Unknown algorithm '{algo}'. Supported: {', '.join(ALGORITHMS)}" ) groups = parse_manifest(in_path) emit_header(groups, ALGORITHMS[algo], algo, out_path)