ilhook/
x64.rs

1mod move_inst;
2#[cfg(target_arch = "x86_64")]
3mod tests;
4
5use std::io::{Cursor, Seek, SeekFrom, Write};
6use std::slice;
7
8#[cfg(windows)]
9use core::ffi::c_void;
10#[cfg(unix)]
11use libc::{__errno_location, c_void, mprotect, sysconf};
12#[cfg(windows)]
13use windows_sys::Win32::Foundation::GetLastError;
14#[cfg(windows)]
15use windows_sys::Win32::System::Memory::VirtualProtect;
16
17use bitflags::bitflags;
18use iced_x86::{Decoder, DecoderOptions, Instruction};
19
20use crate::HookError;
21use move_inst::move_code_to_addr;
22
23const MAX_INST_LEN: usize = 15;
24
25const TRAMPOLINE_MAX_LEN: usize = 1024;
26
27/// This is the routine used in a `jmp-back hook`, which means the RIP will jump back to the
28/// original position after the routine has finished running.
29///
30/// # Parameters
31///
32/// * `regs` - The registers.
33/// * `user_data` - User data that was previously passed to [`Hooker::new`].
34pub type JmpBackRoutine = unsafe extern "win64" fn(regs: *mut Registers, user_data: usize);
35
36/// This is the routine used in a `function hook`, which means the routine will replace the
37/// original function and the RIP will `retn` directly instead of jumping back.
38/// Note that the address being hooked must be the start of a function.
39///
40/// # Parameters
41///
42/// * `regs` - The registers.
43/// * `ori_func_ptr` - The original function pointer. Call this after converting it to the original function type.
44/// * `user_data` - User data that was previously passed to [`Hooker::new`].
45///
46/// # Return value
47///
48/// Returns the new return value of the replaced function.
49pub type RetnRoutine =
50    unsafe extern "win64" fn(regs: *mut Registers, ori_func_ptr: usize, user_data: usize) -> usize;
51
52/// This is the routine used in a `jmp-addr hook`, which means the RIP will jump to the specified
53/// address after the routine has finished running.
54///
55/// # Parameters
56///
57/// * `regs` - The registers.
58/// * `ori_func_ptr` - The original function pointer. Call this after converting it to the original function type.
59/// * `user_data` - User data that was previously passed to [`Hooker::new`].
60pub type JmpToAddrRoutine =
61    unsafe extern "win64" fn(regs: *mut Registers, ori_func_ptr: usize, user_data: usize);
62
63/// This is the routine used in a `jmp-ret hook`, which means the RIP will jump to the return
64/// value of the routine.
65///
66/// # Parameters
67///
68/// * `regs` - The registers.
69/// * `ori_func_ptr` - The original function pointer. Call this after converting it to the original function type.
70/// * `user_data` - User data that was previously passed to [`Hooker::new`].
71///
72/// # Return value
73///
74/// Returns the address you want to jump to.
75pub type JmpToRetRoutine =
76    unsafe extern "win64" fn(regs: *mut Registers, ori_func_ptr: usize, user_data: usize) -> usize;
77
78/// The hooking type.
79pub enum HookType {
80    /// Used in a jmp-back hook
81    JmpBack(JmpBackRoutine),
82
83    /// Used in a function hook
84    Retn(RetnRoutine),
85
86    /// Used in a jmp-addr hook. The first element is the destination address
87    JmpToAddr(usize, JmpToAddrRoutine),
88
89    /// Used in a jmp-ret hook.
90    JmpToRet(JmpToRetRoutine),
91}
92
93/// Jmp type that the `jmp` instruction use.
94pub enum JmpType {
95    /// Direct long jump. `jmp` instruction use 5 bytes, but may fail as memory allocation near the 2GB space may fail.
96    /// `jmp 0xXXXXXXXX`
97    Direct,
98
99    /// Mov rax and jump. Use 11 bytes.
100    /// `mov rax, 0xXXXXXXXXXXXXXXXX; jmp rax;`
101    MovJmp,
102
103    /// Use 2 jmp instructions to jump. You have to specify the position of the second jmp.
104    /// `jmp 0xXXXXXXXX; some codes; mov rax, 0xXXXXXXXX; jmp rax;`
105    TrampolineJmp(usize),
106}
107
108/// The common registers.
109#[repr(C)]
110#[derive(Debug, Clone)]
111pub struct Registers {
112    /// The xmm0 register
113    pub xmm0: u128,
114    /// The xmm1 register
115    pub xmm1: u128,
116    /// The xmm2 register
117    pub xmm2: u128,
118    /// The xmm3 register
119    pub xmm3: u128,
120    /// The r15 register
121    pub r15: u64,
122    /// The r14 register
123    pub r14: u64,
124    /// The r13 register
125    pub r13: u64,
126    /// The r12 register
127    pub r12: u64,
128    /// The r11 register
129    pub r11: u64,
130    /// The r10 register
131    pub r10: u64,
132    /// The r9 register
133    pub r9: u64,
134    /// The r8 register
135    pub r8: u64,
136    /// The rbp register
137    pub rbp: u64,
138    /// The rdi register
139    pub rdi: u64,
140    /// The rsi register
141    pub rsi: u64,
142    /// The rdx register
143    pub rdx: u64,
144    /// The rcx register
145    pub rcx: u64,
146    /// The rbx register
147    pub rbx: u64,
148    /// The rsp register
149    pub rsp: u64,
150    /// The flags register
151    pub rflags: u64,
152    /// Unused var
153    pub _no_use: u64,
154    /// The rax register
155    pub rax: u64,
156}
157
158impl Registers {
159    /// Get the value by index.
160    ///
161    /// # Parameters
162    ///
163    /// * cnt - The index of the arguments.
164    ///
165    /// # Safety
166    ///
167    /// Process may crash if register `rsp` does not point to a valid stack.
168    #[must_use]
169    pub unsafe fn get_stack(&self, cnt: usize) -> u64 {
170        unsafe { *((self.rsp as usize + cnt * 8) as *mut u64) }
171    }
172}
173
174/// The trait which is called before and after the modifying of the `jmp` instruction.
175/// Usually is used to suspend and resume all other threads, to avoid instruction colliding.
176pub trait ThreadCallback {
177    /// the callback before modifying `jmp` instruction, should return true if success.
178    fn pre(&self) -> bool;
179    /// the callback after modifying `jmp` instruction
180    fn post(&self);
181}
182
183/// Option for thread callback
184pub enum CallbackOption {
185    /// Valid callback
186    Some(Box<dyn ThreadCallback>),
187    /// No callback
188    None,
189}
190
191bitflags! {
192    /// Hook flags
193    pub struct HookFlags:u32 {
194        /// If set, will not modify the memory protection of the destination address, so that
195        /// the `hook` function could be ALMOST thread-safe.
196        const NOT_MODIFY_MEMORY_PROTECT = 0x1;
197    }
198}
199
200/// The entry struct in ilhook.
201/// Please read the main doc to view usage.
202pub struct Hooker {
203    addr: usize,
204    hook_type: HookType,
205    thread_cb: CallbackOption,
206    user_data: usize,
207    jmp_inst_size: usize,
208    flags: HookFlags,
209}
210
211/// The hook result returned by `Hooker::hook`.
212pub struct HookPoint {
213    addr: usize,
214    #[allow(dead_code)] // we only use the drop trait of the trampoline
215    trampoline: Box<[u8; TRAMPOLINE_MAX_LEN]>,
216    trampoline_prot: u32,
217    origin: Vec<u8>,
218    thread_cb: CallbackOption,
219    jmp_inst_size: usize,
220    flags: HookFlags,
221}
222
223/// The hook result returned by [hook_closure_jmp_back], [hook_closure_retn],
224/// [hook_closure_jmp_to_addr], and [hook_closure_jmp_to_ret]. This ensures
225/// that the closure's lifetime lasts as long as the hook.
226pub struct ClosureHookPoint<'a> {
227    _inner: HookPoint,
228    _callback: Box<dyn HookCallback + 'a>,
229}
230
231/// A dyn-compatible universal trait we use to be able to store all the
232/// different types of closures in [ClosureHookPoint] without using generics.
233trait HookCallback {}
234impl<T> HookCallback for T {}
235
236#[cfg(not(target_arch = "x86_64"))]
237fn env_lock() {
238    panic!("This crate should only be used in arch x86_32!")
239}
240#[cfg(target_arch = "x86_64")]
241fn env_lock() {}
242
243impl Hooker {
244    /// Create a new Hooker.
245    ///
246    /// # Parameters
247    ///
248    /// * `addr` - The being-hooked address.
249    /// * `hook_type` - The hook type and callback routine.
250    /// * `thread_cb` - The callbacks before and after hooking.
251    /// * `flags` - Hook flags
252    #[must_use]
253    pub fn new(
254        addr: usize,
255        hook_type: HookType,
256        thread_cb: CallbackOption,
257        user_data: usize,
258        flags: HookFlags,
259    ) -> Self {
260        env_lock();
261        Self {
262            addr,
263            hook_type,
264            thread_cb,
265            user_data,
266            jmp_inst_size: 14,
267            flags,
268        }
269    }
270
271    /// Consumes self and execute hooking. Return the `HookPoint`.
272    ///
273    /// # Safety
274    ///
275    /// Process may crash (instead of panic!) if:
276    ///
277    /// 1. addr is not an accessible memory address, or is not long enough.
278    /// 2. addr points to an incorrect position. (At the middle of an instruction, or where after it other instructions may jump to)
279    /// 3. Set `NOT_MODIFY_MEMORY_PROTECT` where it should not be set.
280    /// 4. hook or unhook from 2 or more threads at the same time without `HookFlags::NOT_MODIFY_MEMORY_PROTECT`. Because of memory protection colliding.
281    /// 5. Other unpredictable errors.
282    pub unsafe fn hook(self) -> Result<HookPoint, HookError> {
283        let (moving_insts, origin) = get_moving_insts(self.addr, self.jmp_inst_size)?;
284        let trampoline =
285            generate_trampoline(&self, moving_insts, origin.len() as u8, self.user_data)?;
286        let trampoline_prot = modify_mem_protect(trampoline.as_ptr() as usize, trampoline.len())?;
287        if !self.flags.contains(HookFlags::NOT_MODIFY_MEMORY_PROTECT) {
288            let old_prot = modify_mem_protect(self.addr, self.jmp_inst_size)?;
289            let ret = modify_jmp_with_thread_cb(&self, trampoline.as_ptr() as usize);
290            recover_mem_protect(self.addr, self.jmp_inst_size, old_prot);
291            ret?;
292        } else {
293            modify_jmp_with_thread_cb(&self, trampoline.as_ptr() as usize)?;
294        }
295        Ok(HookPoint {
296            addr: self.addr,
297            trampoline,
298            trampoline_prot,
299            origin,
300            thread_cb: self.thread_cb,
301            jmp_inst_size: self.jmp_inst_size,
302            flags: self.flags,
303        })
304    }
305}
306
307
308/// A high-level helper for hooking a function using the [JmpBackRoutine]
309/// strategy. The [callback] is called before any calls to the function at
310/// [address], and then the original function is called as normal
311/// afterwards.
312///
313/// The callback is passed [Registers] that provide access to the original
314/// function's arguments.
315///
316/// ## Safety
317///
318/// See [`Hooker::hook`] for details on when this is safe to call.
319pub unsafe fn hook_closure_jmp_back<'a, T: Fn(*mut Registers) + Send + Sync + 'a>(
320    address: usize,
321    callback: T,
322    callback_option: CallbackOption,
323    hook_flags: HookFlags,
324) -> Result<ClosureHookPoint<'a>, HookError> {
325    let callback = Box::new(callback);
326    let hooker = Hooker::new(
327        address,
328        HookType::JmpBack(run_jmp_back_closure::<T>),
329        callback_option,
330        &*callback as *const T as usize,
331        hook_flags,
332    );
333    Ok(ClosureHookPoint {
334        _inner: unsafe { hooker.hook()? },
335        _callback: callback,
336    })
337}
338
339/// A high-level helper for hooking a function using the [RetnRoutine]
340/// strategy. All calls to the function at [address] are routed to
341/// [callback] instead, and its return value is used in place of the
342/// original function's return value.
343///
344/// The callback is passed [Registers] that provide access to the original
345/// function's arguments, as well as a usize that can be cast to the
346/// original function's signature using [std::mem::transmute].
347///
348/// ## Safety
349///
350/// See [`Hooker::hook`] for details on when this is safe to call.
351pub unsafe fn hook_closure_retn<
352    'a,
353    T: (Fn(*mut Registers, usize) -> usize) + Send + Sync + 'a,
354>(
355    address: usize,
356    callback: T,
357    callback_option: CallbackOption,
358    hook_flags: HookFlags,
359) -> Result<ClosureHookPoint<'a>, HookError> {
360    let callback = Box::new(callback);
361    let hooker = Hooker::new(
362        address,
363        HookType::Retn(run_retn_closure::<T>),
364        callback_option,
365        &*callback as *const T as usize,
366        hook_flags,
367    );
368    Ok(ClosureHookPoint {
369        _inner: unsafe { hooker.hook()? },
370        _callback: callback,
371    })
372}
373
374/// A high-level helper for hooking a function using the [JmpToAddrRoutine]
375/// strategy. All calls to the function at [address] are routed to
376/// [callback] instead, then the function at [follow_up] is called and its
377/// return value is used in place of the original function's.
378///
379/// The callback is passed [Registers] that provide access to the original
380/// function's arguments, as well as a usize that can be cast to the
381/// original function's signature using [std::mem::transmute].
382///
383/// ## Safety
384///
385/// See [`Hooker::hook`] for details on when this is safe to call.
386pub unsafe fn hook_closure_jmp_to_addr<'a, T: Fn(*mut Registers, usize) + Send + Sync + 'a>(
387    address: usize,
388    follow_up: usize,
389    callback: T,
390    callback_option: CallbackOption,
391    hook_flags: HookFlags,
392) -> Result<ClosureHookPoint<'a>, HookError> {
393    let callback = Box::new(callback);
394    let hooker = Hooker::new(
395        address,
396        HookType::JmpToAddr(follow_up, run_jmp_to_addr_closure::<T>),
397        callback_option,
398        &*callback as *const T as usize,
399        hook_flags,
400    );
401    Ok(ClosureHookPoint {
402        _inner: unsafe { hooker.hook()? },
403        _callback: callback,
404    })
405}
406
407/// A high-level helper for hooking a function using the [JmpToRetRoutine]
408/// strategy. All calls to the function at [address] are routed to
409/// [callback] instead, then the function at the addressed returned by
410/// [callback] is called and its return value is used in place of the
411/// original function's
412///
413/// The callback is passed [Registers] that provide access to the original
414/// function's arguments, as well as a usize that can be cast to the
415/// original function's signature using [std::mem::transmute].
416///
417/// ## Safety
418///
419/// See [`Hooker::hook`] for details on when this is safe to call.
420pub unsafe fn hook_closure_jmp_to_ret<
421    'a,
422    T: (Fn(*mut Registers, usize) -> usize) + Send + Sync + 'a,
423>(
424    address: usize,
425    callback: T,
426    callback_option: CallbackOption,
427    hook_flags: HookFlags,
428) -> Result<ClosureHookPoint<'a>, HookError> {
429    let callback = Box::new(callback);
430    let hooker = Hooker::new(
431        address,
432        HookType::JmpToRet(run_retn_closure::<T>),
433        callback_option,
434        &*callback as *const T as usize,
435        hook_flags,
436    );
437    Ok(ClosureHookPoint {
438        _inner: unsafe { hooker.hook()? },
439        _callback: callback,
440    })
441}
442
443/// The userdata trampoline for [Hooker::hook_closure_jmp_back].
444unsafe extern "win64" fn run_jmp_back_closure<T: Fn(*mut Registers)>(
445    reg: *mut Registers,
446    callback: usize,
447) {
448    unsafe { (*(callback as *const T))(reg) };
449}
450
451/// The userdata trampoline for [Hooker::hook_closure_retn] *and*
452/// [Hooker::hook_closure_jmp_to_ret].
453unsafe extern "win64" fn run_retn_closure<T: Fn(*mut Registers, usize) -> usize>(
454    reg: *mut Registers,
455    original: usize,
456    callback: usize,
457) -> usize {
458    unsafe { (*(callback as *const T))(reg, original) }
459}
460
461/// The userdata trampoline for [Hooker::hook_closure_jmp_to_addr].
462unsafe extern "win64" fn run_jmp_to_addr_closure<T: Fn(*mut Registers, usize)>(
463    reg: *mut Registers,
464    original: usize,
465    callback: usize,
466) {
467    unsafe { (*(callback as *const T))(reg, original) };
468}
469
470impl HookPoint {
471    /// Consume self and unhook the address.
472    pub unsafe fn unhook(self) -> Result<(), HookError> {
473        self.unhook_by_ref()
474    }
475
476    fn unhook_by_ref(&self) -> Result<(), HookError> {
477        let ret: Result<(), HookError>;
478        if !self.flags.contains(HookFlags::NOT_MODIFY_MEMORY_PROTECT) {
479            let old_prot = modify_mem_protect(self.addr, self.jmp_inst_size)?;
480            ret = recover_jmp_with_thread_cb(self);
481            recover_mem_protect(self.addr, self.jmp_inst_size, old_prot);
482        } else {
483            ret = recover_jmp_with_thread_cb(self)
484        }
485        recover_mem_protect(
486            self.trampoline.as_ptr() as usize,
487            self.trampoline.len(),
488            self.trampoline_prot,
489        );
490        ret
491    }
492}
493
494// When the HookPoint drops, it should unhook automatically.
495impl Drop for HookPoint {
496    fn drop(&mut self) {
497        self.unhook_by_ref().unwrap_or_default();
498    }
499}
500
501fn get_moving_insts(
502    addr: usize,
503    min_bytes: usize,
504) -> Result<(Vec<Instruction>, Vec<u8>), HookError> {
505    let code_slice = unsafe { slice::from_raw_parts(addr as *const u8, MAX_INST_LEN * 2) };
506    let mut decoder = Decoder::new(64, code_slice, DecoderOptions::NONE);
507    decoder.set_ip(addr as u64);
508
509    let mut total_bytes = 0;
510    let mut ori_insts: Vec<Instruction> = vec![];
511    for inst in &mut decoder {
512        if inst.is_invalid() {
513            return Err(HookError::Disassemble);
514        }
515        ori_insts.push(inst);
516        total_bytes += inst.len();
517        if total_bytes >= min_bytes {
518            break;
519        }
520    }
521
522    Ok((ori_insts, code_slice[0..decoder.position()].into()))
523}
524
525fn write_trampoline_prolog(buf: &mut impl Write) -> Result<usize, std::io::Error> {
526    // push rsp
527    // pushfq
528    // test rsp,8
529    // je _stack_aligned_16
530    // ; stack not aligned to 16
531    // push rax
532    // sub rsp,0x10
533    // mov rax, [rsp+0x20] # rsp
534    // mov [rsp], rax
535    // mov rax, [rsp+0x18] # rflags
536    // mov [rsp+8], rax
537    // mov rax, [rsp+0x10] # rax
538    // mov [rsp+0x18], rax
539    // mov dword ptr [rsp+0x10],1 # stack flag
540    // jmp _other_registers
541    // _stack_aligned_16:
542    // push rax
543    // push rax
544    // mov rax, [rsp+0x18] # rsp
545    // mov [rsp], rax
546    // mov rax, [rsp+8] # rax
547    // mov [rsp+0x18], rax
548    // mov rax,[rsp+0x10] # rflags
549    // mov [rsp+8], rax
550    // mov dword ptr [rsp+0x10], 0 # stack flag
551    // _other_registers:
552    // push rbx
553    // push rcx
554    // push rdx
555    // push rsi
556    // push rdi
557    // push rbp
558    // push r8
559    // push r9
560    // push r10
561    // push r11
562    // push r12
563    // push r13
564    // push r14
565    // push r15
566    // sub rsp,0x40
567    // movaps xmmword ptr ss:[rsp],xmm0
568    // movaps xmmword ptr ss:[rsp+0x10],xmm1
569    // movaps xmmword ptr ss:[rsp+0x20],xmm2
570    // movaps xmmword ptr ss:[rsp+0x30],xmm3
571    buf.write(&[
572        0x54, 0x9C, 0x48, 0xF7, 0xC4, 0x08, 0x00, 0x00, 0x00, 0x74, 0x2C, 0x50, 0x48, 0x83, 0xEC,
573        0x10, 0x48, 0x8B, 0x44, 0x24, 0x20, 0x48, 0x89, 0x04, 0x24, 0x48, 0x8B, 0x44, 0x24, 0x18,
574        0x48, 0x89, 0x44, 0x24, 0x08, 0x48, 0x8B, 0x44, 0x24, 0x10, 0x48, 0x89, 0x44, 0x24, 0x18,
575        0xC7, 0x44, 0x24, 0x10, 0x01, 0x00, 0x00, 0x00, 0xEB, 0x27, 0x50, 0x50, 0x48, 0x8B, 0x44,
576        0x24, 0x18, 0x48, 0x89, 0x04, 0x24, 0x48, 0x8B, 0x44, 0x24, 0x08, 0x48, 0x89, 0x44, 0x24,
577        0x18, 0x48, 0x8B, 0x44, 0x24, 0x10, 0x48, 0x89, 0x44, 0x24, 0x08, 0xC7, 0x44, 0x24, 0x10,
578        0x00, 0x00, 0x00, 0x00, 0x53, 0x51, 0x52, 0x56, 0x57, 0x55, 0x41, 0x50, 0x41, 0x51, 0x41,
579        0x52, 0x41, 0x53, 0x41, 0x54, 0x41, 0x55, 0x41, 0x56, 0x41, 0x57, 0x48, 0x83, 0xEC, 0x40,
580        0x0F, 0x29, 0x04, 0x24, 0x0F, 0x29, 0x4C, 0x24, 0x10, 0x0F, 0x29, 0x54, 0x24, 0x20, 0x0F,
581        0x29, 0x5C, 0x24, 0x30,
582    ])
583}
584
585fn write_trampoline_epilog1(buf: &mut impl Write) -> Result<usize, std::io::Error> {
586    // movaps xmm0,xmmword ptr ss:[rsp]
587    // movaps xmm1,xmmword ptr ss:[rsp+0x10]
588    // movaps xmm2,xmmword ptr ss:[rsp+0x20]
589    // movaps xmm3,xmmword ptr ss:[rsp+0x30]
590    // add rsp,0x40
591    // pop r15
592    // pop r14
593    // pop r13
594    // pop r12
595    // pop r11
596    // pop r10
597    // pop r9
598    // pop r8
599    // pop rbp
600    // pop rdi
601    // pop rsi
602    // pop rdx
603    // pop rcx
604    // pop rbx
605    // add rsp,8
606    buf.write(&[
607        0x0F, 0x28, 0x04, 0x24, 0x0F, 0x28, 0x4C, 0x24, 0x10, 0x0F, 0x28, 0x54, 0x24, 0x20, 0x0F,
608        0x28, 0x5C, 0x24, 0x30, 0x48, 0x83, 0xC4, 0x40, 0x41, 0x5F, 0x41, 0x5E, 0x41, 0x5D, 0x41,
609        0x5C, 0x41, 0x5B, 0x41, 0x5A, 0x41, 0x59, 0x41, 0x58, 0x5D, 0x5F, 0x5E, 0x5A, 0x59, 0x5B,
610        0x48, 0x83, 0xC4, 0x08,
611    ])
612}
613
614fn write_trampoline_epilog2_common(buf: &mut impl Write) -> Result<usize, std::io::Error> {
615    // test dword ptr ss:[rsp+0x8],1
616    // je _branch1
617    // mov rax, [rsp+0x10]
618    // mov [rsp+0x18], rax
619    // popfq
620    // pop rax
621    // pop rax
622    // pop rax
623    // jmp _branch2
624    // _branch1:
625    // popfq
626    // pop rax
627    // pop rax
628    // _branch2:
629    buf.write(&[
630        0xF7, 0x44, 0x24, 0x08, 0x01, 0x00, 0x00, 0x00, 0x74, 0x10, 0x48, 0x8B, 0x44, 0x24, 0x10,
631        0x48, 0x89, 0x44, 0x24, 0x18, 0x9D, 0x58, 0x58, 0x58, 0xEB, 0x03, 0x9D, 0x58, 0x58,
632    ])
633}
634
635fn write_trampoline_epilog2_jmp_ret(buf: &mut impl Write) -> Result<usize, std::io::Error> {
636    // test dword ptr ss:[rsp+8],1
637    // je _branch1
638    // popfq
639    // mov [rsp], rax
640    // pop rax
641    // pop rax
642    // lea rsp, [rsp+8] # Not use 'add rsp, 8' as it will modify the rflags
643    // jmp _branch2
644    // _branch1:
645    // popfq
646    // mov [rsp-8],rax
647    // pop rax
648    // pop rax
649    // _branch2:
650    // jmp qword ptr ss:[rsp-0x18]
651    buf.write(&[
652        0xF7, 0x44, 0x24, 0x08, 0x01, 0x00, 0x00, 0x00, 0x74, 0x0E, 0x9D, 0x48, 0x89, 0x04, 0x24,
653        0x58, 0x58, 0x48, 0x8D, 0x64, 0x24, 0x08, 0xEB, 0x08, 0x9D, 0x48, 0x89, 0x44, 0x24, 0xF8,
654        0x58, 0x58, 0xFF, 0x64, 0x24, 0xE8
655    ])
656}
657
658fn jmp_addr<T: Write>(addr: u64, buf: &mut T) -> Result<(), HookError> {
659    buf.write(&[0xff, 0x25, 0, 0, 0, 0])?;
660    buf.write(&addr.to_le_bytes())?;
661    Ok(())
662}
663
664fn write_ori_func_addr<T: Write + Seek>(buf: &mut T, ori_func_addr_off: u64, ori_func_off: u64) {
665    let pos = buf.stream_position().unwrap();
666    buf.seek(SeekFrom::Start(ori_func_addr_off)).unwrap();
667    buf.write(&ori_func_off.to_le_bytes()).unwrap();
668    buf.seek(SeekFrom::Start(pos)).unwrap();
669}
670
671fn generate_jmp_back_trampoline<T: Write + Seek>(
672    buf: &mut T,
673    trampoline_base_addr: u64,
674    moving_code: &Vec<Instruction>,
675    ori_addr: usize,
676    cb: JmpBackRoutine,
677    ori_len: u8,
678    user_data: usize,
679) -> Result<(), HookError> {
680    // mov rdx, user_data
681    buf.write(&[0x48, 0xba])?;
682    buf.write(&(user_data as u64).to_le_bytes())?;
683    // mov rcx, rsp
684    // sub rsp, 0x10
685    // mov rax, cb
686    buf.write(&[0x48, 0x89, 0xe1, 0x48, 0x83, 0xec, 0x10, 0x48, 0xb8])?;
687    buf.write(&(cb as usize as u64).to_le_bytes())?;
688    // call rax
689    // add rsp, 0x10
690    buf.write(&[0xff, 0xd0, 0x48, 0x83, 0xc4, 0x10])?;
691    write_trampoline_epilog1(buf)?;
692    write_trampoline_epilog2_common(buf)?;
693
694    let cur_pos = buf.stream_position().unwrap();
695    buf.write(&move_code_to_addr(
696        moving_code,
697        trampoline_base_addr + cur_pos,
698    )?)?;
699
700    jmp_addr(ori_addr as u64 + u64::from(ori_len), buf)?;
701    Ok(())
702}
703
704fn generate_retn_trampoline<T: Write + Seek>(
705    buf: &mut T,
706    trampoline_base_addr: u64,
707    moving_code: &Vec<Instruction>,
708    ori_addr: usize,
709    cb: RetnRoutine,
710    ori_len: u8,
711    user_data: usize,
712) -> Result<(), HookError> {
713    // mov r8, user_data
714    buf.write(&[0x49, 0xb8])?;
715    buf.write(&(user_data as u64).to_le_bytes())?;
716    let ori_func_addr_off = buf.stream_position().unwrap() + 2;
717    // mov rdx, ori_func
718    // mov rcx, rsp
719    // sub rsp,0x20
720    // mov rax, cb
721    buf.write(&[
722        0x48, 0xba, 0, 0, 0, 0, 0, 0, 0, 0, 0x48, 0x89, 0xe1, 0x48, 0x83, 0xec, 0x20, 0x48, 0xb8,
723    ])?;
724    buf.write(&(cb as usize as u64).to_le_bytes())?;
725    // call rax
726    // add rsp, 0x20
727    // mov [rsp + 0xc8], rax
728    buf.write(&[
729        0xff, 0xd0, 0x48, 0x83, 0xc4, 0x20, 0x48, 0x89, 0x84, 0x24, 0xc8, 0x00, 0x00, 0x00,
730    ])?;
731    write_trampoline_epilog1(buf)?;
732    write_trampoline_epilog2_common(buf)?;
733    // ret
734    buf.write(&[0xc3])?;
735
736    let ori_func_off = buf.stream_position().unwrap();
737    buf.write(&move_code_to_addr(
738        moving_code,
739        trampoline_base_addr + ori_func_off,
740    )?)?;
741    jmp_addr(ori_addr as u64 + u64::from(ori_len), buf)?;
742
743    write_ori_func_addr(buf, ori_func_addr_off, trampoline_base_addr + ori_func_off);
744
745    Ok(())
746}
747
748fn generate_jmp_addr_trampoline<T: Write + Seek>(
749    buf: &mut T,
750    trampoline_base_addr: u64,
751    moving_code: &Vec<Instruction>,
752    ori_addr: usize,
753    dest_addr: usize,
754    cb: JmpToAddrRoutine,
755    ori_len: u8,
756    user_data: usize,
757) -> Result<(), HookError> {
758    // mov r8, user_data
759    buf.write(&[0x49, 0xb8])?;
760    buf.write(&(user_data as u64).to_le_bytes())?;
761    let ori_func_addr_off = buf.stream_position().unwrap() + 2;
762    // mov rdx, ori_func
763    // mov rcx, rsp
764    // sub rsp,0x20
765    // mov rax, cb
766    buf.write(&[
767        0x48, 0xba, 0, 0, 0, 0, 0, 0, 0, 0, 0x48, 0x89, 0xe1, 0x48, 0x83, 0xec, 0x20, 0x48, 0xb8,
768    ])?;
769    buf.write(&(cb as usize as u64).to_le_bytes())?;
770    // call rax
771    // add rsp, 0x20
772    buf.write(&[0xff, 0xd0, 0x48, 0x83, 0xc4, 0x20])?;
773    write_trampoline_epilog1(buf)?;
774    write_trampoline_epilog2_common(buf)?;
775    jmp_addr(dest_addr as u64, buf)?;
776
777    let ori_func_off = buf.stream_position().unwrap();
778    buf.write(&move_code_to_addr(
779        moving_code,
780        trampoline_base_addr + ori_func_off,
781    )?)?;
782    jmp_addr(ori_addr as u64 + u64::from(ori_len), buf)?;
783
784    write_ori_func_addr(buf, ori_func_addr_off, trampoline_base_addr + ori_func_off);
785
786    Ok(())
787}
788
789fn generate_jmp_ret_trampoline<T: Write + Seek>(
790    buf: &mut T,
791    trampoline_base_addr: u64,
792    moving_code: &Vec<Instruction>,
793    ori_addr: usize,
794    cb: JmpToRetRoutine,
795    ori_len: u8,
796    user_data: usize,
797) -> Result<(), HookError> {
798    // mov r8, user_data
799    buf.write(&[0x49, 0xb8])?;
800    buf.write(&(user_data as u64).to_le_bytes())?;
801    let ori_func_addr_off = buf.stream_position().unwrap() + 2;
802    // mov rdx, ori_func
803    // mov rcx, rsp
804    // sub rsp,0x20
805    // mov rax, cb
806    buf.write(&[
807        0x48, 0xba, 0, 0, 0, 0, 0, 0, 0, 0, 0x48, 0x89, 0xe1, 0x48, 0x83, 0xec, 0x20, 0x48, 0xb8,
808    ])?;
809    buf.write(&(cb as usize as u64).to_le_bytes())?;
810    // call rax
811    // add rsp, 0x20
812    buf.write(&[0xff, 0xd0, 0x48, 0x83, 0xc4, 0x20])?;
813    write_trampoline_epilog1(buf)?;
814    write_trampoline_epilog2_jmp_ret(buf)?;
815
816    let ori_func_off = buf.stream_position().unwrap();
817    buf.write(&move_code_to_addr(
818        moving_code,
819        trampoline_base_addr + ori_func_off,
820    )?)?;
821    jmp_addr(ori_addr as u64 + u64::from(ori_len), buf)?;
822
823    write_ori_func_addr(buf, ori_func_addr_off, trampoline_base_addr + ori_func_off);
824
825    Ok(())
826}
827
828fn generate_trampoline(
829    hooker: &Hooker,
830    moving_code: Vec<Instruction>,
831    ori_len: u8,
832    user_data: usize,
833) -> Result<Box<[u8; TRAMPOLINE_MAX_LEN]>, HookError> {
834    let mut trampoline_buffer = Box::new([0u8; TRAMPOLINE_MAX_LEN]);
835    let trampoline_addr = trampoline_buffer.as_ptr() as u64;
836    let mut buf = Cursor::new(&mut trampoline_buffer[..]);
837
838    write_trampoline_prolog(&mut buf)?;
839
840    match hooker.hook_type {
841        HookType::JmpBack(cb) => generate_jmp_back_trampoline(
842            &mut buf,
843            trampoline_addr,
844            &moving_code,
845            hooker.addr,
846            cb,
847            ori_len,
848            user_data,
849        ),
850        HookType::Retn(cb) => generate_retn_trampoline(
851            &mut buf,
852            trampoline_addr,
853            &moving_code,
854            hooker.addr,
855            cb,
856            ori_len,
857            user_data,
858        ),
859        HookType::JmpToAddr(dest_addr, cb) => generate_jmp_addr_trampoline(
860            &mut buf,
861            trampoline_addr,
862            &moving_code,
863            hooker.addr,
864            dest_addr,
865            cb,
866            ori_len,
867            user_data,
868        ),
869        HookType::JmpToRet(cb) => generate_jmp_ret_trampoline(
870            &mut buf,
871            trampoline_addr,
872            &moving_code,
873            hooker.addr,
874            cb,
875            ori_len,
876            user_data,
877        ),
878    }?;
879
880    Ok(trampoline_buffer)
881}
882
883#[cfg(windows)]
884fn modify_mem_protect(addr: usize, len: usize) -> Result<u32, HookError> {
885    let mut old_prot: u32 = 0;
886    let old_prot_ptr = std::ptr::addr_of_mut!(old_prot);
887    // PAGE_EXECUTE_READWRITE = 0x40
888    let ret = unsafe { VirtualProtect(addr as *const c_void, len, 0x40, old_prot_ptr) };
889    if ret == 0 {
890        Err(HookError::MemoryProtect(unsafe { GetLastError() }))
891    } else {
892        Ok(old_prot)
893    }
894}
895
896#[cfg(unix)]
897fn modify_mem_protect(addr: usize, len: usize) -> Result<u32, HookError> {
898    let page_size = unsafe { sysconf(30) }; //_SC_PAGESIZE == 30
899    if len > page_size.try_into().unwrap() {
900        Err(HookError::InvalidParameter)
901    } else {
902        //(PROT_READ | PROT_WRITE | PROT_EXEC) == 7
903        let ret = unsafe {
904            mprotect(
905                (addr & !(page_size as usize - 1)) as *mut c_void,
906                page_size as usize,
907                7,
908            )
909        };
910        if ret != 0 {
911            let err = unsafe { *(__errno_location()) };
912            Err(HookError::MemoryProtect(err as u32))
913        } else {
914            // it's too complex to get the original memory protection
915            Ok(7)
916        }
917    }
918}
919#[cfg(windows)]
920fn recover_mem_protect(addr: usize, len: usize, old: u32) {
921    let mut old_prot: u32 = 0;
922    let old_prot_ptr = std::ptr::addr_of_mut!(old_prot);
923    unsafe { VirtualProtect(addr as *const c_void, len, old, old_prot_ptr) };
924}
925
926#[cfg(unix)]
927fn recover_mem_protect(addr: usize, _: usize, old: u32) {
928    let page_size = unsafe { sysconf(30) }; //_SC_PAGESIZE == 30
929    unsafe {
930        mprotect(
931            (addr & !(page_size as usize - 1)) as *mut c_void,
932            page_size as usize,
933            old as i32,
934        )
935    };
936}
937fn modify_jmp(dest_addr: usize, trampoline_addr: usize) {
938    let buf = unsafe { slice::from_raw_parts_mut(dest_addr as *mut u8, 14) };
939    let distance = trampoline_addr as i64 - (dest_addr as i64 + 5);
940    if distance.abs() <= 0x7fff_ffff {
941        // jmp xxx
942        buf[0] = 0xe9;
943        buf[1..5].copy_from_slice(&(distance as i32).to_le_bytes());
944    } else {
945        // jmp qword ptr [rip+0]
946        buf[0..6].copy_from_slice(&[0xff, 0x25, 0, 0, 0, 0]);
947        buf[6..14].copy_from_slice(&(trampoline_addr as u64).to_le_bytes());
948    }
949}
950
951fn modify_jmp_with_thread_cb(hook: &Hooker, trampoline_addr: usize) -> Result<(), HookError> {
952    if let CallbackOption::Some(cbs) = &hook.thread_cb {
953        if !cbs.pre() {
954            return Err(HookError::PreHook);
955        }
956        modify_jmp(hook.addr, trampoline_addr);
957        cbs.post();
958        Ok(())
959    } else {
960        modify_jmp(hook.addr, trampoline_addr);
961        Ok(())
962    }
963}
964
965fn recover_jmp(dest_addr: usize, origin: &[u8]) {
966    let buf = unsafe { slice::from_raw_parts_mut(dest_addr as *mut u8, origin.len()) };
967    // jmp trampoline_addr
968    buf.copy_from_slice(origin);
969}
970
971fn recover_jmp_with_thread_cb(hook: &HookPoint) -> Result<(), HookError> {
972    if let CallbackOption::Some(cbs) = &hook.thread_cb {
973        if !cbs.pre() {
974            return Err(HookError::PreHook);
975        }
976        recover_jmp(hook.addr, &hook.origin);
977        cbs.post();
978    } else {
979        recover_jmp(hook.addr, &hook.origin);
980    }
981    Ok(())
982}