| Crates.io | aegis_vm |
| lib.rs | aegis_vm |
| version | 0.2.51 |
| created_at | 2025-11-28 13:43:02.015646+00 |
| updated_at | 2025-12-16 11:17:51.406875+00 |
| description | Advanced Rust code virtualization and obfuscation framework |
| homepage | |
| repository | https://github.com/onurkun/RustAegis |
| max_upload_size | |
| id | 1955320 |
| size | 1,585,495 |
Next-Generation Virtualization & Obfuscation Framework for Rust
RustAegis is a research-grade software protection system that compiles Rust code into custom, polymorphic virtual machine bytecode. It is designed to protect sensitive logic against reverse engineering and tampering by moving execution from the native CPU to a secure, randomized software interpreter.
#[vm_protect] are automatically wrapped and executed. No manual setup required..build_seed artifact.+, -, ^) into complex, mathematically equivalent boolean expressions.Add the following to your Cargo.toml:
[dependencies]
aegis_vm = "0.2.51"
Apply the #[vm_protect] attribute to any sensitive function you wish to virtualize.
use aegis_vm::vm_protect;
// Standard protection (Polymorphism + Encryption)
#[vm_protect]
fn check_password(input: u64) -> bool {
input == 0xCAFEBABE
}
// Paranoid protection (Heavy MBA + Obfuscation)
// Use this for critical logic like key derivation.
#[vm_protect(level = "paranoid")]
fn derive_key(seed: u64) -> u64 {
// All arithmetic here is transformed into complex boolean logic
(seed ^ 0x1234) + 0xABCD
}
// Advanced: Strings, Arrays, and Control Flow
#[vm_protect(level = "standard")]
fn compute_checksum() -> u64 {
let secret = "LICENSE-KEY";
let weights = [1, 2, 3, 4, 5];
// String length check
if secret.is_empty() {
return 0;
}
// Array iteration with weighted sum
let mut sum: u64 = 0;
for i in 0..weights.len() {
sum += weights.get(i) * (i as u64 + 1);
}
// Combine with string hash
sum + secret.len()
}
// NEW in v0.2.2: Native Function Calls
fn is_license_valid() -> bool {
// External license check logic
true
}
fn log_event(code: u64) {
println!("Event: {}", code);
}
#[vm_protect]
fn check_authorization() -> bool {
// External functions are automatically wrapped!
let valid: bool = is_license_valid(); // Note: explicit bool type
if !valid {
log_event(1); // Native call works
return false;
}
log_event(0);
true
}
.build_seedRustAegis uses a split architecture:
vm-macro): Runs at compile time, generating encrypted bytecode.vm): Runs inside your application, executing the bytecode..anticheat_build_seedTo ensure the compiler uses the exact same encryption keys and opcode mapping that the runtime expects, the system generates a temporary artifact named .anticheat_build_seed in your project root during the build process.
cargo clean to regenerate the seed..anticheat_build_seed to version control if you want unique polymorphism for every deployment.ANTICHEAT_BUILD_KEY environment variable. This overrides the random generation.# For reproducible builds (same opcodes, same keys)
export ANTICHEAT_BUILD_KEY="my-secret-company-build-key"
cargo build --release
RustAegis significantly complicates static and dynamic analysis by flattening control flow and obfuscating data flow.
The VM uses indirect threading (function pointer table) instead of a traditional switch-case dispatcher. This eliminates recognizable patterns in binary analysis:
Traditional Switch-Case:
cmp x8, #0x01 ; Compare opcode
b.eq handler_push ; Branch to handler
cmp x8, #0x02
b.eq handler_pop
...
Indirect Threading (RustAegis):
ldrb w8, [decode_table, opcode] ; Decode shuffled opcode
ldr x9, [handler_table, x8, lsl#3] ; Load handler pointer
blr x9 ; Indirect call
This approach:
The original control flow (if/else, loops) is flattened into data-driven jumps within the interpreter loop.
Native CFG:
Distinct blocks for if, else, and return, easily readable by decompilers.
Figure 1: Native assembly of the license check function. Logic is linear and easy to follow.
VM CFG: A single "God Node" (the dispatcher) with edges pointing back to itself. The actual logic is hidden in the bytecode data, not the CPU instructions.
Figure 2: The same function protected by the VM. The control flow is flattened into the VM's fetch-decode-execute loop.
Instead of a single ADD instruction, the analyst sees a randomized sequence of stack operations implementing mathematically equivalent formulas like:
x + y = (x ^ y) + 2 * (x & y) or (x | y) + (x & y)
Figure 3: Even a simple arithmetic function explodes into a complex graph due to MBA transformations and the VM dispatcher overhead.
Virtualization comes with a cost. RustAegis is designed for security, not speed.
#[vm_protect] only to sensitive functions (license checks, key generation, encryption logic). Do not virtualize tight loops in performance-critical rendering or physics code.x86_64, aarch64, wasm32, and any platform supported by Rust std or alloc (no_std compatible).Check the examples/ directory for complete test cases:
001_test.rs: Native function call support demo.01_arithmetic.rs: Demonstrates MBA transformations.02_control_flow.rs: Demonstrates if/else logic protection.03_loops.rs: Demonstrates loop virtualization.04_wasm.rs: Demonstrates WASM integration.05_whitebox_crypto.rs: Demonstrates White-Box Cryptography key protection.06_native_calls.rs: Manual NativeRegistry usage (legacy).07_native_call_macro.rs: Automatic native call via macro.08_async_vm.rs: NEW - Async VM with state machine obfuscation (experimental).wasm_test/: Complete WASM test project with wasm-pack.Run them with:
cargo run --example 001_test
cargo run --example 01_arithmetic
cargo run --example 07_native_call_macro
cargo run --example 08_async_vm --features async_vm
# For WASM tests
cd examples/wasm_test
wasm-pack test --node
RustAegis fully supports WebAssembly. To use with WASM:
# Add WASM target
rustup target add wasm32-unknown-unknown
# Install wasm-pack (optional, for building/testing)
cargo install wasm-pack
[dependencies]
aegis_vm = { version = "0.2.51", default-features = false }
wasm-bindgen = "0.2"
Since #[vm_protect] and #[wasm_bindgen] cannot be combined directly, use a wrapper:
use aegis_vm::vm_protect;
use wasm_bindgen::prelude::*;
// VM-protected implementation
#[vm_protect(level = "debug")]
fn secret_impl(x: u64) -> u64 {
x ^ 0xDEADBEEF
}
// WASM export wrapper
#[wasm_bindgen]
pub fn secret(x: u64) -> u64 {
secret_impl(x)
}
cd examples/wasm_test
# Build for web
wasm-pack build --target web --release
# Run tests with Node.js
wasm-pack test --node
# Run tests in headless browser
wasm-pack test --headless --firefox
The compiled .wasm file will be in pkg/ directory.
async_vm)Status: Experimental - Use for testing and research only
The Async VM feature transforms the VM execution loop into an async/await state machine, adding an extra layer of obfuscation against reverse engineering.
How it works:
async fn into a complex state machine enumEnable:
[dependencies]
aegis_vm = { version = "0.2.51", features = ["async_vm"] }
Usage:
use aegis_vm::async_vm::execute_async;
// Same as execute(), but uses async state machine internally
let result = execute_async(&bytecode, &input)?;
Trade-offs:
| Aspect | Impact |
|---|---|
| Binary size | +5-10KB (state machine code) |
| Performance | ~2-5% overhead (yield points) |
| Analysis difficulty | Harder to trace in debuggers |
Note: This is an obfuscation layer, not cryptographic security. A skilled analyst can still reverse the state machine given enough time.
Bug Fixes (GitHub Issue #1):
spin Dependency: Added spin = "0.10" to dependencies.#[cfg(feature = "std")] that checked wrong crate.aegis_vm::SpinOnce and aegis_vm::StdVec re-exports instead of requiring user crate to have these dependencies.Async VM Engine (Experimental):
async_vm feature transforms VM dispatch loop into async/await state machine, complicating control flow analysis in debuggers.block_on implementation (~60 lines) with no_std support.YIELD_MASK constant derived from build seed - yield frequency varies per-build (64-256 instruction intervals).std::thread::yield_now() or core::hint::spin_loop() instead of busy-spin.New Module Structure:
src/async_vm/
βββ mod.rs # Module exports
βββ executor.rs # block_on + noop_waker
βββ yielder.rs # YieldNow future
βββ engine.rs # Async run loop
API:
execute_async(code, input) - Async version of execute()execute_async_with_natives(code, input, registry) - With native function supportVmState::get_yield_mask() / set_yield_mask() - Runtime yield configurationCI/CD:
async_vm job to GitHub Actions workflowNative Function Call Support:
#[vm_protect] are now automatically wrapped and executed via native call mechanism. No manual NativeRegistry setup required.fn(&[u64]) -> u64) and creates a native function table at compile-time.NATIVE_CALL Opcode: New opcode 0xF0 <index> <arg_count> for calling external functions from VM bytecode.execute_with_native_table(): New engine function that accepts a native function table for macro-generated code.Boolean NOT Fix:
!bool operator to use XOR instead of bitwise NOT. Previously !true would return 0xFFFFFFFFFFFFFFFE (truthy), now correctly returns 0 (false).is_bool_expr() to detect boolean expressions and apply correct NOT semantics.Improved Error Messages:
println!, log::error!, etc.) now produce clear error: "Macro calls not supported in VM: println!(...). Use a native function wrapper instead."decrypted β bytecode variable name in paranoid mode region integrity check.New Example:
examples/001_test.rs: Demonstrates native call support with VM-protected functions calling external functions.Important Notes:
let result: bool = some_function();u64, u32, i64, i32, u16, u8, bool, charSecurity Hardening:
aegis_str! macro. Strings are decrypted only when accessed at runtime.Architecture Improvements:
handlers/ directory with category-based modules (stack, arithmetic, control, memory, etc.).fn(&mut VmState, &NativeRegistry) -> VmResult<()> signature for consistent dispatch.const (not static) for no_std/WASM compatibility.Binary Analysis Results:
| Metric | Before | After |
|---|---|---|
| Dispatcher Size | ~10KB | ~800 bytes |
| Pattern Visibility | CMP/branch chains | Single indirect call |
| String Exposure | Plaintext errors | Encrypted at rest |
Pattern Matching Engine:
match expression compilation with 45 dedicated tests.1, 42, "hello"n, x_1..=5, 1..101 | 2 | 3(a, b, c)Point { x, y }Point(x, y)n @ 1..=5n if n > 0Memory Management Fix:
break, continue, and return statements would skip heap cleanup.emit_scope_cleanup(): New compiler function that emits HEAP_FREE for all heap variables when exiting scopes early.LoopContext.scope_depth: Loops now track their scope depth for proper cleanup on break/continue.New Features:
Improvements:
Major Refactoring:
expr.rs, stmt.rs, literal.rs, array.rs, control.rs, method.rs, cast.rs, emit.rs) for better maintainability.Vec<BTreeMap<String, VarInfo>> for nested scope support with correct variable shadowing.HEAP_FREE is now emitted on scope exit for String/Vector variables, preventing memory leaks.New Features:
let x = 10; { let x = 20; } works as expected).IDIV and IMOD opcodes for signed integer division/modulo..count_ones(), .count_zeros(), .leading_zeros(), .trailing_zeros().Improvements:
New Features:
len(), get(), push(), concat(), eq(), hash(), is_empty().len(), get(), push(), pop(), set().as u8, as u16, as u32, as u64, as i8, as i16, as i32, as i64.New Features:
Protection Levels:
| Level | ValueCryptor | Full Hash | Region Hash |
|---|---|---|---|
| debug | No | No | No |
| standard | No | Yes | No |
| paranoid | Yes | Yes | Yes |
Note on Runtime Integrity: The current integrity checking protects against static patching (modifications to bytecode on disk). Runtime memory patching detection (continuous integrity checks during execution) is intentionally not included in this version to avoid performance overhead. This may be added as an optional feature in future releases for users who require protection against debugger-based runtime patching.
Improvements:
New Features:
no_std compatibility for wasm32-unknown-unknown targetexamples/04_wasm.rs and examples/wasm_test/ project with wasm-pack integrationsubstitution.rs:
AddSubstitution - Multiple arithmetic identity transformations for ADDSubSubstitution - Multiple arithmetic identity transformations for SUBMulSubstitution - Multiplication obfuscation patternsXorSubstitution - XOR identity transformationsDeadCodeInsertion - Deterministic dead code injectionOpaquePredicate - Always-true/always-false conditionsComparisonSubstitution - Comparison obfuscationControlFlowSubstitution - Control flow helpersBug Fixes:
std::hint::black_box β core::hint::black_box in build.rs for no_std compatibilitySystemTime usage with proper #[cfg(feature = "std")] guards in state.rs and native.rscompiler.rs to use centralized Substitution module instead of inline implementationsImprovements:
This project is for educational and research purposes only. It is designed to demonstrate concepts in software protection, obfuscation, and compiler theory.
MIT