Skip to content

LoneEngineer99/zsCrypt

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

🔐 zsCrypt

Compile-time encrypted memory protection for .NET

.NET 8.0 C# Fody License

Unique encryption algorithms and compile-time encrypted constants — plaintext never appears in the binary


✨ Highlights

🧬 Unique Per-Type — Each subclass gets its own randomly generated algorithm
🔐 Compile-Time Encrypted Constants — Literal values (ints, floats, strings) are encrypted at compile time and never appear as plaintext in the binary
🔀 High Entropy — 20–50 operations (ADD, SUB, XOR, ROL, ROR, NOT) with large random constants, randomly ordered
🔑 IL-Embedded Constants — Large operands (10,000–999,999) emitted directly as IL instructions
🛡️ Memory Safe — All decrypted data zeroed in finally blocks, even on exceptions
👻 Pointer HidingHiddenValue<T> encrypts GCHandle pointers to break reference chains
⏱️ Ephemeral AccessEphemeralValue<T> destroys decrypted values immediately after callback
🏗️ Build-Unique — Fresh random parameters every build; static analysis must restart each time
🎭 Complex IL — Grouped operations with multiple Int64 locals produce intricate decompiled output


📑 Table of Contents


🏛️ Architecture Overview

┌──────────────────────────────────────────────────────────────────────────┐
│                         Consumer Assembly                                │
│                                                                          │
│  class SecureHealth : EncryptedValue<int> { }  ← Fody injects algo A    │
│  class SecureGold   : EncryptedValue<int> { }  ← Fody injects algo B    │
│  class SecureApiKey : EphemeralValue<string> {} ← Fody injects algo C    │
│  EncryptedValue<int> direct = 42;              ← uses base type algo     │
│  var hidden = new HiddenValue<Config>(cfg);    ← uses EncryptedValue<long>│
│                                                                          │
│  Each type gets an override of Encrypt(byte[]) and Decrypt(byte[])       │
│  with a unique chain of 20-50 randomly ordered byte-level operations —   │
│  different large constants and rotation amounts every time.               │
│  HiddenValue delegates encryption to an internal EncryptedValue<long>.    │
└──────────────────────────────────────────────────────────────────────────┘
         │ references
         ▼
┌──────────────────────────────────────────────────────────────────────────┐
│                        zsCrypt Core Types                                │
│                                                                          │
│  Types/                                                                  │
│  ├─ ISecureValue / ISecureValue<T> — common interface                    │
│  ├─ EncryptedValue<T>  — virtual Encrypt/Decrypt (weaver-injected)        │
│  ├─ EphemeralValue<T>  — virtual Encrypt/Decrypt (weaver-injected)        │
│  └─ HiddenValue<T>    — delegates to internal EncryptedValue<long>       │
│                                                                          │
│  Serialization/                                                          │
│  └─ SerializationProvider — Marshal/UTF-8 serialize with memory safety   │
│                                                                          │
│  Memory/                                                                 │
│  └─ MemoryCleaner      — secure zero strings, arrays, values             │
└──────────────────────────────────────────────────────────────────────────┘

┌──────────────────────────────────────────────────────────────────────────┐
│                  zsCrypt.Fody  (build-time only)                         │
│                                                                          │
│  ModuleWeaver                                                            │
│  ├─ Scan assembly for EncryptedValue / EphemeralValue types & subclasses  │
│  ├─ For each: generate unique CSPRNG parameters & operation sequence     │
│  │   ├─ 20-50 randomly ordered operations from 6 types                   │
│  │   │   (ADD, SUB, XOR, ROL, ROR, NOT with large random constants)     │
│  │   └─ Grouped with multiple Int64 locals for complex decompiled output │
│  └─ Emit override Encrypt/Decrypt IL with those parameters               │
│     Large constants (10000-999999) embedded directly as IL operands       │
└──────────────────────────────────────────────────────────────────────────┘

📁 Project Structure

