use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, ItemFn, ReturnType, FnArg, Pat, Type};
mod anti_analysis;
mod compiler;
mod crypto;
mod integrity;
mod mba;
mod opcodes;
mod polymorphic;
mod string_obfuscation;
mod substitution;
mod value_cryptor;
mod whitebox;
#[derive(Debug, Clone, Copy, PartialEq, Default)]
enum ProtectionLevel {
Debug,
#[default]
Standard,
Paranoid,
}
fn parse_protection_level(attr: &str) -> ProtectionLevel {
if attr.contains("debug") {
ProtectionLevel::Debug
} else if attr.contains("paranoid") {
ProtectionLevel::Paranoid
} else {
ProtectionLevel::Standard
}
}
#[proc_macro_attribute]
pub fn vm_protect(attr: TokenStream, item: TokenStream) -> TokenStream {
let input = parse_macro_input!(item as ItemFn);
let attr_str = attr.to_string();
let protection_level = parse_protection_level(&attr_str);
let fn_name = &input.sig.ident;
let fn_vis = &input.vis;
let fn_generics = &input.sig.generics;
let fn_inputs = &input.sig.inputs;
let fn_output = &input.sig.output;
let fn_id = {
let name_str = fn_name.to_string();
let mut hash = 0xcbf29ce484222325u64;
for byte in name_str.bytes() {
hash ^= byte as u64;
hash = hash.wrapping_mul(0x100000001b3);
}
hash
};
let use_mba = protection_level == ProtectionLevel::Paranoid;
let use_substitution = protection_level != ProtectionLevel::Debug;
let compile_result = compiler::compile_function_with_natives(&input, use_mba, use_substitution);
let (raw_bytecode, native_collector) = match compile_result {
Ok(result) => result,
Err(e) => {
return syn::Error::new_spanned(&input, format!("VM compilation error: {}", e))
.to_compile_error()
.into();
}
};
let has_native_calls = native_collector.has_calls();
let native_wrappers = if has_native_calls {
native_collector.generate_wrappers()
} else {
quote! {}
};
let native_table = if has_native_calls {
native_collector.generate_table()
} else {
quote! { let __native_table: &[fn(&[u64]) -> u64] = &[]; }
};
let native_conversion_helper = if has_native_calls {
compiler::native_call::NativeCallCollector::generate_conversion_helper()
} else {
quote! {}
};
let poly_level = match protection_level {
ProtectionLevel::Debug => polymorphic::PolymorphicLevel::None,
ProtectionLevel::Standard => polymorphic::PolymorphicLevel::Medium,
ProtectionLevel::Paranoid => polymorphic::PolymorphicLevel::Heavy,
};
let fn_name_str = fn_name.to_string();
let bytecode = polymorphic::apply_polymorphism(&raw_bytecode, &fn_name_str, poly_level);
let input_prep = generate_input_prep(fn_inputs);
let output_extract = generate_output_extract(fn_output);
let expanded = match protection_level {
ProtectionLevel::Debug => {
let bytecode_len = raw_bytecode.len();
let bytecode_bytes = raw_bytecode.iter().map(|b| quote! { #b });
quote! {
#fn_vis fn #fn_name #fn_generics(#fn_inputs) #fn_output {
static BYTECODE: [u8; #bytecode_len] = [#(#bytecode_bytes),*];
#native_conversion_helper
#native_wrappers
#native_table
let input_buffer: aegis_vm::StdVec<u8> = { #input_prep };
let result = aegis_vm::execute_with_native_table(&BYTECODE, &input_buffer, &__native_table)
.expect("VM execution failed");
#output_extract
}
}
}
ProtectionLevel::Standard | ProtectionLevel::Paranoid => {
let package = match crypto::encrypt_with_seed(&bytecode, fn_id) {
Ok(p) => p,
Err(e) => {
return syn::Error::new_spanned(
&input,
format!("VM encryption error: {}", e)
).to_compile_error().into();
}
};
let integrity_hash = integrity::fnv1a_hash_with_seed(&bytecode);
let ciphertext_len = package.ciphertext.len();
let ciphertext_bytes = package.ciphertext.iter().map(|b| quote! { #b });
let nonce_bytes = package.nonce.iter().map(|b| quote! { #b });
let tag_bytes = package.tag.iter().map(|b| quote! { #b });
let build_id = package.build_id;
let region_check = if protection_level == ProtectionLevel::Paranoid {
let integrity_data = integrity::IntegrityData::compute_default(&bytecode);
let num_regions = integrity_data.regions.len();
let region_starts: Vec<_> = integrity_data.regions.iter().map(|r| r.start).collect();
let region_ends: Vec<_> = integrity_data.regions.iter().map(|r| r.end).collect();
let region_hashes: Vec<_> = integrity_data.regions.iter().map(|r| r.hash).collect();
quote! {
static REGION_STARTS: [u32; #num_regions] = [#(#region_starts),*];
static REGION_ENDS: [u32; #num_regions] = [#(#region_ends),*];
static REGION_HASHES: [u64; #num_regions] = [#(#region_hashes),*];
for i in 0..#num_regions {
let start = REGION_STARTS[i] as usize;
let end = REGION_ENDS[i] as usize;
let region_data = &bytecode[start..end];
let computed = aegis_vm::compute_hash(region_data);
if computed != REGION_HASHES[i] {
panic!("E05:{:02x}", i);
}
}
}
} else {
quote! {}
};
quote! {
#fn_vis fn #fn_name #fn_generics(#fn_inputs) #fn_output {
static ENCRYPTED: [u8; #ciphertext_len] = [#(#ciphertext_bytes),*];
static NONCE: [u8; 12] = [#(#nonce_bytes),*];
static TAG: [u8; 16] = [#(#tag_bytes),*];
static BUILD_ID: u64 = #build_id;
static INTEGRITY_HASH: u64 = #integrity_hash;
#native_conversion_helper
#native_wrappers
#native_table
#[inline(always)]
fn __aegis_decrypt() -> aegis_vm::VmResult<aegis_vm::StdVec<u8>> {
let runtime_build_id = aegis_vm::build_config::BUILD_ID;
if BUILD_ID != runtime_build_id {
return Err(aegis_vm::VmError::InvalidBytecode);
}
let seed = aegis_vm::build_config::get_build_seed();
let ctx = aegis_vm::CryptoContext::new(seed);
let decrypted = ctx.decrypt(&ENCRYPTED, &NONCE, &TAG)
.map_err(|_| aegis_vm::VmError::InvalidBytecode)?;
let computed_hash = aegis_vm::compute_hash(&decrypted);
if computed_hash != INTEGRITY_HASH {
return Err(aegis_vm::VmError::InvalidBytecode);
}
Ok(decrypted)
}
{
static DECRYPTED: aegis_vm::SpinOnce<aegis_vm::StdVec<u8>> = aegis_vm::SpinOnce::new();
let bytecode = DECRYPTED.call_once(|| {
__aegis_decrypt().expect("E02")
});
#region_check
let input_buffer: aegis_vm::StdVec<u8> = { #input_prep };
let result = aegis_vm::execute_with_native_table(bytecode, &input_buffer, &__native_table)
.expect("E04");
#output_extract
}
}
}
}
};
expanded.into()
}
fn generate_input_prep(inputs: &syn::punctuated::Punctuated<FnArg, syn::token::Comma>) -> proc_macro2::TokenStream {
let mut prep_code = Vec::new();
for (idx, arg) in inputs.iter().enumerate() {
if let FnArg::Typed(pat_type) = arg {
if let Pat::Ident(pat_ident) = &*pat_type.pat {
let arg_name = &pat_ident.ident;
let offset = idx * 8;
if is_u64_type(&pat_type.ty) {
prep_code.push(quote! {
buf[#offset..#offset + 8].copy_from_slice(&#arg_name.to_le_bytes());
});
} else if is_bool_type(&pat_type.ty) {
prep_code.push(quote! {
buf[#offset..#offset + 8].copy_from_slice(&(#arg_name as u64).to_le_bytes());
});
} else {
prep_code.push(quote! {
buf[#offset..#offset + 8].copy_from_slice(&(#arg_name as u64).to_le_bytes());
});
}
}
}
}
let num_args = inputs.len();
let buffer_size = num_args * 8;
if num_args == 0 {
quote! { aegis_vm::StdVec::new() }
} else {
quote! {
let mut buf = {
let mut v = aegis_vm::StdVec::with_capacity(#buffer_size);
v.resize(#buffer_size, 0u8);
v
};
#(#prep_code)*
buf
}
}
}
fn generate_output_extract(output: &ReturnType) -> proc_macro2::TokenStream {
match output {
ReturnType::Default => quote! { () },
ReturnType::Type(_, ty) => {
if is_u64_type(ty) {
quote! { result }
} else if is_bool_type(ty) {
quote! { result != 0 }
} else if is_u32_type(ty) {
quote! { result as u32 }
} else if is_i64_type(ty) {
quote! { result as i64 }
} else {
quote! { result as _ }
}
}
}
}
fn is_u64_type(ty: &Type) -> bool {
if let Type::Path(path) = ty {
path.path.is_ident("u64")
} else {
false
}
}
fn is_u32_type(ty: &Type) -> bool {
if let Type::Path(path) = ty {
path.path.is_ident("u32")
} else {
false
}
}
fn is_i64_type(ty: &Type) -> bool {
if let Type::Path(path) = ty {
path.path.is_ident("i64")
} else {
false
}
}
fn is_bool_type(ty: &Type) -> bool {
if let Type::Path(path) = ty {
path.path.is_ident("bool")
} else {
false
}
}
#[proc_macro_attribute]
pub fn obfuscate_strings(_attr: TokenStream, item: TokenStream) -> TokenStream {
let input = parse_macro_input!(item as ItemFn);
let output = string_obfuscation::obfuscate_function(input);
output.into()
}
#[proc_macro]
pub fn aegis_str_internal(input: TokenStream) -> TokenStream {
let lit_str = parse_macro_input!(input as syn::LitStr);
let content = lit_str.value();
if content.is_empty() {
return quote! { "" }.into();
}
let string_id = string_obfuscation::generate_string_id(&content, 0);
let encrypted = string_obfuscation::encrypt_string(&content, string_id);
let encrypted_len = encrypted.len();
let encrypted_bytes: Vec<_> = encrypted.iter().map(|b| quote! { #b }).collect();
let expanded = quote! {
{
static __AEGIS_ENC: [u8; #encrypted_len] = [#(#encrypted_bytes),*];
crate::string_obfuscation::decrypt_static(&__AEGIS_ENC, #string_id)
}
};
expanded.into()
}
#[proc_macro]
pub fn aegis_str(input: TokenStream) -> TokenStream {
let lit_str = parse_macro_input!(input as syn::LitStr);
let content = lit_str.value();
if content.is_empty() {
return quote! { "" }.into();
}
let string_id = string_obfuscation::generate_string_id(&content, 0);
let encrypted = string_obfuscation::encrypt_string(&content, string_id);
let encrypted_len = encrypted.len();
let encrypted_bytes: Vec<_> = encrypted.iter().map(|b| quote! { #b }).collect();
let expanded = quote! {
{
static __AEGIS_ENC: [u8; #encrypted_len] = [#(#encrypted_bytes),*];
aegis_vm::string_obfuscation::decrypt_static(&__AEGIS_ENC, #string_id)
}
};
expanded.into()
}