imping_hook/inject.rs
1//! Code injection at an arbitrary address: machine code, assembly, or a
2//! register-context callback.
3//!
4//! Each runs the injected work, then the displaced instructions, then carries
5//! on. [`detour::RawDetour`] does the relocation and trampoline; only the stub
6//! is written here.
7
8#[cfg(target_arch = "x86_64")]
9use crate::context::Context;
10use crate::error::{Error, OsError, Result};
11use crate::registry;
12use crate::scan::{Pattern, Target};
13use crate::sys;
14use core::mem::ManuallyDrop;
15use core::ptr;
16use region::Protection;
17
18/// The length of the absolute jump that ends a raw-bytes stub.
19#[cfg(target_arch = "x86_64")]
20const ABS_JUMP_LEN: usize = 14;
21#[cfg(target_arch = "x86")]
22const ABS_JUMP_LEN: usize = 6;
23
24#[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
25compile_error!("imping-hook code injection supports only x86 and x86-64");
26
27/// Builds an absolute jump to `target` that clobbers no registers or flags.
28///
29/// `jmp qword ptr [rip+0]` plus the address on x86-64; `push imm32; ret` on
30/// x86, which leaves the stack and flags as they were.
31fn absolute_jump(target: usize) -> [u8; ABS_JUMP_LEN] {
32 let mut bytes = [0u8; ABS_JUMP_LEN];
33 #[cfg(target_arch = "x86_64")]
34 {
35 bytes[0] = 0xFF;
36 bytes[1] = 0x25; // jmp qword ptr [rip+0]; disp32 == 0 leaves bytes[2..6] zero
37 bytes[6..14].copy_from_slice(&(target as u64).to_le_bytes());
38 }
39 #[cfg(target_arch = "x86")]
40 {
41 bytes[0] = 0x68; // push imm32
42 bytes[1..5].copy_from_slice(&(target as u32).to_le_bytes());
43 bytes[5] = 0xC3; // ret
44 }
45 bytes
46}
47
48/// A register-context callback; `inspect` wraps its `Fn(&Context)` into this
49/// shape.
50#[cfg(target_arch = "x86_64")]
51type ContextFn = dyn Fn(&mut Context) + Send + Sync;
52
53/// An [`InjectionBuilder::asm`] closure, run once at install.
54#[cfg(target_arch = "x86_64")]
55type BoxedAsm = Box<
56 dyn FnOnce(
57 &mut iced_x86::code_asm::CodeAssembler,
58 ) -> core::result::Result<(), iced_x86::IcedError>,
59>;
60
61/// A generous upper bound on the size of a register-context stub.
62///
63/// The real stub is a few hundred bytes, and the allocation is page-rounded
64/// anyway.
65#[cfg(target_arch = "x86_64")]
66const CONTEXT_STUB_CAPACITY: usize = 1024;
67
68/// Calls a register-context callback with the saved context.
69///
70/// The stub calls this with the Win64 argument registers set.
71///
72/// # Safety
73///
74/// `ctx` must point to a valid, writable [`Context`], and `closure` to this
75/// injection's callback, both valid for the call.
76#[cfg(target_arch = "x86_64")]
77unsafe extern "C" fn context_trampoline(ctx: *mut Context, closure: *mut Box<ContextFn>) {
78 // SAFETY: the stub passes both pointers, valid for this call.
79 let (closure, ctx): (&ContextFn, &mut Context) = unsafe { (&**closure, &mut *ctx) };
80 // Never unwind into the patched (non-Rust) frames; carry on with the
81 // context as the closure left it.
82 let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| closure(ctx)));
83}
84
85/// Assembles the register-context stub.
86///
87/// It saves the full context on the stack, calls [`context_trampoline`],
88/// restores the (maybe changed) context, then jumps to the trampoline, or to a
89/// changed `rip` with the chosen `rsp`.
90#[cfg(target_arch = "x86_64")]
91fn assemble_context_stub(
92 hook_addr: u64,
93 closure_ptr: u64,
94 rust_trampoline: u64,
95 continue_at: u64,
96) -> core::result::Result<Vec<u8>, iced_x86::IcedError> {
97 use core::mem::{offset_of, size_of};
98 use iced_x86::code_asm::*;
99
100 let size = size_of::<Context>() as i64;
101 let o_rflags = offset_of!(Context, rflags) as i64;
102 let o_rsp = offset_of!(Context, rsp) as i64;
103 let o_rbx = offset_of!(Context, rbx) as i64;
104 let o_r11 = offset_of!(Context, r11) as i64;
105 let o_rip = offset_of!(Context, rip) as i64;
106 let o_xmm = offset_of!(Context, xmm) as i64;
107
108 let xmms = [
109 xmm0, xmm1, xmm2, xmm3, xmm4, xmm5, xmm6, xmm7, xmm8, xmm9, xmm10, xmm11, xmm12, xmm13,
110 xmm14, xmm15,
111 ];
112 // Every general-purpose register except rsp, which is derived.
113 let save = [
114 (rax, offset_of!(Context, rax) as i64),
115 (rbx, o_rbx),
116 (rcx, offset_of!(Context, rcx) as i64),
117 (rdx, offset_of!(Context, rdx) as i64),
118 (rsi, offset_of!(Context, rsi) as i64),
119 (rdi, offset_of!(Context, rdi) as i64),
120 (rbp, offset_of!(Context, rbp) as i64),
121 (r8, offset_of!(Context, r8) as i64),
122 (r9, offset_of!(Context, r9) as i64),
123 (r10, offset_of!(Context, r10) as i64),
124 (r11, o_r11),
125 (r12, offset_of!(Context, r12) as i64),
126 (r13, offset_of!(Context, r13) as i64),
127 (r14, offset_of!(Context, r14) as i64),
128 (r15, offset_of!(Context, r15) as i64),
129 ];
130 // Restored directly; rbx, r11 and rsp are handled specially at the end.
131 let restore = [
132 (rax, offset_of!(Context, rax) as i64),
133 (rcx, offset_of!(Context, rcx) as i64),
134 (rdx, offset_of!(Context, rdx) as i64),
135 (rsi, offset_of!(Context, rsi) as i64),
136 (rdi, offset_of!(Context, rdi) as i64),
137 (rbp, offset_of!(Context, rbp) as i64),
138 (r8, offset_of!(Context, r8) as i64),
139 (r9, offset_of!(Context, r9) as i64),
140 (r10, offset_of!(Context, r10) as i64),
141 (r12, offset_of!(Context, r12) as i64),
142 (r13, offset_of!(Context, r13) as i64),
143 (r14, offset_of!(Context, r14) as i64),
144 (r15, offset_of!(Context, r15) as i64),
145 ];
146
147 let mut a = CodeAssembler::new(64)?;
148
149 // --- Prologue: build the Context on the stack. ---
150 a.sub(rsp, size as i32)?;
151 for (reg, offset) in save {
152 a.mov(qword_ptr(rsp + offset), reg)?;
153 }
154 a.lea(rax, qword_ptr(rsp + size))?; // original rsp (entry rsp, no return address was pushed)
155 a.mov(qword_ptr(rsp + o_rsp), rax)?;
156 a.pushfq()?;
157 a.pop(rax)?;
158 a.mov(qword_ptr(rsp + o_rflags), rax)?;
159 a.mov(rax, hook_addr)?;
160 a.mov(qword_ptr(rsp + o_rip), rax)?;
161 for (index, xmm) in xmms.iter().copied().enumerate() {
162 a.movups(xmmword_ptr(rsp + o_xmm + index as i64 * 16), xmm)?;
163 }
164
165 // --- Call context_trampoline(&ctx, closure). ---
166 a.mov(rcx, rsp)?; // arg0 = &ctx
167 a.mov(rbx, rsp)?; // keep &ctx across the call (rbx is callee-saved)
168 a.mov(rdx, closure_ptr)?; // arg1 = closure
169 a.and(rsp, -16)?; // align the stack
170 a.sub(rsp, 32)?; // 32-byte shadow space
171 a.mov(rax, rust_trampoline)?;
172 a.call(rax)?;
173 a.mov(rsp, rbx)?; // rsp = &ctx again
174
175 // --- Epilogue: restore context, then jump to rip (or the trampoline). ---
176 for (index, xmm) in xmms.iter().copied().enumerate() {
177 a.movups(xmm, xmmword_ptr(rbx + o_xmm + index as i64 * 16))?;
178 }
179 a.mov(rax, qword_ptr(rbx + o_rflags))?;
180 a.push(rax)?;
181 a.popfq()?;
182
183 // Target rip: continue through the displaced originals if rip is unchanged,
184 // else the address the callback redirected to.
185 a.mov(rax, qword_ptr(rbx + o_rip))?;
186 a.mov(r11, hook_addr)?;
187 a.cmp(rax, r11)?;
188 a.mov(r11, continue_at)?;
189 a.cmove(rax, r11)?;
190
191 // Stage, on the target stack, the values the final pops/ret will load.
192 a.mov(r11, qword_ptr(rbx + o_rsp))?; // r11 = target rsp
193 a.mov(qword_ptr(r11 - 8), rax)?; // [target_rsp - 8] = target rip (for ret)
194 a.mov(rax, qword_ptr(rbx + o_rbx))?;
195 a.mov(qword_ptr(r11 - 16), rax)?; // [target_rsp - 16] = ctx.rbx
196 a.mov(rax, qword_ptr(rbx + o_r11))?;
197 a.mov(qword_ptr(r11 - 24), rax)?; // [target_rsp - 24] = ctx.r11
198 a.sub(r11, 24)?; // final rsp
199
200 for (reg, offset) in restore {
201 a.mov(reg, qword_ptr(rbx + offset))?;
202 }
203
204 a.mov(rsp, r11)?; // switch to the (possibly redirected) stack
205 a.pop(r11)?; // ctx.r11
206 a.pop(rbx)?; // ctx.rbx
207 a.ret()?; // -> target rip, with rsp = ctx.rsp
208
209 a.assemble(0)
210}
211
212/// A code injection, and the RAII guard that owns it.
213///
214/// Reaching the target runs the injected work, then the displaced original
215/// instructions, then the rest of the original code. The work is machine code
216/// ([`code`](InjectionBuilder::code), [`asm`](InjectionBuilder::asm)) or a
217/// register-context callback ([`inspect`](InjectionBuilder::inspect),
218/// [`modify`](InjectionBuilder::modify)). Build one with [`Injection::at`].
219///
220/// # Threads, batches and dropping
221///
222/// These work as for [`Hook`](crate::Hook): patching suspends other threads,
223/// [`batch`](crate::batch) queues changes, and dropping reverts the patch and
224/// frees the stub and callback, or leaks them if the patch can't be reverted.
225/// A callback may run on several threads at once, each with its own
226/// [`Context`](crate::Context), so keep its own state in atomics or locks.
227pub struct Injection {
228 // Boxed so its address stays put while a `batch` has a change queued.
229 inner: Box<detour::RawDetour>,
230 // Freed manually, and only once the target no longer jumps to it.
231 stub: ManuallyDrop<region::Allocation>,
232 // A `*mut Box<ContextFn>` for a register-context injection, else null.
233 closure: *mut (),
234 target: usize,
235}
236
237// SAFETY: RawDetour and the callback are Send + Sync, the stub is only written
238// before it's shared, and all patching goes through the global patch lock.
239unsafe impl Send for Injection {}
240// SAFETY: `&Injection` only exposes locked operations and atomics.
241unsafe impl Sync for Injection {}
242
243impl Injection {
244 /// Starts building an injection at `target`.
245 ///
246 /// Nothing is patched until [`install`](InjectionWith::install).
247 pub fn at(target: *const ()) -> InjectionBuilder {
248 InjectionBuilder {
249 target: Target::Address(target),
250 }
251 }
252
253 /// Starts building an injection where a byte pattern matches.
254 ///
255 /// The pattern is searched for in the main module's code when the
256 /// injection is installed, and must match exactly once; see
257 /// [`scan`](crate::scan) for the syntax. Use
258 /// [`offset`](InjectionScan::offset) to land on an instruction inside the
259 /// match.
260 pub fn scan(pattern: &str) -> InjectionScan {
261 InjectionScan {
262 pattern: Pattern::new(pattern),
263 }
264 }
265
266 /// Enables the injection.
267 ///
268 /// Does nothing if already enabled. Suspends other threads while patching;
269 /// see [`Injection`] for threads and batches.
270 ///
271 /// # Errors
272 ///
273 /// Returns an error naming the target if patching fails, such as
274 /// [`Error::TargetModified`] when a third party changed its code.
275 pub fn enable(&self) -> Result<()> {
276 self.toggle(true)
277 }
278
279 /// Disables the injection, restoring the original code.
280 ///
281 /// Does nothing if already disabled. Suspends other threads while patching;
282 /// see [`Injection`] for threads and batches.
283 ///
284 /// # Errors
285 ///
286 /// As for [`enable`](Self::enable).
287 pub fn disable(&self) -> Result<()> {
288 self.toggle(false)
289 }
290
291 /// Enables the injection without suspending other threads.
292 ///
293 /// Faster than [`enable`](Self::enable), and applied at once even inside a
294 /// batch; see [`Hook`](crate::Hook#batches).
295 ///
296 /// # Errors
297 ///
298 /// As for [`enable`](Self::enable).
299 ///
300 /// # Safety
301 ///
302 /// No other thread may run the target's first bytes while they are
303 /// written, or it may execute a half-written instruction.
304 pub unsafe fn enable_unsuspended(&self) -> Result<()> {
305 crate::batch::forget(self.target);
306 // SAFETY: forwarded from the caller.
307 unsafe { self.toggle_now(true, detour::Threads::None) }
308 }
309
310 /// Disables the injection without suspending other threads.
311 ///
312 /// Applied at once like [`enable_unsuspended`](Self::enable_unsuspended).
313 ///
314 /// # Errors
315 ///
316 /// As for [`enable`](Self::enable).
317 ///
318 /// # Safety
319 ///
320 /// No other thread may run the target's first bytes while the original
321 /// ones are written back.
322 pub unsafe fn disable_unsuspended(&self) -> Result<()> {
323 crate::batch::forget(self.target);
324 // SAFETY: forwarded from the caller.
325 unsafe { self.toggle_now(false, detour::Threads::None) }
326 }
327
328 /// Returns whether the injection is enabled.
329 pub fn is_enabled(&self) -> bool {
330 self.inner.is_enabled()
331 }
332
333 /// Keeps the injection installed for the rest of the process.
334 ///
335 /// It stays in its current state, so enable it first if it should stay
336 /// active. The target address stays reserved.
337 pub fn leak(self) {
338 core::mem::forget(self);
339 }
340
341 /// Applies the change with every other thread suspended, or queues it
342 /// inside a batch.
343 fn toggle(&self, enable: bool) -> Result<()> {
344 if crate::batch::queue(&*self.inner, self.target, enable) {
345 return Ok(());
346 }
347 // SAFETY: `Threads::All` suspends every other thread and moves any that
348 // stopped inside the patch; the caller isn't running the patched bytes.
349 unsafe { self.toggle_now(enable, detour::Threads::All) }
350 }
351
352 /// Applies the change now, in its own transaction.
353 ///
354 /// # Safety
355 ///
356 /// Threads not suspended by `threads` must not run the patched bytes while
357 /// they are written.
358 unsafe fn toggle_now(&self, enable: bool, threads: detour::Threads<'_>) -> Result<()> {
359 let mut transaction = detour::Transaction::new();
360 if enable {
361 transaction.enable(&*self.inner);
362 } else {
363 transaction.disable(&*self.inner);
364 }
365 // SAFETY: forwarded from the caller.
366 unsafe { transaction.commit(threads) }
367 .map_err(|error| Error::from_detour(error, self.target))
368 }
369
370 /// Allocates the stub, creates the disabled detour, and writes the bytes
371 /// `render` builds from the stub and trampoline addresses.
372 ///
373 /// The caller claims the target address and releases it on failure.
374 ///
375 /// # Safety
376 ///
377 /// As for [`InjectionWith::install`]. `closure` must be null, or a
378 /// `*mut Box<ContextFn>` the injection frees on drop.
379 unsafe fn install_stub(
380 target: *const (),
381 capacity: usize,
382 closure: *mut (),
383 render: impl FnOnce(usize, usize) -> Vec<u8>,
384 ) -> Result<Injection> {
385 let address = target as usize;
386
387 let mut stub = region::alloc(capacity, Protection::READ_WRITE).map_err(|error| {
388 Error::Os(OsError::from_region(error, "allocating the injection stub"))
389 })?;
390 let stub_ptr = stub.as_mut_ptr::<u8>();
391 let stub_size = stub.len();
392
393 // Make it executable so the backend accepts it as the detour target.
394 // SAFETY: `stub_ptr`/`stub_size` describe the allocation just created.
395 unsafe { region::protect(stub_ptr, stub_size, Protection::READ_EXECUTE) }.map_err(
396 |error| Error::Os(OsError::from_region(error, "protecting the injection stub")),
397 )?;
398
399 // SAFETY: the caller vouches for `target`; the stub is executable and
400 // distinct from it.
401 let inner = unsafe { detour::RawDetour::new(target, stub_ptr.cast()) }
402 .map_err(|error| Error::from_detour(error, address))?;
403 let trampoline = inner.trampoline() as usize;
404
405 let bytes = render(stub_ptr as usize, trampoline);
406 assert!(
407 bytes.len() <= stub_size,
408 "imping-hook: injection stub exceeded its allocation"
409 );
410
411 // SAFETY: we own this allocation.
412 unsafe { region::protect(stub_ptr, stub_size, Protection::READ_WRITE) }.map_err(
413 |error| Error::Os(OsError::from_region(error, "protecting the injection stub")),
414 )?;
415 // SAFETY: the stub has `stub_size >= bytes.len()` writable bytes and
416 // doesn't overlap `bytes`.
417 unsafe { ptr::copy_nonoverlapping(bytes.as_ptr(), stub_ptr, bytes.len()) };
418 // SAFETY: we own this allocation.
419 unsafe { region::protect(stub_ptr, stub_size, Protection::READ_EXECUTE) }.map_err(
420 |error| Error::Os(OsError::from_region(error, "protecting the injection stub")),
421 )?;
422 // SAFETY: the stub spans `stub_size` bytes of memory we own.
423 unsafe { sys::flush_instruction_cache(stub_ptr, stub_size)? };
424
425 Ok(Injection {
426 inner: Box::new(inner),
427 stub: ManuallyDrop::new(stub),
428 closure,
429 target: address,
430 })
431 }
432
433 /// Builds a raw-bytes injection: the caller's code, then a jump to the
434 /// trampoline.
435 ///
436 /// # Safety
437 ///
438 /// As for [`InjectionWith::install`].
439 unsafe fn build_bytes(target: *const (), code: Vec<u8>) -> Result<Injection> {
440 let capacity = code.len() + ABS_JUMP_LEN;
441 // SAFETY: forwarded from the caller.
442 unsafe {
443 Self::install_stub(
444 target,
445 capacity,
446 ptr::null_mut(),
447 move |_stub, trampoline| {
448 let mut bytes = code;
449 bytes.extend_from_slice(&absolute_jump(trampoline));
450 bytes
451 },
452 )
453 }
454 }
455
456 /// Builds a register-context injection from a boxed callback.
457 ///
458 /// # Safety
459 ///
460 /// As for [`InjectionWith::install`].
461 #[cfg(target_arch = "x86_64")]
462 unsafe fn build_context(target: *const (), closure: Box<ContextFn>) -> Result<Injection> {
463 let hook_addr = target as usize as u64;
464 let rust_trampoline = context_trampoline
465 as unsafe extern "C" fn(*mut Context, *mut Box<ContextFn>)
466 as usize as u64;
467 // A thin, stable pointer for the stub to embed; freed on drop.
468 let closure_ptr: *mut Box<ContextFn> = Box::into_raw(Box::new(closure));
469
470 // SAFETY: forwarded from the caller.
471 let result = unsafe {
472 Self::install_stub(
473 target,
474 CONTEXT_STUB_CAPACITY,
475 closure_ptr.cast(),
476 |_stub, continue_at| {
477 assemble_context_stub(
478 hook_addr,
479 closure_ptr as u64,
480 rust_trampoline,
481 continue_at as u64,
482 )
483 .expect("imping-hook: internal error assembling the context stub")
484 },
485 )
486 };
487 if result.is_err() {
488 // SAFETY: `closure_ptr` came from `Box::into_raw` above, and no stub
489 // using it was installed.
490 drop(unsafe { Box::from_raw(closure_ptr) });
491 }
492 result
493 }
494
495 /// Builds an injection from assembly written with iced's `code_asm`.
496 ///
497 /// The block is assembled at the real stub address, so rip-relative
498 /// operands resolve, and followed by a jump to the trampoline.
499 ///
500 /// # Safety
501 ///
502 /// As for [`InjectionWith::install`].
503 #[cfg(target_arch = "x86_64")]
504 unsafe fn build_asm(target: *const (), build: BoxedAsm) -> Result<Injection> {
505 use iced_x86::code_asm::CodeAssembler;
506
507 let address = target as usize;
508 let mut assembler = CodeAssembler::new(64).map_err(|error| Error::Assembly {
509 address,
510 reason: error.to_string(),
511 })?;
512 if let Err(error) = build(&mut assembler) {
513 // The failing instruction isn't added, so the count is its index.
514 let index = assembler.instructions().len();
515 return Err(Error::Assembly {
516 address,
517 reason: format!("instruction {index}: {error}"),
518 });
519 }
520 // Encode at a dummy address just to size the stub; the address changes
521 // relative operands, not the length.
522 let capacity = assembler
523 .assemble(0)
524 .map_err(|error| Error::Assembly {
525 address,
526 reason: error.to_string(),
527 })?
528 .len()
529 + ABS_JUMP_LEN;
530
531 // SAFETY: forwarded from the caller.
532 unsafe {
533 Self::install_stub(
534 target,
535 capacity,
536 ptr::null_mut(),
537 move |stub, trampoline| {
538 // Can't fail once the sizing encode above succeeded.
539 let mut bytes = assembler
540 .assemble(stub as u64)
541 .expect("imping-hook: internal error reassembling injected asm");
542 bytes.extend_from_slice(&absolute_jump(trampoline));
543 bytes
544 },
545 )
546 }
547 }
548}
549
550impl Drop for Injection {
551 fn drop(&mut self) {
552 // Free the stub and callback only once the target is restored. If it
553 // can't be, the target may still jump there, so they leak (as does
554 // detour's own code) and the address stays reserved.
555 crate::batch::forget(self.target);
556 // SAFETY: as in `toggle`; the caller isn't running the patched bytes.
557 if unsafe { self.toggle_now(false, detour::Threads::All) }.is_ok() {
558 registry::release(self.target);
559 // SAFETY: the target no longer jumps to the stub, and neither the
560 // stub nor the callback is used again.
561 unsafe { ManuallyDrop::drop(&mut self.stub) };
562 #[cfg(target_arch = "x86_64")]
563 if !self.closure.is_null() {
564 // SAFETY: a non-null `closure` is this injection's boxed
565 // callback from `Box::into_raw`.
566 drop(unsafe { Box::from_raw(self.closure as *mut Box<ContextFn>) });
567 }
568 }
569 }
570}
571
572impl core::fmt::Debug for Injection {
573 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
574 f.debug_struct("Injection")
575 .field("target", &(self.target as *const ()))
576 .field("enabled", &self.is_enabled())
577 .finish()
578 }
579}
580
581/// What an [`InjectionWith`] injects: raw bytes, an assembly builder, or a
582/// register-context callback.
583enum Payload {
584 Bytes(Vec<u8>),
585 #[cfg(target_arch = "x86_64")]
586 Asm(BoxedAsm),
587 #[cfg(target_arch = "x86_64")]
588 Context(Box<ContextFn>),
589}
590
591/// An injection builder holding the target address, returned by
592/// [`Injection::at`].
593#[must_use = "an injection builder does nothing until the payload and `install` are set"]
594pub struct InjectionBuilder {
595 target: Target,
596}
597
598impl InjectionBuilder {
599 /// Injects the caller's own machine code at the target.
600 ///
601 /// The bytes are copied, so the slice needn't outlive this call. The code
602 /// must follow the ABI rules of [`install`](InjectionWith::install).
603 pub fn code(self, code: &[u8]) -> InjectionWith {
604 InjectionWith {
605 target: self.target,
606 payload: Payload::Bytes(code.to_vec()),
607 }
608 }
609
610 /// Injects machine code written as assembly.
611 ///
612 /// `build` appends to an [`asm::CodeAssembler`](crate::asm::CodeAssembler);
613 /// pass Rust values as operands, e.g. `a.mov(rax, some_addr)`. It's
614 /// assembled at the stub's real address, so rip-relative operands resolve,
615 /// and the jump back is appended. The ABI rules of
616 /// [`install`](InjectionWith::install) apply.
617 #[cfg(target_arch = "x86_64")]
618 pub fn asm<F>(self, build: F) -> InjectionWith
619 where
620 F: FnOnce(
621 &mut crate::asm::CodeAssembler,
622 ) -> core::result::Result<(), crate::asm::IcedError>
623 + 'static,
624 {
625 InjectionWith {
626 target: self.target,
627 payload: Payload::Asm(Box::new(build)),
628 }
629 }
630
631 /// Injects a callback that reads the registers at the target.
632 ///
633 /// `callback` gets the saved [`Context`](crate::Context), then the original
634 /// code resumes unchanged. It may run on several threads at once; see
635 /// [`Injection`].
636 #[cfg(target_arch = "x86_64")]
637 pub fn inspect<F>(self, callback: F) -> InjectionWith
638 where
639 F: Fn(&Context) + Send + Sync + 'static,
640 {
641 InjectionWith {
642 target: self.target,
643 payload: Payload::Context(Box::new(move |ctx: &mut Context| callback(ctx))),
644 }
645 }
646
647 /// Injects a callback that can change the registers at the target.
648 ///
649 /// Like [`inspect`](Self::inspect), but register writes take effect before
650 /// the original code resumes. Changing [`rip`](crate::Context::rip) jumps
651 /// there instead, skipping the displaced instructions.
652 #[cfg(target_arch = "x86_64")]
653 pub fn modify<F>(self, callback: F) -> InjectionWith
654 where
655 F: Fn(&mut Context) + Send + Sync + 'static,
656 {
657 InjectionWith {
658 target: self.target,
659 payload: Payload::Context(Box::new(callback)),
660 }
661 }
662}
663
664/// An injection builder holding a byte pattern to find the target by, returned
665/// by [`Injection::scan`].
666#[must_use = "an injection builder does nothing until the payload and `install` are set"]
667pub struct InjectionScan {
668 pattern: Pattern,
669}
670
671impl InjectionScan {
672 /// Searches a loaded module such as `"engine.dll"` instead of the main
673 /// module.
674 pub fn in_module(self, module: &str) -> Self {
675 InjectionScan {
676 pattern: self.pattern.in_module(module),
677 }
678 }
679
680 /// Moves the target this many bytes on from the start of the match, or back
681 /// for a negative count.
682 pub fn offset(self, bytes: isize) -> Self {
683 InjectionScan {
684 pattern: self.pattern.offset(bytes),
685 }
686 }
687
688 /// Follows the `call` or `jmp` at the target to the code it goes to; see
689 /// [`Pattern::follow_call`].
690 pub fn follow_call(self) -> Self {
691 InjectionScan {
692 pattern: self.pattern.follow_call(),
693 }
694 }
695
696 /// Injects the caller's own machine code; see [`InjectionBuilder::code`].
697 pub fn code(self, code: &[u8]) -> InjectionWith {
698 self.builder().code(code)
699 }
700
701 /// Injects machine code written as assembly; see [`InjectionBuilder::asm`].
702 #[cfg(target_arch = "x86_64")]
703 pub fn asm<F>(self, build: F) -> InjectionWith
704 where
705 F: FnOnce(
706 &mut crate::asm::CodeAssembler,
707 ) -> core::result::Result<(), crate::asm::IcedError>
708 + 'static,
709 {
710 self.builder().asm(build)
711 }
712
713 /// Injects a callback that reads the registers; see
714 /// [`InjectionBuilder::inspect`].
715 #[cfg(target_arch = "x86_64")]
716 pub fn inspect<F>(self, callback: F) -> InjectionWith
717 where
718 F: Fn(&Context) + Send + Sync + 'static,
719 {
720 self.builder().inspect(callback)
721 }
722
723 /// Injects a callback that can change the registers; see
724 /// [`InjectionBuilder::modify`].
725 #[cfg(target_arch = "x86_64")]
726 pub fn modify<F>(self, callback: F) -> InjectionWith
727 where
728 F: Fn(&mut Context) + Send + Sync + 'static,
729 {
730 self.builder().modify(callback)
731 }
732
733 fn builder(self) -> InjectionBuilder {
734 InjectionBuilder {
735 target: Target::Pattern(self.pattern),
736 }
737 }
738}
739
740/// An injection builder holding the target and what to inject, returned by the
741/// [`InjectionBuilder`] and [`InjectionScan`] methods.
742#[must_use = "an injection builder does nothing until `install` is called"]
743pub struct InjectionWith {
744 target: Target,
745 payload: Payload,
746}
747
748impl InjectionWith {
749 /// Installs the injection, disabled; enable it with [`Injection::enable`].
750 ///
751 /// # Errors
752 ///
753 /// - The [`scan`](crate::scan) errors, for an injection built with
754 /// [`Injection::scan`].
755 /// - [`Error::AlreadyHooked`] if a detour or injection already holds the
756 /// address.
757 /// - [`Error::Assembly`] if [`asm`](InjectionBuilder::asm) code can't be
758 /// encoded.
759 /// - Otherwise an error naming the target if it can't be patched.
760 ///
761 /// # Safety
762 ///
763 /// - The target (the address given to [`Injection::at`], or what
764 /// [`Injection::scan`] finds) must start an instruction in executable
765 /// code whose first bytes can be relocated, and which can be resumed
766 /// after the injected work.
767 /// - Code from [`code`](InjectionBuilder::code) or
768 /// [`asm`](InjectionBuilder::asm) must follow the platform ABI: preserve
769 /// every register and flag the surrounding code relies on, keep the stack
770 /// balanced and aligned (with shadow space before any `call` on Windows
771 /// x64), and not branch into the displaced bytes. Register-context
772 /// callbacks handle this themselves.
773 pub unsafe fn install(self) -> Result<Injection> {
774 let target = self.target.resolve()?;
775 let address = target as usize;
776
777 // Claim the address up front so a second hook on it is refused.
778 if !registry::claim(address) {
779 return Err(Error::AlreadyHooked { address });
780 }
781
782 let result = match self.payload {
783 // SAFETY: forwarded from the caller.
784 Payload::Bytes(code) => unsafe { Injection::build_bytes(target, code) },
785 #[cfg(target_arch = "x86_64")]
786 // SAFETY: forwarded from the caller.
787 Payload::Asm(build) => unsafe { Injection::build_asm(target, build) },
788 #[cfg(target_arch = "x86_64")]
789 // SAFETY: forwarded from the caller.
790 Payload::Context(callback) => unsafe { Injection::build_context(target, callback) },
791 };
792 if result.is_err() {
793 registry::release(address);
794 }
795 result
796 }
797}