zsCrypt/
├── zsCrypt.sln
├── README.md
├── demo.md                              # Visual walkthrough with code and output
└── src/
    ├── zsCrypt.Example/                 # Example console app with core types (net8.0)
    │   ├── zsCrypt.Example.csproj
    │   ├── Program.cs                   # Subclass definitions and usage demos
    │   ├── FodyWeavers.xml              # Fody configuration
    │   ├── Serialization/
    │   │   └── SerializationProvider.cs # Type-to-bytes (with unmanaged memory zeroing)
    │   ├── Memory/
    │   │   └── MemoryCleaner.cs         # Secure memory zeroing
    │   └── Types/
    │       ├── ISecureValue.cs        # Common interface for all secure value types
    │       ├── EncryptedValue.cs      # Virtual Encrypt/Decrypt — encrypted value-type wrapper
    │       ├── EphemeralValue.cs      # Virtual Encrypt/Decrypt — controlled-lifetime wrapper
    │       └── HiddenValue.cs         # Delegates to EncryptedValue<long> — GCHandle pointer hiding
    │
    └── zsCrypt.Fody/                    # Fody weaver (netstandard2.0, build-time only)
        ├── zsCrypt.Fody.csproj
        └── ModuleWeaver.cs              # Algorithm injection via Mono.Cecil IL rewriting

🚀 Quick Start

using zsCrypt.Types;

// Option A: Use EncryptedValue<T> directly — the weaver injects a generic algorithm
EncryptedValue<int> health = new(100);
health.Value -= 25;
int hp = health;       // 75 — decrypted automatically
health.Dispose();      // securely clears encrypted bytes

// Option B: Define a subclass — each subclass gets its own unique algorithm
class SecureHealth : EncryptedValue<int> {
    public SecureHealth() { }
    public SecureHealth(int value) : base(value) { }
    public static implicit operator SecureHealth(int value) => new(value);
}

SecureHealth secureHp = 100;
secureHp.Value -= 25;
int raw = secureHp;    // 75 — decrypted automatically
secureHp.Dispose();    // securely clears encrypted bytes

Note

Creating subclasses is optional. Using EncryptedValue<T> or EphemeralValue<T> directly is fully supported — the Fody weaver injects a generic encryption/decryption routine into the base types themselves. Subclasses are useful when you want each type to receive its own unique algorithm. HiddenValue<T> can also be used directly — it delegates to an internal EncryptedValue<long>.


🧩 Core Types

All three types implement ISecureValue (IDisposable + IsDisposed). Types with direct value access also implement ISecureValue<T> (adds Value property).

EncryptedValue<T> and EphemeralValue<T> share the same pattern: virtual Encrypt(byte[]) and Decrypt(byte[]) methods. The Fody weaver injects unique algorithm code into both the base types and each subclass at compile time. Creating subclasses is optional — using the base types directly is fully supported. Without the weaver, values are stored unencrypted (development mode).

HiddenValue<T> takes a different approach — it stores an encrypted GCHandle pointer via an internal EncryptedValue<long>, delegating all encryption to that instance. No subclassing of HiddenValue is needed; use it directly.

📖 See demo.md for a visual walkthrough with code samples and output for every type.

EncryptedValue<T>

Purpose: Encrypts an unmanaged value type (int, float, double, structs) in memory. The value is serialized to bytes, encrypted via the virtual method, and stored. On read, the bytes are cloned, decrypted, deserialized, and the decrypted clone is zeroed inside a try/finally.

Constraint: where T : struct

Key features:

  • Implements ISecureValue<T> for uniform lifecycle and value access
  • Implicit operators for transparent T ↔ EncryptedValue<T> conversion
  • Thread-safe via lock
  • IDisposable — securely clears encrypted bytes on cleanup
  • Decrypted byte clones are always zeroed in finally blocks (exception-safe)
  • Plaintext serialized bytes are zeroed on encryption failure
//Define a subclass — the weaver injects an algorithm at compile time
class SecureHealth : EncryptedValue<int> {
    public SecureHealth() { }
    public SecureHealth(int value) : base(value) { }
    public static implicit operator SecureHealth(int value) => new SecureHealth(value);
}

//Use it
SecureHealth health = 100;
health.Value -= 25;
int raw = health;  // 75
health.Dispose();

HiddenValue<T>

Purpose: Obscures a reference type in memory by storing it behind a GCHandle whose IntPtr pointer value is encrypted via an internal EncryptedValue<long>. Memory scanners following pointer chains will not find the target object because the handle value is encrypted.

Constraint: where T : class

Key features:

  • Implements ISecureValue<T> for uniform lifecycle and value access
  • GCHandle keeps the object alive while hiding the reference path
  • Handle pointer stored as an encrypted long via EncryptedValue<long> — no own Encrypt/Decrypt needed
  • IDisposable + finalizer safety net for GCHandle cleanup
  • Thread-safe via lock
  • No subclassing required — use HiddenValue<T> directly
