├── .gitignore ├── Cargo.toml ├── labyrinth_macros ├── @ ├── Cargo.toml ├── README.md └── src │ └── lib.rs ├── cryptify ├── tests │ └── macro_tests.rs ├── Cargo.toml ├── src │ └── lib.rs └── README.md ├── auto_obfuscate ├── Cargo.toml └── src │ ├── obfuscate.rs │ ├── flow │ └── flow_tests.rs │ ├── string │ └── string_tests.rs │ ├── flow.rs │ ├── main.rs │ ├── string.rs │ ├── rename │ └── rename_tests.rs │ └── rename.rs ├── .github └── workflows │ └── rust.yml ├── LICENSE ├── README.md └── Cargo.lock /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/Cargo.lock 3 | **/target/ 4 | obfuscated_code/ 5 | rust-obfuscator -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "auto_obfuscate", 4 | "cryptify", 5 | "labyrinth_macros", 6 | ] 7 | default-members = ["auto_obfuscate"] 8 | resolver = "2" -------------------------------------------------------------------------------- /labyrinth_macros/@: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "obf_macro" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | -------------------------------------------------------------------------------- /cryptify/tests/macro_tests.rs: -------------------------------------------------------------------------------- 1 | //integration tests testing whole crate 2 | #[test] 3 | fn test_encrypt_macro() { 4 | let decrypted = cryptify::encrypt_string!("Hello World"); 5 | assert_eq!("Hello World", decrypted); 6 | } 7 | 8 | #[test] 9 | fn test_flow_macro() { 10 | //manually test for now with cargo expand 11 | cryptify::flow_stmt!(); 12 | assert_eq!(1, 1); 13 | } 14 | -------------------------------------------------------------------------------- /auto_obfuscate/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rust-obfuscator" 3 | version = "1.1.1" 4 | edition = "2021" 5 | 6 | [[bin]] 7 | name = "rust-obfuscator" 8 | path = "src/main.rs" 9 | 10 | [dependencies] 11 | syn = { version = "2.0", features = ["full", "visit-mut", "visit"] } 12 | quote = "1.0" 13 | proc-macro2 = "1.0" 14 | rand = "0.8.0" 15 | regex = "1.5.4" 16 | clap = "3.0" 17 | cryptify = "3.1.0" -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Obfuscator Unit Tests 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - uses: actions/checkout@v3 15 | - name: Install Rust 16 | uses: actions-rs/toolchain@v1.0.6 17 | with: 18 | profile: minimal 19 | toolchain: stable 20 | override: true 21 | 22 | - name: Run tests 23 | run: RUST_TEST_THREADS=1 cargo test 24 | 25 | -------------------------------------------------------------------------------- /cryptify/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "cryptify" 3 | version = "3.1.1" 4 | edition = "2021" 5 | authors = ["Pranav Dronavalli "] 6 | description = "A procedural macro library to obfuscate Rust code. Provides compile-time string encryption and random flow obfuscation." 7 | license = "MIT" 8 | repository = "https://github.com/dronavallipranav/rust-obfuscator/tree/main/cryptify" 9 | 10 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 11 | 12 | [dependencies] 13 | labyrinth_macros = "2.0.0" 14 | -------------------------------------------------------------------------------- /cryptify/src/lib.rs: -------------------------------------------------------------------------------- 1 | //re-export proc macro crate 2 | pub use labyrinth_macros::*; 3 | 4 | /// A helper decryption function meant to decrypt encrypted strings at runtime 5 | /// 6 | /// # Parameters 7 | /// - `input`: The encrypted string literal 8 | /// 9 | pub fn decrypt_string(encrypted: &str) -> String { 10 | let key = std::env::var("CRYPTIFY_KEY").unwrap_or_else(|_| "xnasff3wcedj".to_string()); 11 | encrypted 12 | .chars() 13 | .zip(key.chars().cycle()) 14 | .map(|(encrypted_char, key_char)| ((encrypted_char as u8) ^ (key_char as u8)) as char) 15 | .collect() 16 | } 17 | -------------------------------------------------------------------------------- /labyrinth_macros/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "labyrinth_macros" 3 | version = "3.0.0" 4 | edition = "2021" 5 | authors = ["Pranav Dronavalli "] 6 | description = "A procedural macro crate for the cryptify crate meant to provide obfuscation through compile time string encryption. not meant to be used standalone" 7 | license = "MIT" 8 | repository = "https://github.com/dronavallipranav/rust-obfuscator/tree/main/labyrinth_macros" 9 | 10 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 11 | [lib] 12 | proc-macro = true 13 | 14 | [dependencies] 15 | syn = { version = "2.0", features = ["full", "visit-mut"] } 16 | quote = "1.0" 17 | proc-macro2 = "1.0" 18 | rand = "0.8.0" -------------------------------------------------------------------------------- /labyrinth_macros/README.md: -------------------------------------------------------------------------------- 1 | # labyrinth_macros 2 | 3 | `labyrinth_macros` is a procedural macro crate designed to complement the `cryptify` super crate. It provides compile-time string and control flow obfuscation capabilities, aimed at enhancing the security and complexity of Rust code. Not meant to be used standalone, necessary obfuscation features are in the super crate `cryptify` 4 | 5 | ## Features 6 | 7 | - **String Obfuscation**: Automatically encrypts string literals in your code at compile time, making them harder to read and understand. 8 | - **Flow Obfuscation**: Introduces dummy loops and random variables into control flows, enhancing the overall obfuscation of the logic. 9 | 10 | # License 11 | labyrinth_macros is licensed under the MIT License - see the [LICENSE](https://github.com/dronavallipranav/rust-obfuscator/blob/main/LICENSE) file for details. -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Pranav Dronavalli 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /auto_obfuscate/src/obfuscate.rs: -------------------------------------------------------------------------------- 1 | use crate::string::{ StringObfuscator, StringConfig }; 2 | use crate::rename::{ VariableRenamer, RenameConfig }; 3 | use crate::flow::{ FlowObfuscator, FlowConfig }; 4 | 5 | #[derive(Clone)] 6 | pub struct Config { 7 | pub rename_config: RenameConfig, 8 | pub flow_config: FlowConfig, 9 | pub string_config: StringConfig, 10 | } 11 | 12 | impl Config { 13 | pub fn default() -> Self { 14 | Self { 15 | rename_config: RenameConfig::default(), 16 | flow_config: FlowConfig::default(), 17 | string_config: StringConfig::default(), 18 | } 19 | } 20 | } 21 | 22 | pub struct Obfuscator { 23 | rename_obfuscator: VariableRenamer, 24 | flow_obfuscator: FlowObfuscator, 25 | string_obfuscator: StringObfuscator, 26 | } 27 | 28 | impl Obfuscator { 29 | #[allow(dead_code)] 30 | pub fn new() -> Self { 31 | Self { 32 | rename_obfuscator: VariableRenamer::new(RenameConfig::default()), 33 | flow_obfuscator: FlowObfuscator::new(FlowConfig::default()), 34 | string_obfuscator: StringObfuscator::new(StringConfig::default()), 35 | } 36 | } 37 | pub fn from_config(config: Config) -> Self { 38 | Self { 39 | rename_obfuscator: VariableRenamer::new(config.rename_config), 40 | flow_obfuscator: FlowObfuscator::new(config.flow_config), 41 | string_obfuscator: StringObfuscator::new(config.string_config), 42 | } 43 | } 44 | 45 | pub fn obfuscate(&mut self, code: &str) -> String { 46 | let mut result = code.to_string(); 47 | if self.string_obfuscator.enabled { 48 | result = self.string_obfuscator.obfuscate_strings(&result); 49 | } 50 | if self.flow_obfuscator.enabled { 51 | result = self.flow_obfuscator.flow_obfuscate(&result); 52 | } 53 | if self.rename_obfuscator.enabled { 54 | result = self.rename_obfuscator.rename(&result); 55 | } 56 | result 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /auto_obfuscate/src/flow/flow_tests.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | use super::*; 3 | 4 | #[test] 5 | fn test_loop_insertion() { 6 | let code = 7 | r#" 8 | fn main() { 9 | let mut num1 = 10; 10 | let num2 = 20; 11 | num1 = 30; 12 | let sum = num1 + num2; 13 | println!("The sum is: {}", sum); 14 | } 15 | "#; 16 | let flow_config = FlowConfig { 17 | enable_flow_obfuscation: true, 18 | use_macro: false, 19 | }; 20 | let mut obfuscator = FlowObfuscator::new(flow_config); 21 | let modified_code = obfuscator.flow_obfuscate(code); 22 | 23 | assert_ne!(modified_code, code); 24 | //check if loop is inserted at start of block 25 | assert!(modified_code.contains("_is_dummy_145"), "Dummy loop not found in modified code"); 26 | //valid rust code 27 | let parse_result = syn::parse_file(&modified_code); 28 | assert!(parse_result.is_ok(), "Modified code is not valid Rust code"); 29 | } 30 | 31 | #[test] 32 | fn test_loop_skip() { 33 | let code = 34 | r#" 35 | fn calculate_sum(num1: i32, num2: i32) -> i32 { 36 | num1 + num2 37 | } 38 | fn ok(){ 39 | 40 | } 41 | fn main() { 42 | let mut num1 = 10; 43 | let num2 = 20; 44 | num1 = 30; 45 | let sum = num1 + num2; 46 | println!("The sum is: {}", sum); 47 | } 48 | "#; 49 | let flow_config = FlowConfig { 50 | enable_flow_obfuscation: true, 51 | use_macro: false, 52 | }; 53 | let mut obfuscator = FlowObfuscator::new(flow_config); 54 | let modified_code = obfuscator.flow_obfuscate(code); 55 | 56 | assert_ne!(modified_code, code); 57 | //check if loop is inserted at start of block 58 | assert!(modified_code.contains("_is_dummy_145"), "Dummy loop not found in modified code"); 59 | 60 | let parse_result = syn::parse_file(&modified_code); 61 | assert!(parse_result.is_ok(), "Modified code is not valid Rust code"); 62 | 63 | let num_loops = modified_code.matches("_is_dummy_145").count(); 64 | assert!(num_loops == 2, "exactly two dummy loops not found in modified code"); 65 | } 66 | -------------------------------------------------------------------------------- /cryptify/README.md: -------------------------------------------------------------------------------- 1 | # cryptify 2 | 3 | `cryptify` is a procedural macro crate for compile-time rust obfuscation. It provides the user with string encryption and compile-time determined flow obfuscation and random variables which survive compile-time optimization. 4 | 5 | 6 | [rust-obfuscator](https://github.com/dronavallipranav/rust-obfuscator) - Check out this auto obfuscator tool for easier usage and integration 7 | ## Features 8 | 9 | - **String Obfuscation**: Automatically encrypts string literals in your code at compile time, making them harder to read and understand. 10 | - **Flow Obfuscation**: Introduces dummy loops and random variables into control flows, enhancing the overall obfuscation of the logic. 11 | 12 | # Usage 13 | 14 | ## Bring macro into scope 15 | ```rs 16 | use cryptify; 17 | 18 | fn main(){ 19 | let decrypted = cryptify::encrypt_string!("Hello, World!"); 20 | println!(decrypted); 21 | println!("{}", cryptify::encrypt_string!("formatted!")); 22 | } 23 | ``` 24 | 25 | Set the **CRYPTIFY_KEY** environment variable for custom encryption otherwise it defaults to defined fixed key 26 | 27 | ## Output 28 | ``` 29 | Hello World! 30 | formatted! 31 | ``` 32 | ## Example of expanded Flow_Stmt! 33 | 34 | ```rs 35 | { 36 | let _is_dummy_145 = true; 37 | let _dummy_upper_bound = 100; 38 | let _dummy_increment = 1i32; 39 | let mut _dummy_counter = 10i32; 40 | let _extra_dummy_var = 2i32; 41 | loop { 42 | if _dummy_counter > _dummy_upper_bound { 43 | break; 44 | } 45 | unsafe { 46 | std::ptr::write_volatile( 47 | &mut _dummy_counter, 48 | _dummy_counter + _dummy_increment, 49 | ); 50 | } 51 | } 52 | }; 53 | match (&1, &1) { 54 | (left_val, right_val) => { 55 | if !(*left_val == *right_val) { 56 | let kind = ::core::panicking::AssertKind::Eq; 57 | ::core::panicking::assert_failed( 58 | kind, 59 | &*left_val, 60 | &*right_val, 61 | ::core::option::Option::None, 62 | ); 63 | } 64 | } 65 | }; 66 | 67 | ``` 68 | 69 | # License 70 | cryptify is licensed under the MIT License - see the [LICENSE](https://github.com/dronavallipranav/rust-obfuscator/blob/main/LICENSE) file for details. 71 | -------------------------------------------------------------------------------- /auto_obfuscate/src/string/string_tests.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | use super::*; 3 | 4 | #[test] 5 | fn test_replacement_in_expr() { 6 | let code = r#" 7 | fn main() { 8 | let b = "Hello, world!"; 9 | } 10 | "#; 11 | let string_config = StringConfig::default(); 12 | let mut string_obfuscator = StringObfuscator::new(string_config); 13 | let obfuscated_code = string_obfuscator.obfuscate_strings(code); 14 | assert_ne!(code, obfuscated_code); 15 | assert!(obfuscated_code.contains("encrypt_string")); 16 | 17 | let parse_result = syn::parse_file(&obfuscated_code); 18 | assert!(parse_result.is_ok(), "Modified code is not valid Rust code"); 19 | } 20 | 21 | #[test] 22 | fn test_no_macro() { 23 | let code = 24 | r#" 25 | fn main() { 26 | println!("Hello, world!"); 27 | let word_re = Regex::new(r"\b\w+\b").unwrap(); 28 | } 29 | "#; 30 | let string_config = StringConfig::default(); 31 | let mut string_obfuscator = StringObfuscator::new(string_config); 32 | let obfuscated_code = string_obfuscator.obfuscate_strings(code); 33 | assert!(!obfuscated_code.contains("encrypt_string")); 34 | let parse_result = syn::parse_file(&obfuscated_code); 35 | assert!(parse_result.is_ok(), "Modified code is not valid Rust code"); 36 | } 37 | 38 | #[test] 39 | fn test_percentage() { 40 | let code = 41 | r#" 42 | fn main() { 43 | let a = "a"; 44 | let b = "b"; 45 | let c = "c"; 46 | let d = "d"; 47 | let e = "e"; 48 | let f = "f"; 49 | let g = "g"; 50 | let h = "h"; 51 | let i = "i"; 52 | let j = "j"; 53 | println!("Hello"); 54 | println!("Hello"); 55 | println!("Hello"); 56 | println!("Hello"); 57 | println!("Hello"); 58 | println!("Hello"); 59 | } 60 | "#; 61 | 62 | let mut string_config = StringConfig::default(); 63 | string_config.percentage = 80; 64 | let mut string_obfuscator = StringObfuscator::new(string_config); 65 | let obfuscated_code = string_obfuscator.obfuscate_strings(code); 66 | assert_ne!(code, obfuscated_code); 67 | assert!(obfuscated_code.contains("encrypt_string ! (\"h\")")); 68 | assert!(obfuscated_code.contains("let i = \"i\"")); 69 | println!("{}", obfuscated_code); 70 | 71 | let parse_result = syn::parse_file(&obfuscated_code); 72 | assert!(parse_result.is_ok(), "Modified code is not valid Rust code"); 73 | } 74 | -------------------------------------------------------------------------------- /auto_obfuscate/src/flow.rs: -------------------------------------------------------------------------------- 1 | use syn::{ visit_mut::VisitMut, Block, Stmt, parse_file, Expr, Pat, PatIdent, parse_quote }; 2 | use quote::quote; 3 | use rand::{ Rng, thread_rng }; 4 | use rand::seq::SliceRandom; 5 | 6 | #[cfg(test)] 7 | mod flow_tests; 8 | 9 | #[derive(Clone)] 10 | pub struct FlowConfig { 11 | pub enable_flow_obfuscation: bool, 12 | pub use_macro: bool, 13 | } 14 | impl FlowConfig { 15 | pub fn default() -> Self { 16 | Self { 17 | enable_flow_obfuscation: true, 18 | use_macro: true, 19 | } 20 | } 21 | } 22 | 23 | pub struct FlowObfuscator { 24 | loop_counter: u32, 25 | pub use_macro: bool, 26 | pub enabled: bool, 27 | } 28 | 29 | impl FlowObfuscator { 30 | pub fn new(config: FlowConfig) -> Self { 31 | Self { 32 | loop_counter: 0, 33 | use_macro: config.use_macro, 34 | enabled: config.enable_flow_obfuscation, 35 | } 36 | } 37 | pub fn flow_obfuscate(&mut self, code: &str) -> String { 38 | let ast = parse_file(code).expect("Failed to parse code"); 39 | let mut modified_ast = ast.clone(); 40 | self.visit_file_mut(&mut modified_ast); 41 | let modified_code = quote!(#modified_ast).to_string(); 42 | modified_code 43 | } 44 | //check to see if statement in block is dummy loop 45 | fn is_dummy_loop(stmt: &Stmt) -> bool { 46 | if let Stmt::Expr(Expr::Block(expr_block), _) = stmt { 47 | for stmt in &expr_block.block.stmts { 48 | if let Stmt::Local(local) = stmt { 49 | if let Pat::Ident(PatIdent { ident, .. }) = &local.pat { 50 | if ident == "_is_dummy_145" { 51 | return true; 52 | } 53 | } 54 | } 55 | } 56 | } 57 | 58 | false 59 | } 60 | //helper to generate random dummy loop 61 | fn generate_dummy_loop() -> Stmt { 62 | let mut rng = thread_rng(); 63 | 64 | let initial_value = rng.gen_range(1..=10); 65 | let increment_value = rng.gen_range(1..=5); 66 | let add_extra_dummy_variable = rng.gen_bool(0.5); 67 | 68 | let mut statements = vec![ 69 | quote! { let mut _dummy_counter = #initial_value; }, 70 | quote! { let _dummy_increment = #increment_value; }, 71 | quote! { let _dummy_upper_bound = 100; } 72 | ]; 73 | 74 | //add extra dummy variable occasionally 75 | if add_extra_dummy_variable { 76 | let extra_dummy_value = rng.gen_range(1..=10); 77 | statements.push(quote! { let _extra_dummy_var = #extra_dummy_value; }); 78 | } 79 | 80 | //randomize the order of variable assignments 81 | statements.shuffle(&mut rng); 82 | 83 | let loop_block = 84 | quote! { 85 | loop { 86 | if _dummy_counter > _dummy_upper_bound{ 87 | break; 88 | } 89 | //prevent compiler optimizations 90 | unsafe { 91 | std::ptr::write_volatile(&mut _dummy_counter, _dummy_counter + _dummy_increment); 92 | } 93 | } 94 | }; 95 | 96 | parse_quote! { 97 | { 98 | let _is_dummy_145 = true; 99 | #(#statements)* 100 | #loop_block 101 | } 102 | } 103 | } 104 | } 105 | 106 | impl VisitMut for FlowObfuscator { 107 | fn visit_block_mut(&mut self, block: &mut Block) { 108 | //check if the block already contains the dummy loop 109 | if block.stmts.iter().any(|stmt| Self::is_dummy_loop(stmt)) || self.loop_counter % 3 != 0 { 110 | self.loop_counter += 1; 111 | return; 112 | } 113 | //if use macro enabled, use macro to expand to dummy loop 114 | if self.use_macro { 115 | let macro_call = syn::parse_quote! { 116 | cryptify::flow_stmt!(); 117 | }; 118 | block.stmts.insert(0, macro_call); 119 | } else { 120 | let dummy_loop = Self::generate_dummy_loop(); 121 | block.stmts.insert(0, dummy_loop); 122 | } 123 | 124 | self.loop_counter += 1; 125 | syn::visit_mut::visit_block_mut(self, block); 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /auto_obfuscate/src/main.rs: -------------------------------------------------------------------------------- 1 | mod rename; 2 | mod flow; 3 | mod string; 4 | mod obfuscate; 5 | use crate::obfuscate::{ Obfuscator, Config }; 6 | use clap::{ App, Arg }; 7 | use std::path::Path; 8 | use std::fs; 9 | 10 | fn main() { 11 | //default config 12 | let mut config = Config::default(); 13 | let matches = App::new("Rust Automatic Obfuscator") 14 | .version("1.0") 15 | .author("Pranav Dronavalli") 16 | .about("Obfuscates Rust source code") 17 | .arg( 18 | Arg::with_name("path") 19 | .help("Path to the Rust file or directory") 20 | .required(true) 21 | .index(1) 22 | ) 23 | .arg(Arg::with_name("no_string").long("no_string").help("Disable string obfuscation")) 24 | .arg(Arg::with_name("no_flow").long("no_flow").help("Disable control flow obfuscation")) 25 | .arg( 26 | Arg::with_name("disable_macro") 27 | .long("disable_macro") 28 | .help("disable macro and modify source directly for flow obfuscation") 29 | ) 30 | .arg(Arg::with_name("var").long("var").help("Enable variable renaming")) 31 | .arg(Arg::with_name("p") 32 | .short('p') 33 | .long("percent_strings_to_encrypt") 34 | .help("set upper bound for string literal encryption") 35 | .value_name("PERCENTAGE")) 36 | .get_matches(); 37 | 38 | let path = matches.value_of("path").unwrap(); 39 | 40 | //disable string obfuscation if the flag is set 41 | if matches.is_present("no_string") { 42 | config.string_config.enable_string_obfuscation = false; 43 | } 44 | 45 | //disable flow obfuscation if the flag is set 46 | if matches.is_present("no_flow") { 47 | config.flow_config.enable_flow_obfuscation = false; 48 | } 49 | 50 | //disable use of proc macro if the flag is set 51 | if matches.is_present("disable_macro") { 52 | config.flow_config.use_macro = false; 53 | } 54 | //enable variable renaming if the flag is set 55 | if matches.is_present("var") { 56 | config.rename_config.enable_rename_obfuscation = true; 57 | } 58 | //set upper bound for string literal encryption 59 | if let Some(percentage) = matches.value_of("p") { 60 | config.string_config.percentage = match percentage.parse() { 61 | Ok(n) if n <= 100 => n, 62 | _ => { 63 | eprintln!("-p: expected integer between 0 and 100, got: `{}`", percentage); 64 | eprintln!("defaulting to 100%"); 65 | 100 66 | } 67 | }; 68 | } 69 | 70 | process_path(&path, &config); 71 | } 72 | 73 | fn process_path(path_str: &str, config: &Config) { 74 | let path = Path::new(path_str); 75 | if path.is_dir() { 76 | process_directory(path, config); 77 | } else if path.is_file() { 78 | process_file(path, config); 79 | } else { 80 | eprintln!("Invalid path: {}", path_str); 81 | } 82 | } 83 | //process all files in directory 84 | fn process_directory(dir_path: &Path, config: &Config) { 85 | for entry in fs::read_dir(dir_path).expect("Failed to read directory") { 86 | let entry = entry.expect("Failed to read entry"); 87 | let path = entry.path(); 88 | if path.is_file() { 89 | process_file(&path, config); 90 | } 91 | } 92 | } 93 | //read code from file 94 | fn process_file(file_path: &Path, config: &Config) { 95 | if file_path.extension().unwrap_or_default() == "rs" { 96 | let code = fs::read_to_string(file_path).expect("Failed to read file"); 97 | 98 | let mut obfuscator = Obfuscator::from_config(config.clone()); 99 | let obfuscated_code = obfuscator.obfuscate(&code); 100 | 101 | //check if obfuscated code is valid Rust code 102 | let parse_result = syn::parse_file(&obfuscated_code); 103 | if parse_result.is_err() { 104 | eprintln!("Obfuscated code is not valid Rust code"); 105 | return; 106 | } 107 | write_obfuscated_code(file_path, &obfuscated_code); 108 | } 109 | } 110 | //write file to obfuscated_code directory 111 | fn write_obfuscated_code(original_path: &Path, obfuscated_code: &str) { 112 | let obfuscated_dir = Path::new("obfuscated_code"); 113 | fs::create_dir_all(&obfuscated_dir).expect("Failed to create directory"); 114 | 115 | let obfuscated_path = obfuscated_dir.join(original_path.file_name().unwrap()); 116 | println!("Writing to {:?}", obfuscated_path); 117 | fs::write(obfuscated_path, obfuscated_code).expect("Failed to write obfuscated code"); 118 | } 119 | -------------------------------------------------------------------------------- /labyrinth_macros/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! `labyrinth_macros` crate provides procedural macros for compile-time obfuscation. NOT MEANT TO BE USED STANDALONE. 2 | //! 3 | //! This crate includes macros like `encrypt_string` and `flow_stmt` which are used 4 | //! to enhance the security of Rust code by obfuscating strings and control flows. 5 | use proc_macro::TokenStream; 6 | use quote::quote; 7 | use syn::*; 8 | use std::env; 9 | use rand::Rng; 10 | use rand::seq::SliceRandom; 11 | 12 | /// A procedural macro that adds a compile-time randomly generated loop and variables. 13 | /// 14 | /// # Note 15 | /// The unsafe operation is meant to help the dummy loop survive compiler optimizations. only writes to dummy variable 16 | /// 17 | #[proc_macro] 18 | pub fn flow_stmt(_: TokenStream) -> TokenStream { 19 | let mut rng = rand::thread_rng(); 20 | 21 | let initial_value = rng.gen_range(1..=10); 22 | let increment_value = rng.gen_range(1..=4); 23 | let add_extra_dummy_variable = rng.gen_bool(0.5); 24 | 25 | let mut statements = vec![ 26 | quote! { let mut _dummy_counter = #initial_value; }, 27 | quote! { let _dummy_increment = #increment_value; }, 28 | quote! { let _dummy_upper_bound = 100; } 29 | ]; 30 | 31 | //add random dummy variable occasionally 32 | if add_extra_dummy_variable { 33 | let extra_dummy_value = rng.gen_range(1..=10); 34 | statements.push(quote! { let _extra_dummy_var = #extra_dummy_value; }); 35 | } 36 | 37 | //randomize the order of variable assignments 38 | statements.shuffle(&mut rng); 39 | 40 | let loop_block = 41 | quote! { 42 | loop { 43 | if _dummy_counter > _dummy_upper_bound { 44 | break; 45 | } 46 | //prevent compiler optimizations 47 | unsafe { 48 | std::ptr::write_volatile(&mut _dummy_counter, _dummy_counter + _dummy_increment); 49 | } 50 | } 51 | }; 52 | 53 | let generated_loop = 54 | quote! { 55 | { 56 | let _is_dummy_145 = true; 57 | #(#statements)* 58 | #loop_block 59 | } 60 | }; 61 | 62 | TokenStream::from(generated_loop) 63 | } 64 | /// A procedural macro that encrypts a string literal at compile time. 65 | /// 66 | /// # Parameters 67 | /// - `input`: The string literal to be encrypted. 68 | /// 69 | #[proc_macro] 70 | pub fn encrypt_string(input: TokenStream) -> TokenStream { 71 | let input = parse_macro_input!(input as LitStr); 72 | let string = input.value(); 73 | 74 | //set key to seeded env key or default 75 | let key = env::var("CRYPTIFY_KEY").unwrap_or_else(|_| "xnasff3wcedj".to_string()); 76 | 77 | let encrypted_string = xor_cipher(&string, &key); 78 | 79 | let output = quote! { 80 | cryptify::decrypt_string(#encrypted_string).as_ref() 81 | }; 82 | 83 | TokenStream::from(output) 84 | } 85 | 86 | fn xor_cipher(input: &str, key: &str) -> String { 87 | input 88 | .chars() 89 | .zip(key.chars().cycle()) 90 | .map(|(input_char, key_char)| { ((input_char as u8) ^ (key_char as u8)) as char }) 91 | .collect() 92 | } 93 | 94 | //for self-contained tests 95 | #[allow(dead_code)] 96 | fn decrypt_string(encrypted: &str) -> String { 97 | let key = std::env::var("CRYPTIFY_KEY").unwrap_or_else(|_| "xnasff3wcedj".to_string()); 98 | encrypted 99 | .chars() 100 | .zip(key.chars().cycle()) 101 | .map(|(encrypted_char, key_char)| ((encrypted_char as u8) ^ (key_char as u8)) as char) 102 | .collect() 103 | } 104 | 105 | //unit tests testing decryption logic 106 | #[cfg(test)] 107 | mod tests { 108 | use super::*; 109 | 110 | #[test] 111 | fn test_xor_cipher_and_decrypt() { 112 | let key = "xnasff3wcedj"; 113 | let test_strings = ["Hello", "World", "1234", "!@#$%^&*()"]; 114 | 115 | for &original in &test_strings { 116 | let encrypted = xor_cipher(original, &key); 117 | let decrypted = decrypt_string(&encrypted); 118 | assert_eq!(original, decrypted, "Failed for string: {}", original); 119 | } 120 | } 121 | #[test] 122 | fn test_xor_cipher_and_decrypt_customkey() { 123 | //set key 124 | std::env::set_var("CRYPTIFY_KEY", "testkey"); 125 | //test loc from encrypt_string meant to extract key 126 | let key = env::var("CRYPTIFY_KEY").unwrap_or_else(|_| "xnasff3wcedj".to_string()); 127 | assert_eq!(key, "testkey"); 128 | 129 | let test_strings = ["Hello", "World", "1234", "!@#$%^&*()"]; 130 | for &original in &test_strings { 131 | let encrypted = xor_cipher(original, &key); 132 | let decrypted = decrypt_string(&encrypted); 133 | assert_eq!(original, decrypted, "Failed for string: {}", original); 134 | } 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /auto_obfuscate/src/string.rs: -------------------------------------------------------------------------------- 1 | use syn::{ 2 | visit_mut::VisitMut, 3 | visit::Visit, 4 | parse_file, 5 | parse_str, 6 | Expr, 7 | ExprLit, 8 | LitStr, 9 | Lit, 10 | File, 11 | Local, 12 | parse_quote, 13 | }; 14 | use quote::quote; 15 | use proc_macro2::{ TokenStream, TokenTree }; 16 | 17 | #[cfg(test)] 18 | mod string_tests; 19 | 20 | #[derive(Clone)] 21 | pub struct StringConfig { 22 | pub enable_string_obfuscation: bool, 23 | pub percentage: u8, 24 | } 25 | impl StringConfig { 26 | pub fn default() -> Self { 27 | Self { 28 | enable_string_obfuscation: true, 29 | percentage: 100, 30 | } 31 | } 32 | } 33 | 34 | pub struct StringObfuscator { 35 | pub enabled: bool, 36 | #[allow(dead_code)] 37 | percentage: u8, 38 | encrypted_count: usize, 39 | strings_to_encrypt: usize, 40 | num_strings_encrypted: usize, 41 | } 42 | 43 | impl StringObfuscator { 44 | pub fn new(config: StringConfig) -> Self { 45 | Self { 46 | enabled: config.enable_string_obfuscation, 47 | percentage: config.percentage, 48 | encrypted_count: 0, 49 | strings_to_encrypt: 0, 50 | num_strings_encrypted: 0, 51 | } 52 | } 53 | #[allow(dead_code)] 54 | fn process_macro_tokens(&self, tokens: TokenStream) -> TokenStream { 55 | tokens 56 | .into_iter() 57 | .map(|token| { 58 | ( 59 | match token { 60 | TokenTree::Literal(lit) => { 61 | //convert literal obj to string 62 | let lit_str = lit.to_string(); 63 | 64 | //replace literal obj with macro call 65 | if let Ok(lit_str) = parse_str::(&lit_str) { 66 | let macro_call: TokenStream = 67 | quote! { 68 | cryptify::encrypt_string!(#lit_str) 69 | }; 70 | return macro_call; 71 | } 72 | 73 | TokenTree::Literal(lit) 74 | } 75 | //handle nested groups in macro 76 | TokenTree::Group(group) => { 77 | let new_stream = self.process_macro_tokens(group.stream()); 78 | TokenTree::Group(proc_macro2::Group::new(group.delimiter(), new_stream)) 79 | } 80 | _ => token, 81 | } 82 | ).into() 83 | }) 84 | .collect() 85 | } 86 | 87 | pub fn obfuscate_strings(&mut self, code: &str) -> String { 88 | let ast = parse_file(code).expect("Failed to parse code"); 89 | 90 | let total_strings = count_string_literals(&ast); 91 | let strings_to_encrypt = ( 92 | ((self.percentage as f32) / 100.0) * 93 | (total_strings as f32) 94 | ).ceil() as usize; 95 | self.encrypted_count = 0; 96 | self.strings_to_encrypt = strings_to_encrypt; 97 | 98 | let mut modified_ast = ast.clone(); 99 | self.visit_file_mut(&mut modified_ast); 100 | let modified_code = quote!(#modified_ast).to_string(); 101 | modified_code 102 | } 103 | } 104 | 105 | impl VisitMut for StringObfuscator { 106 | //replace all string literals with call to obfuscation macro 107 | fn visit_local_mut(&mut self, local: &mut Local) { 108 | if let Some(local_init) = &mut local.init { 109 | if self.num_strings_encrypted >= self.strings_to_encrypt { 110 | return; 111 | } 112 | self.num_strings_encrypted += 1; 113 | 114 | 115 | //match on local variables that contain string literal assignments 116 | if let Expr::Lit(ExprLit { lit: Lit::Str(lit_str), .. }) = &*local_init.expr { 117 | let encrypted = quote! { cryptify::encrypt_string!(#lit_str) }; 118 | let new_expr: Expr = parse_quote!(#encrypted); 119 | *local_init.expr = *Box::new(new_expr); 120 | } 121 | } 122 | 123 | syn::visit_mut::visit_local_mut(self, local); 124 | } 125 | } 126 | 127 | struct StringLiteralCounter { 128 | count: usize, 129 | } 130 | 131 | impl StringLiteralCounter { 132 | fn new() -> Self { 133 | Self { count: 0 } 134 | } 135 | } 136 | 137 | impl<'ast> Visit<'ast> for StringLiteralCounter { 138 | fn visit_lit_str(&mut self, _lit_str: &'ast LitStr) { 139 | self.count += 1; 140 | } 141 | } 142 | 143 | fn count_string_literals(ast: &File) -> usize { 144 | let mut counter = StringLiteralCounter::new(); 145 | counter.visit_file(ast); 146 | counter.count 147 | } 148 | -------------------------------------------------------------------------------- /auto_obfuscate/src/rename/rename_tests.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | use super::*; 3 | use regex::Regex; 4 | 5 | //function for testing 6 | fn is_valid_rust_var_name(name: &str) -> bool { 7 | let re = Regex::new(r"^[a-zA-Z_][a-zA-Z0-9_]*$").unwrap(); 8 | re.is_match(name) 9 | } 10 | 11 | #[test] 12 | fn test_variable_renamer() { 13 | let code = 14 | r#" 15 | fn calculate_sum(a: i32, b: i32) -> i32 { 16 | let result = a + b; 17 | result 18 | } 19 | 20 | fn main() { 21 | let mut num1 = 10; 22 | let num2 = 20; 23 | num1 = 30; 24 | let sum = calculate_sum(num1, num2); 25 | println!("The sum is: {}", sum); 26 | } 27 | "#; 28 | let rename_config = RenameConfig { 29 | enable_rename_obfuscation: true, 30 | }; 31 | let mut renamer = VariableRenamer::new(rename_config); 32 | let modified_code = renamer.rename(code); 33 | 34 | //compare the modified code with the original 35 | assert_ne!(modified_code, code); 36 | 37 | //check if names used are all valid rust variable names 38 | for new_name in renamer.renamed_vars.values() { 39 | assert!(is_valid_rust_var_name(new_name), "Invalid variable name: {}", new_name); 40 | } 41 | 42 | //original names should not be found in modified code (except for sum TO DO: remove when string encryption is implemented) 43 | let original_names = vec!["calculate_sum", "result", "num1", "num2"]; 44 | for name in original_names { 45 | assert!( 46 | !modified_code.contains(name), 47 | "Original name '{}' still found in modified code", 48 | name 49 | ); 50 | } 51 | let parse_result = syn::parse_file(&modified_code); 52 | assert!(parse_result.is_ok(), "Modified code is not valid Rust code"); 53 | } 54 | #[test] 55 | fn test_nested_function_calls() { 56 | let code = 57 | r#" 58 | fn add_one(x: i32) -> i32 { 59 | x + 1 60 | } 61 | 62 | fn calculate_sum(a: i32, b: i32) -> i32 { 63 | let result = a + b; 64 | result 65 | } 66 | 67 | fn main() { 68 | let mut num1 = 10; 69 | let num2 = 20; 70 | num1 = 30; 71 | let sum = calculate_sum(add_one(num1), num2); 72 | println!("The sum is: {}", sum); 73 | } 74 | "#; 75 | let rename_config = RenameConfig { 76 | enable_rename_obfuscation: true, 77 | }; 78 | let mut renamer = VariableRenamer::new(rename_config); 79 | let modified_code = renamer.rename(code); 80 | 81 | let original_names = vec!["calculate_sum", "add_one", "num1", "num2", "result"]; 82 | for name in original_names { 83 | assert!( 84 | !modified_code.contains(name), 85 | "Original function name '{}' still found in modified code", 86 | name 87 | ); 88 | } 89 | let parse_result = syn::parse_file(&modified_code); 90 | assert!(parse_result.is_ok(), "Modified code is not valid Rust code"); 91 | } 92 | 93 | #[test] 94 | fn test_nested_macros() { 95 | let code = 96 | r#" 97 | fn main() { 98 | let num1 = 10; 99 | let num2 = 20; 100 | println!("Formatted: {}", format!("Num1: {}, Num2: {}", num1, num2)); 101 | } 102 | "#; 103 | let rename_config = RenameConfig { 104 | enable_rename_obfuscation: true, 105 | }; 106 | let mut renamer = VariableRenamer::new(rename_config); 107 | let modified_code = renamer.rename(code); 108 | 109 | let original_names = vec!["calculate_sum", "add_one", "num1", "num2", "result"]; 110 | for name in original_names { 111 | assert!( 112 | !modified_code.contains(name), 113 | "Original function name '{}' still found in modified code", 114 | name 115 | ); 116 | } 117 | let parse_result = syn::parse_file(&modified_code); 118 | assert!(parse_result.is_ok(), "Modified code is not valid Rust code"); 119 | } 120 | #[test] 121 | fn test_user_defined_nested_macro() { 122 | let code = 123 | r#" 124 | macro_rules! identity { 125 | ($x:expr) => ($x) 126 | } 127 | 128 | fn main() { 129 | let num1 = 10; 130 | let num2 = 20; 131 | println!("Num1: {}", identity!(num1)); 132 | println!("Num2: {}", identity!(num2)); 133 | } 134 | "#; 135 | let rename_config = RenameConfig { 136 | enable_rename_obfuscation: true, 137 | }; 138 | let mut renamer = VariableRenamer::new(rename_config); 139 | let modified_code = renamer.rename(code); 140 | 141 | let original_names = vec!["calculate_sum", "add_one", "num1", "num2", "result"]; 142 | for name in original_names { 143 | assert!( 144 | !modified_code.contains(name), 145 | "Original function name '{}' still found in modified code", 146 | name 147 | ); 148 | } 149 | let parse_result = syn::parse_file(&modified_code); 150 | assert!(parse_result.is_ok(), "Modified code is not valid Rust code"); 151 | } 152 | #[test] 153 | fn test_function_in_macro() { 154 | let code = 155 | r#" 156 | fn add_one(x: i32) -> i32 { 157 | x + 1 158 | } 159 | 160 | fn main() { 161 | let num = 10; 162 | println!("Num + 1: {}", add_one(num)); 163 | } 164 | "#; 165 | let rename_config = RenameConfig { 166 | enable_rename_obfuscation: true, 167 | }; 168 | let mut renamer = VariableRenamer::new(rename_config); 169 | let modified_code = renamer.rename(code); 170 | 171 | let original_names = vec!["calculate_sum", "add_one", "num1", "num2", "result"]; 172 | for name in original_names { 173 | assert!( 174 | !modified_code.contains(name), 175 | "Original function name '{}' still found in modified code", 176 | name 177 | ); 178 | } 179 | let parse_result = syn::parse_file(&modified_code); 180 | assert!(parse_result.is_ok(), "Modified code is not valid Rust code"); 181 | } 182 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Rust-Obfuscator 2 | 3 | `rust-obfuscator` is a set of tools designed to automatically obfuscate Rust source code by inserting procedural macros or by (optionally) providing the obfuscation in the source code directly. For more fine-grained obfuscation a procedural macro library [cryptify](https://crates.io/crates/cryptify) is also provided. 4 | 5 | ## Currently Supported 6 | 1. string literal encryption 7 | 2. control-flow obfuscation 8 | 3. control-flow obfuscation (source code) 9 | 4. variable renaming (source code) 10 | 11 | ## Features 12 | 13 | - **String Encryption**: Automatically encrypts string literals assigned to local variables at compile time. 14 | - Can also be used for formatted strings, but currently requires manual placement 15 | ```rs 16 | println!("{}", cryptify::encrypt_string!("hello!")); 17 | ``` 18 | - **Control Flow Obfuscation**: Introduces compile-dummy dummy loops and random variables. 19 | - **Customizable Obfuscation**: Offers flexibility to enable or disable specific obfuscation features based on your requirements. 20 | - **Variable Renaming**: Obfuscation of the source code directly, if you'd like to ship the code or just want to make your code look worse. 21 | - NOTE: var renaming not fully functional as of 1.1.1 working on full-support as some cases aren't covered yet. Can still use the tool and fix the appropiate compiler errors. 22 | 23 | ## Installation 24 | 25 | Add `cryptify` to your `Cargo.toml` as a dependency: 26 | 27 | ```toml 28 | [dependencies] 29 | cryptify = "3.1.1" 30 | ``` 31 | 32 | To install `rust-obfuscator`, clone the repository and build the tool using Cargo from the root: 33 | ``` 34 | cargo build --release --bin rust-obfuscator 35 | ``` 36 | The binary can then be found under /target/release, you can copy it to the root of the project like such 37 | ``` 38 | cp ./target/release/rust-obfuscator . 39 | ``` 40 | 41 | # Usage 42 | Set the **CRYPTIFY_KEY** environment variable for custom encryption otherwise it defaults to defined fixed key 43 | - Add to source code you'd like to modify 44 | ```rs 45 | use cryptify; 46 | ``` 47 | The binary can be used on either a file or a directory. If provided with a directory it will only modify rust source files within that directory not any subdirectories 48 | ```sh 49 | ./rust-obfuscator path/to/your_project 50 | ``` 51 | - All Obfuscated code will be under the **obfuscated_code** directory that is created from the directory the tool was run. 52 | - **Recommended to use a Rust Formatter with the obfuscated code as syn naturally modifies the structure and it will be written to the file as one line** 53 | 54 | ## Option Flags 55 | - --no_string: Disables string obfuscation. 56 | - --no_flow: Disables control flow obfuscation. 57 | - --disable_macro: Uses direct source manipulation for flow obfuscation instead of procedural macros. 58 | - --var: Enables variable renaming source code obfuscation. 59 | 60 | ### Example usage with flag 61 | ```sh 62 | rust-obfuscator path/to/your_project --no_flow 63 | ``` 64 | (disables flow obfuscation) 65 | 66 | # Input 67 | -running the tool with no config 68 | ```rs 69 | use cryptify; 70 | mod word_counter; 71 | use std::env; 72 | use std::fs; 73 | use word_counter::count_words; 74 | fn main() { 75 | let b = "Hello World"; 76 | println!("{}", b); 77 | let args: Vec = env::args().collect(); 78 | if args.len() < 2 { 79 | eprintln!("Usage: {} ", args[0]); 80 | return; 81 | } 82 | let filename = &args[1]; 83 | let content = fs::read_to_string(filename).expect("Could not read file"); 84 | let word_counts = count_words(&content); 85 | for (word, count) in word_counts.iter() { 86 | println!("{}: {}", word, count); 87 | } 88 | } 89 | 90 | fn dummy() { 91 | let a = 1; 92 | let b = 2; 93 | let c = a + b; 94 | println!("{}", c); 95 | } 96 | 97 | fn calc_sum(a: i32, b: i32) -> i32 { 98 | cryptify::flow_stmt!(); 99 | let c = a + b; 100 | c 101 | } 102 | 103 | fn helloooo(){ 104 | println!("hi"); 105 | } 106 | 107 | ``` 108 | # Output 109 | ```rs 110 | fn main() { 111 | cryptify::flow_stmt!(); 112 | let b = cryptify::encrypt_string!("Hello World"); 113 | println!("{}", b); 114 | let args: Vec = env::args().collect(); 115 | if args.len() < 2 { 116 | eprintln!("Usage: {} ", args[0]); 117 | return; 118 | } 119 | let filename = &args[1]; 120 | let content = fs::read_to_string(filename).expect("Could not read file"); 121 | let word_counts = count_words(&content); 122 | for (word, count) in word_counts.iter() { 123 | println!("{}: {}", word, count); 124 | } 125 | } 126 | fn dummy() { 127 | cryptify::flow_stmt!(); 128 | let a = 1; 129 | let b = 2; 130 | let c = a + b; 131 | println!("{}", c); 132 | } 133 | fn calc_sum(a: i32, b: i32) -> i32 { 134 | cryptify::flow_stmt!(); 135 | let c = a + b; 136 | c 137 | } 138 | fn helloooo() { 139 | println!("hi"); 140 | } 141 | ``` 142 | ## Expanded Output 143 | ```rs 144 | fn main() { 145 | { 146 | let _is_dummy_145 = true; 147 | let _dummy_upper_bound = 100; 148 | let _random_dummy_var = 1; 149 | let mut _dummy_counter = 6i32; 150 | let _dummy_increment = 2i32; 151 | loop { 152 | if _dummy_counter > _dummy_upper_bound { 153 | break; 154 | } 155 | unsafe { 156 | std::ptr::write_volatile( 157 | &mut _dummy_counter, 158 | _dummy_counter + _dummy_increment, 159 | ); 160 | } 161 | } 162 | }; 163 | let b = cryptify::decrypt_string("0\u{b}\r\u{1f}\tFd\u{18}\u{11}\t\0"); 164 | { 165 | ::std::io::_print(format_args!("{0}\n", b)); 166 | }; 167 | let args: Vec = env::args().collect(); 168 | if args.len() < 2 { 169 | { 170 | ::std::io::_eprint(format_args!("Usage: {0} \n", args[0])); 171 | }; 172 | return; 173 | } 174 | let filename = &args[1]; 175 | let content = fs::read_to_string(filename).expect("Could not read file"); 176 | let word_counts = count_words(&content); 177 | for (word, count) in word_counts.iter() { 178 | { 179 | ::std::io::_print(format_args!("{0}: {1}\n", word, count)); 180 | }; 181 | } 182 | } 183 | fn dummy() { 184 | { 185 | let _is_dummy_145 = true; 186 | let mut _dummy_counter = 4i32; 187 | let _dummy_upper_bound = 100; 188 | let _dummy_increment = 3i32; 189 | loop { 190 | if _dummy_counter > _dummy_upper_bound { 191 | break; 192 | } 193 | unsafe { 194 | std::ptr::write_volatile( 195 | &mut _dummy_counter, 196 | _dummy_counter + _dummy_increment, 197 | ); 198 | } 199 | } 200 | }; 201 | let a = 1; 202 | let b = 2; 203 | let c = a + b; 204 | { 205 | ::std::io::_print(format_args!("{0}\n", c)); 206 | }; 207 | } 208 | fn calc_sum(a: i32, b: i32) -> i32 { 209 | { 210 | let _is_dummy_145 = true; 211 | let mut _dummy_counter = 8i32; 212 | let _dummy_increment = 3i32; 213 | let _extra_dummy_var = 4i32; 214 | let _dummy_upper_bound = 100; 215 | loop { 216 | if _dummy_counter > _dummy_upper_bound { 217 | break; 218 | } 219 | unsafe { 220 | std::ptr::write_volatile( 221 | &mut _dummy_counter, 222 | _dummy_counter + _dummy_increment, 223 | ); 224 | } 225 | } 226 | }; 227 | let c = a + b; 228 | c 229 | } 230 | fn helloooo() { 231 | { 232 | ::std::io::_print(format_args!("hi\n")); 233 | }; 234 | } 235 | ``` 236 | # License 237 | rust-obfuscator is licensed under the MIT License - see the [LICENSE](https://github.com/dronavallipranav/rust-obfuscator/blob/main/LICENSE) file for details. 238 | -------------------------------------------------------------------------------- /auto_obfuscate/src/rename.rs: -------------------------------------------------------------------------------- 1 | use rand::{ Rng }; 2 | use syn::{ 3 | visit_mut::VisitMut, 4 | parse_file, 5 | Ident, 6 | ItemFn, 7 | Local, 8 | Expr, 9 | ExprPath, 10 | Macro, 11 | Visibility, 12 | UseTree, 13 | UsePath, 14 | UseName, 15 | UseRename, 16 | ItemUse, 17 | }; 18 | use quote::quote; 19 | use std::collections::{ HashMap, HashSet }; 20 | use proc_macro2::{ TokenStream, TokenTree, Group }; 21 | 22 | #[cfg(test)] 23 | mod rename_tests; 24 | 25 | #[derive(Clone)] 26 | pub struct RenameConfig { 27 | pub enable_rename_obfuscation: bool, 28 | } 29 | 30 | //default rename to false 31 | impl RenameConfig { 32 | pub fn default() -> Self { 33 | Self { 34 | enable_rename_obfuscation: false, 35 | } 36 | } 37 | } 38 | 39 | pub struct VariableRenamer { 40 | renamed_vars: HashMap, 41 | imported_functions: HashSet, 42 | pub enabled: bool, 43 | } 44 | 45 | impl VariableRenamer { 46 | pub fn new(config: RenameConfig) -> Self { 47 | VariableRenamer { 48 | renamed_vars: HashMap::new(), 49 | imported_functions: HashSet::new(), 50 | enabled: config.enable_rename_obfuscation, 51 | } 52 | } 53 | //helper to process Macros tokenstream and check if it is an identifier or another macro or func call 54 | fn process_tokens(&mut self, tokens: TokenStream) -> TokenStream { 55 | tokens 56 | .into_iter() 57 | .map(|token| { 58 | match token { 59 | TokenTree::Group(group) => { 60 | let modified_tokens = self.process_tokens(group.stream()); 61 | TokenTree::Group(Group::new(group.delimiter(), modified_tokens)) 62 | } 63 | TokenTree::Ident(ident) => { 64 | if let Some(new_name) = self.renamed_vars.get(&ident.to_string()) { 65 | TokenTree::Ident(Ident::new(new_name, ident.span())) 66 | } else { 67 | TokenTree::Ident(ident) 68 | } 69 | } 70 | _ => token, 71 | } 72 | }) 73 | .collect() 74 | } 75 | 76 | //scan use statements to identify imported functions and add them to blacklist 77 | fn identify_imported_functions(&mut self, tree: &UseTree) { 78 | match tree { 79 | UseTree::Path(UsePath { ident: _, tree, .. }) => { 80 | self.identify_imported_functions(tree); 81 | } 82 | UseTree::Name(UseName { ident }) => { 83 | self.imported_functions.insert(ident.to_string()); 84 | } 85 | UseTree::Rename(UseRename { rename, .. }) => { 86 | self.imported_functions.insert(rename.to_string()); 87 | } 88 | _ => {} 89 | } 90 | } 91 | pub fn rename(&mut self, code: &str) -> String { 92 | let ast = parse_file(code).expect("Failed to parse code"); 93 | let mut modified_ast = ast.clone(); 94 | self.visit_file_mut(&mut modified_ast); 95 | let modified_code = quote!(#modified_ast).to_string(); 96 | modified_code 97 | } 98 | } 99 | 100 | //check to see if the function is local, only rename local functions for now 101 | fn is_local_function(fn_item: &ItemFn) -> bool { 102 | !matches!(fn_item.vis, Visibility::Public(_)) 103 | } 104 | 105 | impl VisitMut for VariableRenamer { 106 | //visit use statements to identify imported functions 107 | fn visit_item_use_mut(&mut self, i: &mut ItemUse) { 108 | self.identify_imported_functions(&i.tree); 109 | syn::visit_mut::visit_item_use_mut(self, i); 110 | } 111 | 112 | fn visit_item_fn_mut(&mut self, i: &mut ItemFn) { 113 | //rename function names unless it's main 114 | let old_name = i.sig.ident.to_string(); 115 | if 116 | old_name != "main" && 117 | is_local_function(i) && 118 | !self.imported_functions.contains(&old_name) 119 | { 120 | if !self.renamed_vars.contains_key(&old_name) { 121 | let new_name = random_name(); 122 | self.renamed_vars.insert(old_name.clone(), new_name.clone()); 123 | i.sig.ident = Ident::new(&new_name, i.sig.ident.span()); 124 | } 125 | } 126 | //rename function arguments 127 | for input in &mut i.sig.inputs { 128 | if let syn::FnArg::Typed(pat_type) = input { 129 | if let syn::Pat::Ident(pat_ident) = &mut *pat_type.pat { 130 | let old_param = pat_ident.ident.to_string(); 131 | let new_param = random_name(); 132 | self.renamed_vars.insert(old_param.clone(), new_param.clone()); 133 | pat_ident.ident = Ident::new(&new_param, pat_ident.ident.span()); 134 | } 135 | } 136 | } 137 | 138 | let len = i.block.stmts.len(); 139 | for (index, stmt) in i.block.stmts.iter_mut().enumerate() { 140 | match stmt { 141 | syn::Stmt::Local(local) => self.visit_local_mut(local), 142 | 143 | //check if last statement is an expression 144 | syn::Stmt::Expr(expr, _) if index == len - 1 => { 145 | self.visit_expr_mut(expr); 146 | } 147 | 148 | _ => syn::visit_mut::visit_stmt_mut(self, stmt), 149 | } 150 | } 151 | } 152 | 153 | fn visit_macro_mut(&mut self, i: &mut Macro) { 154 | i.tokens = self.process_tokens(i.tokens.clone()); 155 | } 156 | 157 | fn visit_expr_mut(&mut self, expr: &mut Expr) { 158 | //check expression type and rename vars accordingly 159 | match expr { 160 | Expr::Path(ExprPath { ref mut path, .. }) => { 161 | if let Some(last_segment) = path.segments.last_mut() { 162 | let var_name = last_segment.ident.to_string(); 163 | if let Some(new_name) = self.renamed_vars.get(&var_name) { 164 | last_segment.ident = Ident::new(new_name, last_segment.ident.span()); 165 | } 166 | } 167 | } 168 | Expr::Assign(expr_assign) => { 169 | //check left of expression 170 | if let Expr::Path(ExprPath { ref mut path, .. }) = *expr_assign.left { 171 | if let Some(last_segment) = path.segments.last_mut() { 172 | let var_name = last_segment.ident.to_string(); 173 | if let Some(new_name) = self.renamed_vars.get(&var_name) { 174 | last_segment.ident = Ident::new(new_name, last_segment.ident.span()); 175 | } 176 | } 177 | } 178 | // recursively visit right of assignment in case of more complex expression 179 | self.visit_expr_mut(&mut *expr_assign.right); 180 | } 181 | //handle function call 182 | Expr::Call(expr_call) => { 183 | //rename function names 184 | if let Expr::Path(expr_path) = &mut *expr_call.func { 185 | if let Some(last_segment) = expr_path.path.segments.last_mut() { 186 | let func_name = last_segment.ident.to_string(); 187 | if let Some(new_name) = self.renamed_vars.get(&func_name) { 188 | last_segment.ident = Ident::new(new_name, last_segment.ident.span()); 189 | } 190 | } 191 | } 192 | //rename all function arguments 193 | for arg in &mut expr_call.args { 194 | self.visit_expr_mut(arg); 195 | } 196 | } 197 | 198 | _ => {} 199 | } 200 | 201 | syn::visit_mut::visit_expr_mut(self, expr); 202 | } 203 | 204 | //visit local variables 205 | fn visit_local_mut(&mut self, local: &mut Local) { 206 | if let Some(local_init) = &mut local.init { 207 | self.visit_expr_mut(local_init.expr.as_mut()); 208 | } 209 | //change variable name 210 | if let syn::Pat::Ident(ref mut pat_ident) = local.pat { 211 | let old_name = pat_ident.ident.to_string(); 212 | let new_name = random_name(); 213 | self.renamed_vars.insert(old_name, new_name.clone()); 214 | pat_ident.ident = Ident::new(&new_name, pat_ident.ident.span()); 215 | } 216 | } 217 | } 218 | 219 | // Function to generate a random name 220 | fn random_name() -> String { 221 | let mut rng = rand::thread_rng(); 222 | let name_length = rng.gen_range(3..=10); 223 | 224 | let mut last_char_was_underscore = false; 225 | let mut name = String::new(); 226 | 227 | while name.len() < name_length { 228 | let next_char = if rng.gen_bool(0.8) { rng.gen_range(b'a'..=b'z') as char } else { '_' }; 229 | 230 | // Ensure not two underscores in a row 231 | if !(last_char_was_underscore && next_char == '_') { 232 | name.push(next_char); 233 | last_char_was_underscore = next_char == '_'; 234 | } 235 | } 236 | // Ensure the name does not start or end with an underscore 237 | if name.starts_with('_') { 238 | name.remove(0); 239 | name.insert(0, rng.gen_range(b'a'..=b'z') as char); 240 | } 241 | if name.ends_with('_') { 242 | name.pop(); 243 | name.push(rng.gen_range(b'a'..=b'z') as char); 244 | } 245 | 246 | name 247 | } 248 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "aho-corasick" 7 | version = "1.1.2" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" 10 | dependencies = [ 11 | "memchr", 12 | ] 13 | 14 | [[package]] 15 | name = "atty" 16 | version = "0.2.14" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 19 | dependencies = [ 20 | "hermit-abi", 21 | "libc", 22 | "winapi", 23 | ] 24 | 25 | [[package]] 26 | name = "autocfg" 27 | version = "1.1.0" 28 | source = "registry+https://github.com/rust-lang/crates.io-index" 29 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 30 | 31 | [[package]] 32 | name = "bitflags" 33 | version = "1.3.2" 34 | source = "registry+https://github.com/rust-lang/crates.io-index" 35 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 36 | 37 | [[package]] 38 | name = "cfg-if" 39 | version = "1.0.0" 40 | source = "registry+https://github.com/rust-lang/crates.io-index" 41 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 42 | 43 | [[package]] 44 | name = "clap" 45 | version = "3.2.25" 46 | source = "registry+https://github.com/rust-lang/crates.io-index" 47 | checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123" 48 | dependencies = [ 49 | "atty", 50 | "bitflags", 51 | "clap_lex", 52 | "indexmap", 53 | "strsim", 54 | "termcolor", 55 | "textwrap", 56 | ] 57 | 58 | [[package]] 59 | name = "clap_lex" 60 | version = "0.2.4" 61 | source = "registry+https://github.com/rust-lang/crates.io-index" 62 | checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" 63 | dependencies = [ 64 | "os_str_bytes", 65 | ] 66 | 67 | [[package]] 68 | name = "cryptify" 69 | version = "3.1.0" 70 | source = "registry+https://github.com/rust-lang/crates.io-index" 71 | checksum = "763ef95b1ec9e4dfb083b3ba233395fe49b480259f9a1037312887492380cf9a" 72 | dependencies = [ 73 | "labyrinth_macros 2.0.0", 74 | ] 75 | 76 | [[package]] 77 | name = "cryptify" 78 | version = "3.1.1" 79 | dependencies = [ 80 | "labyrinth_macros 2.0.0", 81 | ] 82 | 83 | [[package]] 84 | name = "getrandom" 85 | version = "0.2.11" 86 | source = "registry+https://github.com/rust-lang/crates.io-index" 87 | checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f" 88 | dependencies = [ 89 | "cfg-if", 90 | "libc", 91 | "wasi", 92 | ] 93 | 94 | [[package]] 95 | name = "hashbrown" 96 | version = "0.12.3" 97 | source = "registry+https://github.com/rust-lang/crates.io-index" 98 | checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" 99 | 100 | [[package]] 101 | name = "hermit-abi" 102 | version = "0.1.19" 103 | source = "registry+https://github.com/rust-lang/crates.io-index" 104 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" 105 | dependencies = [ 106 | "libc", 107 | ] 108 | 109 | [[package]] 110 | name = "indexmap" 111 | version = "1.9.3" 112 | source = "registry+https://github.com/rust-lang/crates.io-index" 113 | checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" 114 | dependencies = [ 115 | "autocfg", 116 | "hashbrown", 117 | ] 118 | 119 | [[package]] 120 | name = "labyrinth_macros" 121 | version = "2.0.0" 122 | source = "registry+https://github.com/rust-lang/crates.io-index" 123 | checksum = "a49a429784bf214985bd5e5c058a89889a2e5b9fc60ebd60ced2bf0429791fa4" 124 | dependencies = [ 125 | "proc-macro2", 126 | "quote", 127 | "rand", 128 | "syn", 129 | ] 130 | 131 | [[package]] 132 | name = "labyrinth_macros" 133 | version = "3.0.0" 134 | dependencies = [ 135 | "proc-macro2", 136 | "quote", 137 | "rand", 138 | "syn", 139 | ] 140 | 141 | [[package]] 142 | name = "libc" 143 | version = "0.2.151" 144 | source = "registry+https://github.com/rust-lang/crates.io-index" 145 | checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4" 146 | 147 | [[package]] 148 | name = "memchr" 149 | version = "2.7.1" 150 | source = "registry+https://github.com/rust-lang/crates.io-index" 151 | checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" 152 | 153 | [[package]] 154 | name = "os_str_bytes" 155 | version = "6.6.1" 156 | source = "registry+https://github.com/rust-lang/crates.io-index" 157 | checksum = "e2355d85b9a3786f481747ced0e0ff2ba35213a1f9bd406ed906554d7af805a1" 158 | 159 | [[package]] 160 | name = "ppv-lite86" 161 | version = "0.2.17" 162 | source = "registry+https://github.com/rust-lang/crates.io-index" 163 | checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" 164 | 165 | [[package]] 166 | name = "proc-macro2" 167 | version = "1.0.75" 168 | source = "registry+https://github.com/rust-lang/crates.io-index" 169 | checksum = "907a61bd0f64c2f29cd1cf1dc34d05176426a3f504a78010f08416ddb7b13708" 170 | dependencies = [ 171 | "unicode-ident", 172 | ] 173 | 174 | [[package]] 175 | name = "quote" 176 | version = "1.0.35" 177 | source = "registry+https://github.com/rust-lang/crates.io-index" 178 | checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" 179 | dependencies = [ 180 | "proc-macro2", 181 | ] 182 | 183 | [[package]] 184 | name = "rand" 185 | version = "0.8.5" 186 | source = "registry+https://github.com/rust-lang/crates.io-index" 187 | checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" 188 | dependencies = [ 189 | "libc", 190 | "rand_chacha", 191 | "rand_core", 192 | ] 193 | 194 | [[package]] 195 | name = "rand_chacha" 196 | version = "0.3.1" 197 | source = "registry+https://github.com/rust-lang/crates.io-index" 198 | checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 199 | dependencies = [ 200 | "ppv-lite86", 201 | "rand_core", 202 | ] 203 | 204 | [[package]] 205 | name = "rand_core" 206 | version = "0.6.4" 207 | source = "registry+https://github.com/rust-lang/crates.io-index" 208 | checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" 209 | dependencies = [ 210 | "getrandom", 211 | ] 212 | 213 | [[package]] 214 | name = "regex" 215 | version = "1.10.2" 216 | source = "registry+https://github.com/rust-lang/crates.io-index" 217 | checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" 218 | dependencies = [ 219 | "aho-corasick", 220 | "memchr", 221 | "regex-automata", 222 | "regex-syntax", 223 | ] 224 | 225 | [[package]] 226 | name = "regex-automata" 227 | version = "0.4.3" 228 | source = "registry+https://github.com/rust-lang/crates.io-index" 229 | checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" 230 | dependencies = [ 231 | "aho-corasick", 232 | "memchr", 233 | "regex-syntax", 234 | ] 235 | 236 | [[package]] 237 | name = "regex-syntax" 238 | version = "0.8.2" 239 | source = "registry+https://github.com/rust-lang/crates.io-index" 240 | checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" 241 | 242 | [[package]] 243 | name = "rust-obfuscator" 244 | version = "1.0.0" 245 | dependencies = [ 246 | "clap", 247 | "cryptify 3.1.0", 248 | "proc-macro2", 249 | "quote", 250 | "rand", 251 | "regex", 252 | "syn", 253 | ] 254 | 255 | [[package]] 256 | name = "strsim" 257 | version = "0.10.0" 258 | source = "registry+https://github.com/rust-lang/crates.io-index" 259 | checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" 260 | 261 | [[package]] 262 | name = "syn" 263 | version = "2.0.47" 264 | source = "registry+https://github.com/rust-lang/crates.io-index" 265 | checksum = "1726efe18f42ae774cc644f330953a5e7b3c3003d3edcecf18850fe9d4dd9afb" 266 | dependencies = [ 267 | "proc-macro2", 268 | "quote", 269 | "unicode-ident", 270 | ] 271 | 272 | [[package]] 273 | name = "termcolor" 274 | version = "1.4.0" 275 | source = "registry+https://github.com/rust-lang/crates.io-index" 276 | checksum = "ff1bc3d3f05aff0403e8ac0d92ced918ec05b666a43f83297ccef5bea8a3d449" 277 | dependencies = [ 278 | "winapi-util", 279 | ] 280 | 281 | [[package]] 282 | name = "textwrap" 283 | version = "0.16.0" 284 | source = "registry+https://github.com/rust-lang/crates.io-index" 285 | checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d" 286 | 287 | [[package]] 288 | name = "unicode-ident" 289 | version = "1.0.12" 290 | source = "registry+https://github.com/rust-lang/crates.io-index" 291 | checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" 292 | 293 | [[package]] 294 | name = "wasi" 295 | version = "0.11.0+wasi-snapshot-preview1" 296 | source = "registry+https://github.com/rust-lang/crates.io-index" 297 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 298 | 299 | [[package]] 300 | name = "winapi" 301 | version = "0.3.9" 302 | source = "registry+https://github.com/rust-lang/crates.io-index" 303 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 304 | dependencies = [ 305 | "winapi-i686-pc-windows-gnu", 306 | "winapi-x86_64-pc-windows-gnu", 307 | ] 308 | 309 | [[package]] 310 | name = "winapi-i686-pc-windows-gnu" 311 | version = "0.4.0" 312 | source = "registry+https://github.com/rust-lang/crates.io-index" 313 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 314 | 315 | [[package]] 316 | name = "winapi-util" 317 | version = "0.1.6" 318 | source = "registry+https://github.com/rust-lang/crates.io-index" 319 | checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" 320 | dependencies = [ 321 | "winapi", 322 | ] 323 | 324 | [[package]] 325 | name = "winapi-x86_64-pc-windows-gnu" 326 | version = "0.4.0" 327 | source = "registry+https://github.com/rust-lang/crates.io-index" 328 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 329 | --------------------------------------------------------------------------------