mirror of
https://github.com/youssefnoob003/SindriKit
synced 2026-07-07 21:57:09 +00:00
173 lines
5.1 KiB
Python
173 lines
5.1 KiB
Python
"""
|
|
generate_hashes.py: SindriKit compile-time hash generator
|
|
|
|
Usage:
|
|
generate_hashes.py <hashes.ini> <sindri_hashes.h> <ALGO>
|
|
|
|
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 <hashes.ini> <sindri_hashes.h> <ALGO> [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)
|