ASM Obfuscation

This commit is contained in:
Michael Mikovsky
2025-12-13 16:49:32 -07:00
parent 7525b9a213
commit a7cd65f7ae
10 changed files with 1131 additions and 148 deletions
+205 -1
View File
@@ -22,6 +22,18 @@ dependencies = [
"memchr",
]
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitflags"
version = "2.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3"
[[package]]
name = "block-buffer"
version = "0.10.4"
@@ -64,6 +76,18 @@ version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
name = "cfg_aliases"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e"
[[package]]
name = "cfg_aliases"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
[[package]]
name = "cipher"
version = "0.4.4"
@@ -162,12 +186,53 @@ version = "0.2.177"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976"
[[package]]
name = "lock_api"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965"
dependencies = [
"scopeguard",
]
[[package]]
name = "memchr"
version = "2.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
[[package]]
name = "parking_lot"
version = "0.12.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a"
dependencies = [
"lock_api",
"parking_lot_core",
]
[[package]]
name = "parking_lot_core"
version = "0.9.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1"
dependencies = [
"cfg-if",
"libc",
"redox_syscall",
"smallvec",
"windows-link",
]
[[package]]
name = "ppv-lite86"
version = "0.2.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
dependencies = [
"zerocopy",
]
[[package]]
name = "proc-macro2"
version = "1.0.103"
@@ -192,6 +257,44 @@ version = "5.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
[[package]]
name = "rand"
version = "0.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1"
dependencies = [
"rand_chacha",
"rand_core",
]
[[package]]
name = "rand_chacha"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
dependencies = [
"ppv-lite86",
"rand_core",
]
[[package]]
name = "rand_core"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38"
dependencies = [
"getrandom",
]
[[package]]
name = "redox_syscall"
version = "0.5.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
dependencies = [
"bitflags 2.10.0",
]
[[package]]
name = "regex"
version = "1.12.2"
@@ -221,6 +324,12 @@ version = "0.8.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58"
[[package]]
name = "scopeguard"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "sha2"
version = "0.10.9"
@@ -232,6 +341,51 @@ dependencies = [
"digest",
]
[[package]]
name = "smallvec"
version = "1.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
[[package]]
name = "static_init"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8bae1df58c5fea7502e8e352ec26b5579f6178e1fdb311e088580c980dee25ed"
dependencies = [
"bitflags 1.3.2",
"cfg_aliases 0.2.1",
"libc",
"parking_lot",
"parking_lot_core",
"static_init_macro",
"winapi",
]
[[package]]
name = "static_init_macro"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1389c88ddd739ec6d3f8f83343764a0e944cd23cfbf126a9796a714b0b6edd6f"
dependencies = [
"cfg_aliases 0.1.1",
"memchr",
"proc-macro2",
"quote",
"syn 1.0.109",
]
[[package]]
name = "syn"
version = "1.0.109"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "syn"
version = "2.0.109"
@@ -275,7 +429,9 @@ version = "0.0.0"
dependencies = [
"proc-macro2",
"quote",
"syn",
"rand",
"static_init",
"syn 2.0.109",
"unshell-crypt",
]
@@ -294,8 +450,56 @@ dependencies = [
"wit-bindgen",
]
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-link"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
[[package]]
name = "wit-bindgen"
version = "0.46.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59"
[[package]]
name = "zerocopy"
version = "0.8.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd74ec98b9250adb3ca554bdde269adf631549f51d8a8f8f0a10b50f1cb298c3"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.8.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8a8d209fdf45cf5138cbb5a506f6b52522a25afccc534d1475dad8e31105c6a"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.109",
]
+4 -2
View File
@@ -10,8 +10,10 @@ proc-macro = true
obfuscate = []
[dependencies]
unshell-crypt = {path = "../unshell-crypt"}
quote = "1.0.42"
syn = {version = "2.0.109", features = ["full"]}
proc-macro2 = "1.0.103"
unshell-crypt = {path = "../unshell-crypt"}
static_init = "1.0.4"
rand = "0.9.2"
+209
View File
@@ -0,0 +1,209 @@
use proc_macro::TokenStream;
use quote::quote;
use rand::rngs::SmallRng;
use rand::{Rng, RngCore, SeedableRng};
use syn::{LitFloat, parse_macro_input};
const MAX_INSTRUCTIONS: u32 = 20; // Maximum instructions per recursive block
const MIN_LENGTH: f64 = 10.; // Min length per 1/weight
// The full list of 64-bit registers in AT&T syntax (used by default in asm!)
const REGISTERS: &[&str] = &[
"%rax", "%rbx", "%rcx", "%rdx", "%rsi", "%rdi", "%r8", "%r9", "%r10", "%r11", "%r12", "%r13",
"%r14", "%r15",
];
// Conditional Jumps in AT&T syntax.
const COND_JUMPS: &[&str] = &[
"je", "jne", "jg", "jge", "jl", "jle", "ja", "jnb", "jc", "jnc", "jz", "jnz",
];
// Arithmetic/Logic operations with the 'q' (quad-word) suffix.
const ARITHITHMETIC_OPS: &[&str] = &["addq", "subq", "xorq", "andq", "orq"];
// --- Helper Functions for Modular Generation ---
/// Generates a unique label name for the given depth and ID.
fn generate_label(prefix: &str, depth: u32, block_id: u32, id: u32) -> String {
format!(".L_{}_{}_{}_{}", prefix, depth, block_id, id)
}
/// Generates a highly randomized, complex instruction using different addressing modes.
fn generate_complex_mutation(rng: &mut SmallRng) -> String {
let op = ARITHITHMETIC_OPS[rng.random_range(0..ARITHITHMETIC_OPS.len())];
match rng.random_range(0..3) {
// Pattern 0: Register-Immediate
// Example: "addq $0x1234, %rax"
0 => {
let reg = REGISTERS[rng.random_range(0..REGISTERS.len())];
let immediate = rng.random_range(1..=0xFFFF);
format!("\t{} ${}, {}", op, immediate, reg)
}
// Pattern 1: Register-Register
// Example: "xorq %rbx, %rcx"
1 => {
let reg_src = REGISTERS[rng.random_range(0..REGISTERS.len())];
let reg_dst = REGISTERS[rng.random_range(0..REGISTERS.len())];
format!("\t{} {}, {}", op, reg_src, reg_dst)
}
// Pattern 2: LEA (Complex Address Calculation)
// Example: "leaq (%rax, %rbx, 4), %rcx"
2 => {
let reg_base = REGISTERS[rng.random_range(0..REGISTERS.len())];
let reg_index = REGISTERS[rng.random_range(0..REGISTERS.len())];
let reg_dst = REGISTERS[rng.random_range(0..REGISTERS.len())];
let scale = 1 << rng.random_range(0..4); // Scale is 1, 2, 4, or 8
format!(
"\tleaq ({}, {}, {}), {}",
reg_base, reg_index, scale, reg_dst
)
}
_ => String::new(), // Should not happen
}
}
/// Generates a comparison followed by a conditional jump to a specific label.
fn generate_conditional_jump(rng: &mut SmallRng, label: &str) -> String {
let reg1 = REGISTERS[rng.random_range(0..REGISTERS.len())];
let reg2 = REGISTERS[rng.random_range(0..REGISTERS.len())];
let jump = COND_JUMPS[rng.random_range(0..COND_JUMPS.len())];
// Example: "cmpq %rdx, %rsi; jg .L_target_"
format!("\tcmpq {}, {}; {} {}\n", reg1, reg2, jump, label)
}
// --- The Core DAG Recursive Algorithm ---
fn generate_dag_block(weight: f64, rng: &mut SmallRng, depth: u32, id_counter: &mut u32) -> String {
// 1. Termination Check
if rng.random_bool(weight) {
return String::new(); // Stop recursion
}
let block_id = *id_counter;
*id_counter += 1;
// 2. Randomize Block Length: The length is now based on WEIGHT.
// If rng < WEIGHT, stop growing the block. Otherwise, continue.
let mut num_labels: u32 = 0;
while !rng.random_bool(weight) && num_labels < MAX_INSTRUCTIONS {
num_labels += 1;
}
// Ensure at least one instruction/label exists if we entered the block
if num_labels == 0 {
num_labels = 1;
}
// Generate all labels for this block (L0 to Ln-1)
let labels: Vec<String> = (0..num_labels)
.map(|i| generate_label("dag", depth, block_id, i))
.collect();
let mut assembly_block = String::new();
// 3. Instruction Loop and DAG construction
for i in 0..num_labels {
let current_label = &labels[i as usize];
assembly_block.push_str(&format!("{}:\n", current_label));
let mut instruction_count = 0;
// Generate a random number of mutations based on WEIGHT
while !rng.random_bool(weight.powi(2)) && instruction_count < MAX_INSTRUCTIONS * 2 {
assembly_block.push_str(&format!("{}\n", generate_complex_mutation(rng)));
instruction_count += 1;
}
// Conditional Forward Jump (Creates DAG edges)
if i < num_labels - 1 && !rng.random_bool(weight * 0.5) {
// Jump to a random label strictly ahead of the current one
let target_index = rng.random_range(i as usize + 1..num_labels as usize);
let target_label = &labels[target_index];
assembly_block.push_str(&generate_conditional_jump(rng, target_label));
}
// Recursive Call (Nesting)
if depth < 2 {
// Lower probability for deep nesting
assembly_block.push_str(&generate_dag_block(weight, rng, depth + 1, id_counter));
}
}
// 4. Backward Conditional Jump (Adds controlled cycles)
// Only at the end of the block, allowing a chance to loop back to an earlier instruction.
if num_labels > 1 && rng.random_bool(weight) {
let target_index = rng.random_range(0..num_labels as usize - 1);
let target_label = &labels[target_index];
assembly_block.push_str(&format!("{}\n", generate_complex_mutation(rng)));
assembly_block.push_str(&generate_conditional_jump(rng, target_label));
assembly_block.push_str("// Backward Conditional Jump to maintain short execution\n");
}
assembly_block
}
pub fn junk_asm(input: TokenStream) -> TokenStream {
// 1. Parse the input (expecting an optional f32 weight)
let weight: f64 = if input.is_empty() {
None
} else {
match parse_macro_input!(input as LitFloat).base10_parse::<f64>() {
Ok(w) => Some(w), // Clamp to a sensible range
Err(_) => None,
}
}
.expect("Expected F64");
// let final_weight = input_weight.unwrap_or(WEIGHT);
// 2. Setup
let mut rng = SmallRng::from_os_rng();
let mut id_counter = 0;
// let random_u64_addr: u64 = rng.next_u64(); // The simulated external address
// 3. Generate Assembly
let main_assembly = {
loop {
let res = generate_dag_block(weight, &mut rng, 0, &mut id_counter);
if res.len() as f64 > weight * MIN_LENGTH {
break res;
}
}
};
println!("{}", main_assembly);
// 4. Wrap in `asm!`
let expanded = quote! {
// Output will replace the junk_asm!(...) call
{
// Execute the code using the standard `asm!` macro.
unsafe {
#[allow(named_asm_labels)]
core::arch::asm!(
// The generated junk code
// Note: We MUST use AT&T syntax (e.g., %rax, $100) due to options(att_syntax)
// The code is generated in AT&T syntax.
#main_assembly,
// Pass the simulated external address into a temporary register (%r15)
// This allows instructions to reference an "external" scope using memory reads/writes.
// in(reg) external_addr_ref,
// Clobber all general-purpose registers to force saving/restoring
clobber_abi("sysv64"),
// Correct options for non-volatile junk code
options(att_syntax, nomem, nostack, preserves_flags)
);
}
}
};
expanded.into()
}
// NOTE: To make the example runnable, the `src/main.rs` file would now call
// junk_asm!(0.2) or junk_asm!(). The instruction sizes and jump structure
// are now compliant with your requirements.
+19 -143
View File
@@ -3,171 +3,47 @@
use proc_macro::TokenStream;
use quote::quote;
use syn::{ItemFn, parse_macro_input};
use syn::parse_macro_input;
mod format_helper;
use format_helper::*;
use syn::LitStr;
mod junk_asm;
// Put all encrypt-related dependencies in a module, so they are easier to use with the feature flag
#[allow(dead_code)]
mod no_obfuscate;
#[allow(dead_code)]
mod obfuscate;
#[cfg(not(feature = "obfuscate"))]
use no_obfuscate as obs;
#[cfg(feature = "obfuscate")]
mod obs_deps {
pub use unshell_crypt::BACKUP_ENV_KEY;
pub use unshell_crypt::ENV_KEY_NAME;
pub use unshell_crypt::STATIC_IV;
pub use unshell_crypt::aes::encrypt_aes_lines;
pub use unshell_crypt::fill;
}
#[cfg(feature = "obfuscate")]
use obs_deps::*;
use obfuscate as obs;
// String obfuscation
#[proc_macro]
#[cfg(not(feature = "obfuscate"))]
pub fn obs(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as LitStr);
(quote::quote! {
String::from(#input)
})
.into()
obs::obs(input)
}
#[proc_macro_attribute]
#[cfg(not(feature = "obfuscate"))]
pub fn obfuscated_symbol(_attr: TokenStream, item: TokenStream) -> TokenStream {
let func = parse_macro_input!(item as ItemFn);
TokenStream::from(quote! {
#[unsafe(no_mangle)]
#func
})
obs::obfuscated_symbol(_attr, item)
}
#[proc_macro]
#[cfg(not(feature = "obfuscate"))]
pub fn symbol(input: TokenStream) -> TokenStream {
input
}
#[proc_macro_attribute]
#[cfg(feature = "obfuscate")]
pub fn obfuscated_symbol(_attr: TokenStream, item: TokenStream) -> TokenStream {
// Parse the input function
let func = parse_macro_input!(item as ItemFn);
// Get the original function name
let fn_name = func.sig.ident.to_string();
// get the encryption key
let key_str = std::env::var(ENV_KEY_NAME).unwrap_or(BACKUP_ENV_KEY.to_owned());
// Generate the new, obfuscated name
let obfuscated_name = encrypt_aes_lines(&fn_name, &key_str, STATIC_IV);
// Create a new string literal for the name
let new_name_lit = LitStr::new(&obfuscated_name, func.sig.ident.span());
// Re-build the function, but add #[no_mangle]
// and rename the *exported* symbol via #[export_name]
TokenStream::from(quote! {
#[unsafe(export_name = #new_name_lit)]
#func
})
}
// --- NEW MACRO 2: The macro for the loader ---
#[proc_macro]
#[cfg(feature = "obfuscate")]
pub fn symbol(input: TokenStream) -> TokenStream {
// Parse the input as a string literal
let lit_str = parse_macro_input!(input as LitStr);
let original_name = lit_str.value();
// get the encryption key
let key_str = std::env::var(ENV_KEY_NAME).unwrap_or(BACKUP_ENV_KEY.to_owned());
// Generate the exact same obfuscated name
let obfuscated_name = encrypt_aes_lines(&original_name, &key_str, STATIC_IV);
// Expand to a static string literal
TokenStream::from(quote! {
#obfuscated_name
})
obs::symbol(input)
}
#[proc_macro]
#[cfg(feature = "obfuscate")]
pub fn obs(input: TokenStream) -> TokenStream {
// Parse the input as a string literal
let lit_str = parse_macro_input!(input as LitStr);
let original_str = lit_str.value();
// Handle empty strings explicitly
if original_str.is_empty() {
return TokenStream::from(quote! { String::new() });
}
// --- Obfuscated Branch Logic ---
// This code runs at compile-time
let str_bytes = original_str.as_bytes();
let len = str_bytes.len();
// 1. Generate a unique, random key for this string
let mut key = vec![0u8; len];
fill(&mut key).expect("Failed to get random bytes for XOR key");
// 2. XOR the string with the key
let mut obfuscated = Vec::with_capacity(len);
for i in 0..len {
obfuscated.push(str_bytes[i] ^ key[i]);
}
// 3. This is the code that will be injected into the user's binary
// It runs at *runtime* to decrypt the string.
let obfuscated_expansion = quote! {
{
// These static arrays are stored directly in your binary
static OBFUSCATED_DATA: [u8; #len] = [ #( #obfuscated ),* ];
static KEY_DATA: [u8; #len] = [ #( #key ),* ];
let mut decrypted = Vec::with_capacity(#len);
for i in 0..#len {
decrypted.push(OBFUSCATED_DATA[i] ^ KEY_DATA[i]);
}
// We can trust this since the source was a valid String literal
String::from_utf8(decrypted).unwrap()
}
};
TokenStream::from(obfuscated_expansion)
pub fn junk_asm(input: TokenStream) -> TokenStream {
junk_asm::junk_asm(input)
}
// #[proc_macro]
// pub fn file_literal(_input: TokenStream) -> TokenStream {
// // let input = input.into_iter().collect::<Vec<_>>();
// // if input.len() != 1 {
// // let msg = format!("expected exactly one input token, got {}", input.len());
// // return quote! { compile_error!(#msg) }.into();
// // }
// let string = file!();
// let lit_str = LitStr::new(string, proc_macro2::Span::call_site());
// // let string_lit = match LitStr::try_from(&input) {
// // // Error if the token is not a string literal
// // Err(e) => return e.to_compile_error(),
// // Ok(lit) => lit,
// // };
// (quote! {
// #lit_str
// })
// .into()
// }
//
#[proc_macro]
pub fn file_symbol(_input: TokenStream) -> TokenStream {
+23
View File
@@ -0,0 +1,23 @@
use proc_macro::TokenStream;
use quote::quote;
use syn::{ItemFn, LitStr, parse_macro_input};
pub fn obs(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as LitStr);
(quote::quote! {
String::from(#input)
})
.into()
}
pub fn obfuscated_symbol(_attr: TokenStream, item: TokenStream) -> TokenStream {
let func = parse_macro_input!(item as ItemFn);
TokenStream::from(quote! {
#[unsafe(no_mangle)]
#func
})
}
pub fn symbol(input: TokenStream) -> TokenStream {
input
}
+98
View File
@@ -0,0 +1,98 @@
use proc_macro::TokenStream;
use quote::quote;
use syn::{ItemFn, LitStr, parse_macro_input};
use unshell_crypt::{BACKUP_ENV_KEY, ENV_KEY_NAME, STATIC_IV, aes::encrypt_aes_lines, fill};
#[static_init::dynamic]
static KEY: String = {
std::env::var(ENV_KEY_NAME).unwrap_or({
// Diagnostic::new(proc_macro::Level::Warning, "Using default encryption key!").emit();
println!("Using default encryption key!");
BACKUP_ENV_KEY.to_owned()
})
};
pub fn obfuscated_symbol(_attr: TokenStream, item: TokenStream) -> TokenStream {
// Parse the input function
let func = parse_macro_input!(item as ItemFn);
// Get the original function name
let fn_name = func.sig.ident.to_string();
// Generate the new, obfuscated name
let obfuscated_name = encrypt_aes_lines(&fn_name, &KEY, STATIC_IV);
// Create a new string literal for the name
let new_name_lit = LitStr::new(&obfuscated_name, func.sig.ident.span());
// Re-build the function, but add #[no_mangle]
// and rename the *exported* symbol via #[export_name]
TokenStream::from(quote! {
#[unsafe(export_name = #new_name_lit)]
#func
})
}
pub fn symbol(input: TokenStream) -> TokenStream {
// Parse the input as a string literal
let lit_str = parse_macro_input!(input as LitStr);
let original_name = lit_str.value();
// Generate the exact same obfuscated name
let obfuscated_name = encrypt_aes_lines(&original_name, &KEY, STATIC_IV);
// Expand to a static string literal
TokenStream::from(quote! {
#obfuscated_name
})
}
pub fn obs(input: TokenStream) -> TokenStream {
// Parse the input as a string literal
let lit_str = parse_macro_input!(input as LitStr);
let original_str = lit_str.value();
// Handle empty strings explicitly
if original_str.is_empty() {
return TokenStream::from(quote! { String::new() });
}
// --- Obfuscated Branch Logic ---
// This code runs at compile-time
let str_bytes = original_str.as_bytes();
let len = str_bytes.len();
// 1. Generate a unique, random key for this string
let mut key = vec![0u8; len];
fill(&mut key).expect("Failed to get random bytes for XOR key");
// 2. XOR the string with the key
let mut obfuscated = Vec::with_capacity(len);
for i in 0..len {
obfuscated.push(str_bytes[i] ^ key[i]);
}
// 3. This is the code that will be injected into the user's binary
// It runs at *runtime* to decrypt the string.
let obfuscated_expansion = quote! {
{
// These static arrays are stored directly in your binary
static OBFUSCATED_DATA: [u8; #len] = [ #( #obfuscated ),* ];
static KEY_DATA: [u8; #len] = [ #( #key ),* ];
let mut decrypted = Vec::with_capacity(#len);
for i in 0..#len {
decrypted.push(OBFUSCATED_DATA[i] ^ KEY_DATA[i]);
}
// We can trust this since the source was a valid String literal
String::from_utf8(decrypted).unwrap()
}
};
TokenStream::from(obfuscated_expansion)
}