//Use HiddenValue directly — encryption is handled by the internal EncryptedValue<long>
using (var config = new HiddenValue<DatabaseConfig>(LoadConfig())) {
    var conn = new SqlConnection(config.Value.ConnectionString);
}

EphemeralValue<T>

Purpose: The most secure wrapper. Encrypts data and strictly controls the lifetime of the decrypted form. The decrypted value is only accessible inside a callback and is destroyed immediately after the callback returns. Internally uses HiddenValue<byte[]> for the backing cipher text, adding pointer obscuring on top of encryption.

Constraint: T must be supported by both SerializationProvider and MemoryCleaner.

Key features:

  • Implements ISecureValue for uniform lifecycle management
  • Callback-based access via ref delegates: Use(RefAction<T>) and Use<TResult>(RefFunc<T, TResult>) — prevents value-type stack copies from leaking unzeroed memory
  • Decrypted bytes AND deserialized values destroyed in finally blocks (exception-safe)
  • ref constructor can erase the caller's original variable
  • Plaintext bytes zeroed on encryption failure in Set
  • IDisposable for cleanup

⚠️ CRITICAL — Do not allocate new copies of the value inside the callback

The decrypted value is passed by ref using custom RefAction<T> / RefFunc<T, TResult> delegates. This prevents automatic stack copies of value types from leaking unzeroed memory — the callback operates on the same location that MemoryCleaner.Destroy zeroes after the callback returns.

MemoryCleaner zeroes reference-type values in-place: for strings it overwrites the character buffer directly; for byte arrays it clears the array in place. This means holding another reference to the same object is safe — both variables point to the same backing memory that will be zeroed when the callback exits.

What IS unsafe is any operation that allocates a new object, because that new object is untracked and will not be zeroed:

  • Strings: concatenation ("Bearer " + key), string.Copy, Substring, ToUpper, string interpolation ($"...{key}...") — each produces a new string
  • Value types: any explicit assignment (e.g. int x = value;) copies the value itself, leaving the copy unzeroed
class SecureApiKey : EphemeralValue<string> {
    public SecureApiKey() { }
    public SecureApiKey(ref string value, bool eraseOrigin = true) : base(ref value, eraseOrigin) { }
}

string apiKey = "sk-abc123";
using (var secure = new SecureApiKey(ref apiKey, eraseOrigin: true)) {
    //apiKey characters are now zeroed

    secure.Use((ref string key) => {
        //✅ SAFE: the value is passed by ref — no stack copy is made
        httpClient.DefaultRequestHeaders.Authorization =
            new AuthenticationHeaderValue("Bearer", key);
        //key's character buffer is zeroed when this callback exits
    });

    //✅ ALSO SAFE: 'alias' points to the same string object; its chars will be zeroed too
    //string alias = null;
    //secure.Use((ref string key) => alias = key);

    //❌ UNSAFE: concatenation allocates a NEW string that will NOT be zeroed
    //string header = null;
    //secure.Use((ref string key) => header = "Bearer " + key);  // new string escapes lifecycle control
}

🧬 Fody Weaver — Compile-Time Algorithm Injection

How It Works

  1. At build time, Fody runs zsCrypt.Fody's ModuleWeaver.Execute() as a post-compile step
  2. The weaver scans the target assembly for EncryptedValue<T>, EphemeralValue<T>, and all types that inherit from them (HiddenValue is excluded — it delegates to EncryptedValue internally)
  3. For each type, it generates unique random parameters using RandomNumberGenerator
  4. It creates override methods for Encrypt(byte[]) and Decrypt(byte[]) with IL that implements the unique algorithm
  5. The modified assembly is written back — the algorithm exists only in IL, never in source

What Gets Injected

