SindriKit 1.5.0: The SND_MORPH Mutation Engine

A recurring theme in offensive tool development is the battle against static signatures. You can dynamically resolve your APIs, string-hash your module names… but the static shape of your compiled code remains deterministic. When an EDR fingerprints the specific sequence of your custom syscall stub or the memory layout of your execution context, the entire toolchain is burned.

SindriKit 1.5.0 changes this dynamic with the introduction of the SND_MORPH Mutation Engine.

Instead of relying solely on post-compilation obfuscators, SindriKit now injects structural and instruction-level polymorphism directly into the source tree before the compiler even touches it. The result is a unique binary footprint on every compilation, without altering a single byte of semantic behavior.

Source, architecture docs, and PoCs: SindriKit on GitHub.

The Pre-Build Engine

The engine is integrated into the CMake build system. By simply passing -DSND_MORPH=ON at configure time, the orchestrator is on.

It creates a temp morphed/ source tree, copies the framework source code into it, and executes a series of Python-based mutation passes. The compiler is then redirected to build from this mutated directory. This means your codebase remains untouched, but the generated binary is different every time you build.

C Source Polymorphism & Call Graph Splitting

The first mutation pass targets the C codebase. EDRs often build control flow graphs (CFGs) to signature malicious behavior. To defeat this, junk_c.py injects Volatile Opaque Predicates directly into the execution flow.

These are dynamically generated C logic blocks (simple **if, while loops, dummy switch statements) that use volatile variables and randomized magic constants. Because they are volatile, the compiler’s optimizer cannot optimize them away. They evaluate to a predictable outcome at runtime (essentially a highly convoluted NOP), but they obscure the static control flow graph.

Dead Function Generation

To take this a step further, SindriKit 1.5.0 introduces Call Graph Splitting. The mutator now generates completely random, non-existent static C functions at the global scope, filled with random math loops. The opaque predicates randomly call these “dead functions” from within their unreachable branches.

// Example generated dead function
static int snd_dead_func_a83f(int a, int b) {
    int v = a ^ b;
    for(int i = 0; i < 3; i++) {
        v = (v << 2) | (v >> 30);
        v += (b * 155);
    }
    return v ^ 3445;
}

This forces static analysis tools (and human reverse engineers) to map out a function call graph that leads completely to dead ends, increasing the time required to analyze the binary.

The C parser actively tracks brace depth and C scopes to intelligently buffer statements. It will never inject code immediately after a return, break, or goto, ensuring that all injected junk is mathematically reachable and preventing C4702 (unreachable code) compiler errors.

ABI-Aware Assembly Polymorphism

SindriKit utilizes hand-written MASM assembly for its FFI bridges, Syscall Invoker… These stubs are prime targets for signature generation.

The masm_mutate.py pass adds instruction-level polymorphism to all .asm files:

  1. Functional NOPs: Injects single-byte or multi-byte equivalent NOPs (e.g., **xchg eax, eax`) randomly between instructions.
  2. Junk Math: Injects mathematically neutral operations (e.g., lea reg, [reg+0], **mov reg, reg`).

EFLAGS and ABI Safety

Injecting random assembly is incredibly dangerous. SindriKit’s mutator is strictly architecture and ABI aware. It differentiates between x86 and x64 files to prevent undefined register errors. Furthermore, it strictly uses instructions like lea for math injections because they are guaranteed to not modify the CPU’s EFLAGS register. This guarantees that the mutator will never accidentally destroy the conditional jump flags (like the Zero Flag) of the framework’s native execution flow.

Structural Scrambling

shuffle

Shuffling diagram for clarity

The final mutation pass (struct_shuffle.py) targets the memory layout of the framework’s internal data structures.

By wrapping sensitive structures in SND_SHUFFLE_START and SND_SHUFFLE_END macros, the parser will randomly shuffle the order of the structure’s fields on every build. If a security product attempts to signature the memory layout of the snd_engine_ctx_t context block, the signature will be broken upon the next compilation.

The parser tracks brace depth to safely shuffle nested structures and anonymous unions intact, and it automatically detects and preserves #ifdef preprocessor blocks to prevent cross-compilation errors.

Conclusion

The SND_MORPH engine demonstrates the power of SindriKit’s architecture. By shifting evasion tactics from post-compilation wrappers directly into the build pipeline, operators can iterate rapidly and deploy resilient artifacts at the push of a button.

Offensive development deserves better architecture, and with v1.5.0, SindriKit continues to prove it.

*