4.0 KiB
generate_hashes.py
Location: scripts/generate_hashes.py
This script is the core of SindriKit's compile-time API hashing pipeline. It is invoked automatically by the CMake build system before compilation begins. Its output is a generated C header (include/generated/sindri_hashes.h) containing #define macros for every API name and module name the framework needs to resolve at runtime.
Purpose
Offensive tooling that resolves Windows APIs by plaintext name (e.g., "NtAllocateVirtualMemory") leaves those strings embedded in the binary's .rdata section. Static analysis tools and YARA rules trivially flag these artifacts. SindriKit eliminates this surface by pre-computing hashes of all required strings at configure time. At runtime, the framework hashes encountered export names and compares the result against these compile-time constants. The plaintext string never appears in the binary.
Anti-Analysis: Randomized Seed
The script accepts a RANDOMIZE flag (ON/OFF) as its final argument.
- When ON: The script generates a fresh, randomized 32-bit seed in the range
[0x10000000, 0xFFFFFFFF]. This guarantees that hash values are unique per build, preventing signature-based detection and thwarting pre-computed rainbow tables targeting standard DJB2/FNV1A initialization constants. - When OFF: The script uses a deterministic initialization constant (e.g.,
0x5381for DJB2). This ensures that the generated header remains identical across executions, preserving file system timestamps and preventing unnecessary recompilations during local development.
The generated header emits the seed as SND_HASH_SEED, and the runtime hashing functions in hash.h consume it dynamically.
Supported Algorithms
| Algorithm | CMake Variable | Description |
|---|---|---|
| DJB2 | SND_HASH_ALGO=DJB2 |
Daniel J. Bernstein's hash. Shift-and-add (h = (h << 5) + h + c). |
| FNV-1a | SND_HASH_ALGO=FNV1A |
Fowler–Noll–Vo variant 1a. XOR-then-multiply with prime 16777619. |
Switching the algorithm across the entire codebase requires only changing the SND_HASH_ALGO CMake variable without any source code modifications.
Usage
python scripts/generate_hashes.py <hashes.ini> <sindri_hashes.h> <ALGO> <RANDOMIZE>
| Argument | Description |
|---|---|
hashes.ini |
Path to the hash manifest (typically config/hashes.ini) |
sindri_hashes.h |
Output path for the generated C header |
ALGO |
Hashing algorithm to use (DJB2 or FNV1A) |
RANDOMIZE |
Toggle randomized seeds (ON or OFF) |
Build System Integration (Smart Caching)
Under normal operation, this script is never invoked manually. The CMake build system evaluates it at configure time via execute_process.
To support aggressive incremental builds without sacrificing opsec agility, the script employs "Smart Caching" logic:
- It pre-calculates the exact header string in memory.
- It attempts to read the existing
sindri_hashes.hfrom disk. - If the contents are a perfect 1:1 match, the script cleanly exits without touching the file, preserving the disk modification timestamp.
Because the timestamp remains unmodified, ninja and MSBuild correctly deduce that dependent C files do not need to recompile, vastly improving developer velocity when SND_RANDOMIZE_SEED=OFF.
Output Format
The generated header contains:
- A
SND_HASH_SEEDdefine with the randomized seed value. - Grouped
SND_HASH_<NAME>defines for each module and API.
// AUTO-GENERATED by scripts/generate_hashes.py (DO NOT EDIT)
// Algorithm: DJB2
#define SND_HASH_SEED 0xA3F7B1C2U
// --------------------------------------------------------------------------
// ntdll.dll
// --------------------------------------------------------------------------
#define SND_HASH_NTDLL_DLL 0x1B2C3D4EU // module
#define SND_HASH_NTALLOCATEVIRTUALMEMORY 0x5A6B7C8DU
#define SND_HASH_NTPROTECTVIRTUALMEMORY 0x9E0F1A2BU
...