use proc_macro2::TokenStream;
use quote::{quote, ToTokens};
use syn::{
visit_mut::{self, VisitMut},
Expr, ExprLit, Lit,
};
use crate::crypto::get_build_seed;
pub fn encrypt_string(plaintext: &str, string_id: u64) -> Vec<u8> {
let build_seed = get_build_seed();
let bytes = plaintext.as_bytes();
let mut encrypted = vec![0u8; bytes.len()];
for (i, &byte) in bytes.iter().enumerate() {
let key_byte = derive_key_byte(&build_seed, string_id, i as u64);
encrypted[i] = byte ^ key_byte;
}
encrypted
}
fn derive_key_byte(seed: &[u8; 32], string_id: u64, position: u64) -> u8 {
let mut hash = 0xcbf29ce484222325u64;
for &byte in seed {
hash ^= byte as u64;
hash = hash.wrapping_mul(0x100000001b3);
}
for &byte in &string_id.to_le_bytes() {
hash ^= byte as u64;
hash = hash.wrapping_mul(0x100000001b3);
}
for &byte in &position.to_le_bytes() {
hash ^= byte as u64;
hash = hash.wrapping_mul(0x100000001b3);
}
(hash & 0xFF) as u8
}
pub fn generate_string_id(content: &str, index: usize) -> u64 {
let mut hash = 0xcbf29ce484222325u64;
for byte in content.bytes() {
hash ^= byte as u64;
hash = hash.wrapping_mul(0x100000001b3);
}
hash ^= index as u64;
hash = hash.wrapping_mul(0x100000001b3);
hash
}
pub struct StringObfuscator {
string_counter: usize,
pub encrypted_strings: Vec<(u64, Vec<u8>)>,
}
impl StringObfuscator {
pub fn new() -> Self {
Self {
string_counter: 0,
encrypted_strings: Vec::new(),
}
}
}
impl VisitMut for StringObfuscator {
fn visit_expr_mut(&mut self, expr: &mut Expr) {
visit_mut::visit_expr_mut(self, expr);
if let Expr::Lit(ExprLit { lit: Lit::Str(lit_str), .. }) = expr {
let content = lit_str.value();
if content.is_empty() {
return;
}
let string_id = generate_string_id(&content, self.string_counter);
self.string_counter += 1;
let encrypted = encrypt_string(&content, string_id);
let encrypted_len = encrypted.len();
self.encrypted_strings.push((string_id, encrypted.clone()));
let encrypted_bytes: Vec<_> = encrypted.iter().map(|b| quote! { #b }).collect();
let replacement = quote! {
{
static ENCRYPTED: [u8; #encrypted_len] = [#(#encrypted_bytes),*];
static STRING_ID: u64 = #string_id;
aegis_vm::string_obfuscation::decrypt_static(&ENCRYPTED, STRING_ID)
}
};
*expr = syn::parse2(replacement).expect("Failed to parse replacement expression");
}
}
}
pub fn obfuscate_function(mut func: syn::ItemFn) -> TokenStream {
let mut obfuscator = StringObfuscator::new();
obfuscator.visit_item_fn_mut(&mut func);
func.to_token_stream()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_encrypt_decrypt_roundtrip() {
let original = "Hello, World!";
let string_id = generate_string_id(original, 0);
let encrypted = encrypt_string(original, string_id);
let build_seed = get_build_seed();
let mut decrypted = vec![0u8; encrypted.len()];
for (i, &byte) in encrypted.iter().enumerate() {
let key_byte = derive_key_byte(&build_seed, string_id, i as u64);
decrypted[i] = byte ^ key_byte;
}
assert_eq!(original.as_bytes(), &decrypted[..]);
}
#[test]
fn test_different_ids_different_encryption() {
let content = "same content";
let enc1 = encrypt_string(content, 1);
let enc2 = encrypt_string(content, 2);
assert_ne!(enc1, enc2);
}
}