Skip to main content

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}