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