For each type, the weaver generates a unique chain of 20–50 randomly selected and ordered operations. The conceptual equivalent (the actual code is IL, not C#):

// Auto-generated by zsCrypt.Fody — unique per type, unique per build
class SecureHealth : EncryptedValue<int> {
    protected override void Encrypt(byte[] data) {
        for (int i = 0; i < data.Length; i++) {
            long val = data[i];
            val += 487293;                        // ADD large constant
            val ^= 173059;                        // XOR large constant
            val = (val << 3) | ((val & 0xFF) >> 5); // Rotate left 3
            val = ~val;                           // Bitwise NOT
            val -= 892714;                        // SUB large constant
            val = ((val & 0xFF) >> 5) | (val << 3); // Rotate right 5
            val ^= 551082;                        // XOR large constant
            val += 304167;                        // ADD large constant
            // ... more random operations (20-50 total) ...
            data[i] = (byte)(val & 0xFF);
        }
    }

    protected override void Decrypt(byte[] data) {
        // Exact inverse: reversed order, inverse operations
        // Sub → Xor → Rol → Add → Ror → Not → Xor → Sub → ...
    }
}

Operation Pool

The weaver selects from 6 distinct operation types, each with random parameters:

Operation Encrypt Decrypt (Inverse) Parameters
ADD Constant val = val + c val = val - c c: 10,000–999,999
SUB Constant val = val - c val = val + c c: 10,000–999,999
XOR Constant val ^= c val ^= c (self-inverse) c: 10,000–999,999
Rotate Left ROL(val, n) ROR(val, n) n: 1–7 bits
Rotate Right ROR(val, n) ROL(val, n) n: 1–7 bits
Bitwise NOT ~val ~val (self-inverse)

Important

All operations use Int64 (long) arithmetic internally and are masked with & 0xFF at the end to stay within the byte range. The large constants (10,000–999,999) make pattern recognition significantly harder compared to small byte-range values.

IL complexity: Operations are grouped into blocks of 5–30 and processed through 5–10 Int64 local variables. The grouping and local-variable chaining produce complex expressions in decompiled output, making reverse engineering significantly harder.

Why this design is effective:

  • Each type has a completely different algorithm — cracking one tells you nothing about another
  • The large random constants (10,000–999,999) are embedded directly as IL operands, not stored as arrays
  • Each build produces different random parameters — static analysis must be redone per build
  • 6 operation types × 20–50 operations × large random constants × grouped local-variable chains creates enormous combinatorial diversity
  • The generated IL with grouped Int64 operations blends with normal compiled code — no obvious "crypto routine" signature

🔐 Compile-Time Constant Encryption

The Fody weaver doesn't just inject encryption algorithms — it also encrypts literal constant values directly at compile time. When you assign a literal (int, float, or string) to an encrypted type, the weaver replaces the plaintext value in the binary with pre-encrypted bytes. The original constant never appears in the compiled assembly.

// "12345" is encrypted in the binary — it does NOT exist as plaintext in the DLL
EncryptedValue<int> secret = new(12345);
Console.WriteLine(secret.Value);  // 12345 — decrypted at runtime

// String literals are also encrypted — "sk-example-..." is NOT in the binary
EphemeralValue<string> key = "sk-example-xxxxxxxxxxxx";
key.Use((ref string k) => Console.WriteLine(k));  // decrypted only inside the callback

// Subclass literals use that type's own algorithm
SecureHealth health = 9999;  // 9999 encrypted at compile time with SecureHealth's algorithm

Important

This uses the same per-type algorithm that the weaver injects for runtime encryption — no separate mechanism. The literal is serialized to bytes, encrypted with the type's own algorithm at build time, and stored as an encrypted byte array in the IL. At runtime, the type's Decrypt method handles it normally.


🔨 Building

Prerequisites

  • .NET 8.0 SDK or later
  • Visual Studio 2022+ or the dotnet CLI

Build from command line

cd zsCrypt
dotnet restore
dotnet build

Run the example

dotnet run --project src/zsCrypt.Example
📋 Expected output
=== Obfuscation Library Demo ===
Each type has a unique algorithm injected by the Fody weaver.

--- EncryptedValue<int> (direct usage, no subclass) ---
  Initial value: 42
  After add:     50

--- SecureHealth (EncryptedValue<int>) ---
  Initial health: 100
  After damage:   75
  Implicit cast:  75

--- SecureGold (EncryptedValue<int>, different algorithm) ---
  Initial gold: 5000
  After loot:   5250

--- SecureScore (EncryptedValue<float>) ---
  Score: 99.5

--- HiddenValue<string> (encrypted GCHandle pointer) ---
  Hidden value: SuperSecretPassword123
  Via implicit: SuperSecretPassword123
  Disposed — GCHandle freed, encrypted data cleared.

--- HiddenValue<byte[]> (encrypted GCHandle pointer) ---
  Hidden bytes: [1, 2, 3, 4]

--- SecureApiKey (EphemeralValue<string>) ---
  Before: apiKey = "sk-abc123def456ghi789"
  After ref store: apiKey = "" (zeroed)
  Inside callback: "sk-abc123def456ghi789"
  Callback done — decrypted value destroyed.

--- SecurePassword (EphemeralValue<string>, different algorithm) ---
  Computed length inside callback: 17
  Returned length: 17
  Decrypted value is already destroyed.

=== Demo Complete ===

📖 Usage Guide

Step 1: Define Subclasses (Optional)

Creating subclasses is optional — you can use EncryptedValue<T> directly. However, each subclass you create gets its own unique algorithm injected at compile time. Even two subclasses wrapping the same type get different algorithms:

using zsCrypt.Types;

//Use EncryptedValue<T> directly — encryption is injected into the base type
EncryptedValue<int> directValue = new(42);
int raw = directValue.Value;  // decrypted automatically

//Or define subclasses — each gets a DIFFERENT algorithm
class SecureHealth : EncryptedValue<int> {
    public SecureHealth() { }
    public SecureHealth(int value) : base(value) { }
    public static implicit operator SecureHealth(int value) => new SecureHealth(value);
}

class SecureGold : EncryptedValue<int> {
    public SecureGold() { }
    public SecureGold(int value) : base(value) { }
    public static implicit operator SecureGold(int value) => new SecureGold(value);
}

//EphemeralValue subclass for controlled-lifetime sensitive data
class SecureApiKey : EphemeralValue<string> {
    public SecureApiKey() { }
    public SecureApiKey(ref string value, bool eraseOrigin = true) : base(ref value, eraseOrigin) { }
}

Step 2: Use Them

//EncryptedValue — use directly or via subclass
EncryptedValue<int> points = new(50);
int p = points.Value;

//EncryptedValue subclass — transparent usage via implicit operators
SecureHealth health = 100;
health.Value -= 25;
int hp = health;

//HiddenValue — use directly, no subclass needed
using (var config = new HiddenValue<AppConfig>(LoadConfig())) {
    UseConfig(config.Value);
}

//EphemeralValue — callback-only, automatic cleanup
string key = GetApiKey();
using (var secure = new SecureApiKey(ref key, eraseOrigin: true)) {
    secure.Use((ref string k) => CallApi(k));

    int len = secure.Use((ref string k) => k.Length);
}

Note

Creating subclasses is optional. Using EncryptedValue<T> or EphemeralValue<T> directly (e.g., new EncryptedValue<int>(42)) is fully supported — the Fody weaver injects a generic encryption/decryption routine into the base types themselves. Subclasses are useful when you want each type to receive its own unique algorithm. HiddenValue<T> can also be used directly — it delegates to an internal EncryptedValue<long>.


🔒 Security Model

Threat model

This library helps protects against:

  • 🔍 Memory scanners (Cheat Engine, process memory dumpers) searching for known value patterns
  • 🔗 Automated pointer-chain scanners that follow GCHandle → object references
  • 🕵️ Casual reverse engineering of process memory dumps
  • 🔎 Static binary analysis — literal constants are encrypted at compile time and never appear as plaintext
  • 🔄 Cross-build analysis — each build has different algorithms and keys

It does not protect against:

  • ⚡ Analysis by advanced attackers.

Defense layers

# Layer Technique Protects Against
1 Compile-time constant encryption Literal values encrypted at build time — plaintext never in the binary Static binary analysis for hardcoded secrets
2 Algorithm uniqueness Different operation chain per type (20-50 ops from 6 types) Cross-type correlation
3 Build uniqueness New random parameters per build (CSPRNG) Cross-build static analysis
4 IL-embedded constants Large operands (10,000–999,999) as direct IL instructions Memory scanning for key material
5 Operation diversity ADD, SUB, XOR, ROL, ROR, NOT with large random constants Pattern matching / signature detection
6 IL complexity Grouped operations with multiple Int64 locals (5–10) Automated decompiler pattern recognition
7 Pointer hiding GCHandle + IntPtr encrypted via EncryptedValue<long> Reference chain scanning
8 Lifetime control Callback-only access via ref delegates (EphemeralValue) Decrypted value lingering + value-type stack copies
9 Memory zeroing try/finally + MemoryCleaner on all decrypted buffers Residual data in freed memory
10 Unmanaged zeroing ZeroUnmanagedMemory before FreeHGlobal Plaintext in unmanaged heap
11 Origin erasure EphemeralValue ref constructor Original variable remaining readable
12 Failure safety Plaintext zeroed in catch blocks on encrypt failure Leak on exception paths

📄 License

This project is provided as-is for educational and development purposes.

About

C# wrappers for obfuscation of protected values with compile time encryption support.

Topics

Resources

License

Contributing

Stars

Watchers

Forks

Contributors

Languages