hooker/
lib.rs

1#![cfg_attr(not(feature = "std"), no_std)]
2
3use core::{mem::MaybeUninit, ops::Range};
4
5use arrayvec::ArrayVec;
6use thiserror_no_std::Error;
7use zydis_sys::{
8    ZyanStatus, ZydisDecodedInstruction, ZydisDecodedOperand, ZydisDecoder, ZydisDecoderDecodeFull,
9    ZydisDecoderInit, ZydisEncoderDecodedInstructionToEncoderRequest,
10    ZydisEncoderEncodeInstruction, ZydisEncoderRequest, ZydisMachineMode, ZydisMnemonic,
11    ZydisOperandType, ZydisRegister, ZydisRegisterGetLargestEnclosing, ZydisStackWidth,
12    ZYDIS_MAX_INSTRUCTION_LENGTH, ZYDIS_MAX_OPERAND_COUNT,
13};
14
15const MAX_INSN_OPERANDS: usize = ZYDIS_MAX_OPERAND_COUNT as usize;
16const ZYAN_IS_ERROR_BIT_MASK: u32 = 0x80000000;
17const POSSIBLE_TMP_REGS: &[ZydisRegister] = &[
18    ZydisRegister::ZYDIS_REGISTER_RAX,
19    ZydisRegister::ZYDIS_REGISTER_RBX,
20    ZydisRegister::ZYDIS_REGISTER_RCX,
21    ZydisRegister::ZYDIS_REGISTER_RDX,
22    ZydisRegister::ZYDIS_REGISTER_RSI,
23    ZydisRegister::ZYDIS_REGISTER_RDI,
24    ZydisRegister::ZYDIS_REGISTER_RBP,
25];
26
27const PUSH_RIP_INSN_LEN: usize = 5;
28const PUSH_RIP_INSN: [u8; PUSH_RIP_INSN_LEN] = [0xE8, 0x00, 0x00, 0x00, 0x00];
29
30const RET_INSN: u8 = 0xc3;
31
32macro_rules! const_max {
33    ($a: expr, $b: expr) => {
34        if $a > $b {
35            $a
36        } else {
37            $b
38        }
39    };
40    ($a: expr, $b: expr, $($other: expr),+) => {
41        if $a > $b {
42            const_max!($a, $($other),+)
43        } else {
44            const_max!($b, $($other),+)
45        }
46    }
47}
48macro_rules! max_size {
49    ($($ty: ty),+) => {
50        const_max!(
51            $(
52                core::mem::size_of::<$ty>()
53            ),+
54        )
55    };
56}
57
58const MAX_INSN_LEN: usize = ZYDIS_MAX_INSTRUCTION_LENGTH as usize;
59
60/// the amount of bytes added to a relocated [rip+X] addressing instruction.
61const RELOCATED_MEM_RIP_INSN_ADDED_LEN: usize =
62    core::mem::size_of::<RelocatedMemRipPrefix>() + core::mem::size_of::<RelocatedMemRipPostfix>();
63
64/// the maximum length of a relocated [rip+X] addressing instruction.
65const MAX_RELOCATED_MEM_RIP_INSN_LEN: usize = MAX_INSN_LEN + RELOCATED_MEM_RIP_INSN_ADDED_LEN;
66
67/// the maximum length of a relocated instruction
68pub const MAX_RELOCATED_INSN_LEN: usize = const_max!(
69    max_size!(RelocatedJmpImm, RelocatedCallImm, RelocatedCondJmpImm),
70    MAX_RELOCATED_MEM_RIP_INSN_LEN
71);
72
73/// the maximum length of an instruction that is either relocated or the original instruction
74pub const MAX_MAYBE_RELOCATED_INSN_LEN: usize = const_max!(MAX_RELOCATED_INSN_LEN, MAX_INSN_LEN);
75
76/// the length of a short relative jumper.
77pub const SHORT_REL_JUMPER_LEN: usize = core::mem::size_of::<ShortRelJumper>();
78
79/// the length of a short jumper.
80pub const SHORT_JUMPER_LEN: usize = core::mem::size_of::<ShortJumper>();
81
82/// the length of a long jumper.
83pub const LONG_JUMPER_LEN: usize = core::mem::size_of::<LongJumper>();
84
85/// the maximum length of a jumper of any kind.
86pub const MAX_JUMPER_LEN: usize = core::mem::size_of::<JumperUnion>();
87
88/// the maximum length of a trampoiline.
89pub const MAX_TRAMPOLINE_LEN: usize = {
90    // reserve space for all relocated instructions plus a jumper at the end
91    MAX_RELOCATED_INSNS_LEN + MAX_JUMPER_LEN
92};
93
94/// the maximum amount of instructions that we may need to relocate when hooking a fn.
95pub const MAX_RELOCATED_INSNS_AMOUNT: usize = {
96    // at the worst case, every instruction is one byte, so we will have to relocate each of them.
97    MAX_JUMPER_LEN
98};
99
100/// the maximum total length of all of the relocated instruction when relocating the instructions at the start of a fn.
101pub const MAX_RELOCATED_INSNS_LEN: usize = {
102    // this is basically the maximum amount of instructions that we may relocate multiplied by the maximum size of the *maybe* relocated
103    // instruction, since not all instructions will actually require relocations, some will be copied as is.
104    MAX_RELOCATED_INSNS_AMOUNT * MAX_MAYBE_RELOCATED_INSN_LEN
105};
106
107/// a type alias for the bytes of a jumper.
108pub type JumperBytes = ArrayVec<u8, MAX_JUMPER_LEN>;
109
110/// a type alias for the bytes of a relocated instruction.
111pub type RelocatedInsnBytes = ArrayVec<u8, MAX_RELOCATED_INSN_LEN>;
112
113/// a type alias for the bytes of some relocated instructions.
114pub type RelocatedInsnsBytes = ArrayVec<u8, MAX_RELOCATED_INSNS_LEN>;
115
116/// a type alias for the bytes of a trampoline.
117pub type TrampolineBytes = ArrayVec<u8, MAX_TRAMPOLINE_LEN>;
118
119/// a type alias for the bytes of an instruction.
120type InsnBytes = ArrayVec<u8, MAX_INSN_LEN>;
121
122/// generates information needed to hook the given fn.
123pub fn gen_hook_info(
124    hooked_fn_content: &[u8],
125    hooked_fn_runtime_addr: u64,
126    hook_fn_runtime_addr: u64,
127) -> Result<HookInfo, HookError> {
128    let jumper = determine_best_jumper_kind_and_build(hooked_fn_runtime_addr, hook_fn_runtime_addr);
129    let relocated_fn = relocate_fn_start(hooked_fn_content, hooked_fn_runtime_addr, jumper.len())?;
130    Ok(HookInfo {
131        jumper,
132        relocated_fn,
133    })
134}
135
136/// information required for hooking a fn
137pub struct HookInfo {
138    jumper: JumperBytes,
139    relocated_fn: RelocatedFnStart,
140}
141impl HookInfo {
142    /// returns the jumper which should be placed at the start of the hooked fn in order to hook it.
143    /// this takes ownership of the hook info. make sure that you first build your trampoline.
144    pub fn jumper(&self) -> &JumperBytes {
145        &self.jumper
146    }
147    /// returns the size of the trampoline which will be built for this hooked fn.
148    pub fn trampoline_size(&self) -> usize {
149        self.relocated_fn.trampoline_size()
150    }
151    /// builds a trampoline which will be placed at the given runtime address.
152    /// the size of the trampoline can be determined by calling [`trampoline_size`].
153    ///
154    /// [`trampoline_size`]: HookInfo::trampoline_size
155    pub fn build_trampoline(&self, trampoline_runtime_addr: u64) -> TrampolineBytes {
156        self.relocated_fn.build_trampoline(trampoline_runtime_addr)
157    }
158}
159
160/// determines the best jumper kind to use in a specific case.
161pub fn determine_best_jumper_kind(jumper_addr: u64, target_addr: u64) -> JumperKind {
162    let short_rel_hook_offset =
163        (jumper_addr + SHORT_REL_JUMPER_LEN as u64).wrapping_sub(target_addr) as i64;
164    if i32::try_from(short_rel_hook_offset).is_ok() {
165        JumperKind::ShortRel
166    } else if u32::try_from(target_addr).is_ok() {
167        JumperKind::Short
168    } else {
169        JumperKind::Long
170    }
171}
172
173/// determines the best jumper kind to use in a specific case and builds it.
174pub fn determine_best_jumper_kind_and_build(
175    hooked_fn_runtime_addr: u64,
176    hook_fn_runtime_addr: u64,
177) -> JumperBytes {
178    determine_best_jumper_kind(hooked_fn_runtime_addr, hook_fn_runtime_addr)
179        .build(hooked_fn_runtime_addr, hook_fn_runtime_addr)
180}
181
182/// relocate the instructions at the start of the fn so that we can put them in a different memory address and they
183/// will still work fine.
184///
185/// # Safety
186///
187/// the provided `relocate_bytes_amount` must be lower than or equal to the max jumper size, otherwise the function will panic.
188pub fn relocate_fn_start(
189    hooked_fn_content: &[u8],
190    hooked_fn_runtime_addr: u64,
191    relocate_bytes_amount: usize,
192) -> Result<RelocatedFnStart, HookError> {
193    let mut cur_index = 0;
194    let decoder = Decoder::new();
195    let mut relocated_insns_bytes = RelocatedInsnsBytes::new();
196    let relocated_insns_addr_range =
197        hooked_fn_runtime_addr..hooked_fn_runtime_addr + relocate_bytes_amount as u64;
198    while cur_index < relocate_bytes_amount {
199        let insn = decoder
200            .decode(
201                &hooked_fn_content[cur_index..],
202                hooked_fn_runtime_addr + cur_index as u64,
203            )
204            .map_err(|_| HookError::FailedToDecodeInsn { offset: cur_index })?;
205        match relocate_insn(&insn, cur_index, &relocated_insns_addr_range)? {
206            Some(relocated_insn) => relocated_insns_bytes
207                .try_extend_from_slice(relocated_insn.as_slice())
208                .unwrap(),
209            None => {
210                // copy the instruction as is
211                relocated_insns_bytes
212                    .try_extend_from_slice(
213                        &hooked_fn_content[cur_index..cur_index + insn.insn.length as usize],
214                    )
215                    .unwrap()
216            }
217        }
218        let insn_end_index = cur_index + insn.insn.length as usize;
219        // if we encounter a `ret`, this is the end of this fn, so make sure that this is the last instruction that
220        // we need to relocate.
221        if insn.insn.mnemonic == ZydisMnemonic::ZYDIS_MNEMONIC_RET
222            && insn_end_index < relocate_bytes_amount
223        {
224            return Err(HookError::FnTooSmallTooHook {
225                fn_size: insn_end_index,
226                jumper_size: relocate_bytes_amount,
227            });
228        }
229        cur_index += insn.insn.length as usize;
230    }
231    let trampoline_jumper_target_offset = cur_index;
232    while cur_index < hooked_fn_content.len() {
233        let Ok(decoded_insn) = decoder.decode(
234            &hooked_fn_content[cur_index..],
235            hooked_fn_runtime_addr + cur_index as u64,
236        ) else {
237            break;
238        };
239        if mnemonic_is_branch(decoded_insn.insn.mnemonic).is_some() {
240            check_jmp_insn_doesnt_jump_into_relocated_insns(
241                &decoded_insn,
242                cur_index,
243                &relocated_insns_addr_range,
244            )?;
245        }
246        // if we encounter a `ret`, this is the end of this fn, so make sure that this is the last instruction that
247        // we need to relocate.
248        if decoded_insn.insn.mnemonic == ZydisMnemonic::ZYDIS_MNEMONIC_RET {
249            break;
250        }
251        cur_index += decoded_insn.insn.length as usize;
252    }
253    Ok(RelocatedFnStart {
254        relocated_insns_bytes,
255        trampoline_jumper_target_offset,
256        hooked_fn_runtime_addr,
257    })
258}
259
260fn relocate_insn(
261    decoded_insn: &DecodedInsnInfo,
262    decoded_insn_offset: usize,
263    relocated_insns_addr_range: &Range<u64>,
264) -> Result<Option<RelocatedInsnBytes>, HookError> {
265    if let Some(branch_kind) = mnemonic_is_branch(decoded_insn.insn.mnemonic) {
266        return relocate_branch_insn(
267            decoded_insn,
268            decoded_insn_offset,
269            branch_kind,
270            relocated_insns_addr_range,
271        );
272    }
273    // for detecting other branch types which we do not support, we can just check for rip relative immediate operands.
274    if decoded_insn.does_have_rel_imm_operand() {
275        return Err(HookError::UnsupportedRipRelativeInsn {
276            offset: decoded_insn_offset,
277        });
278    }
279
280    let mut rip_relative_operands = decoded_insn
281        .visible_operands()
282        .iter()
283        .enumerate()
284        .filter(|(_, operand)| is_operand_rip_relative_mem_access(&operand));
285    let Some((rip_relative_operand_index, rip_relative_operand)) = rip_relative_operands.next()
286    else {
287        // no rip relative operands, so no need to relocate the instruction
288        return Ok(None);
289    };
290    // an instruction can't have multiple rip relative operands
291    assert!(rip_relative_operands.next().is_none());
292
293    Ok(Some(relocate_mem_rip_insn(
294        decoded_insn,
295        decoded_insn_offset,
296        rip_relative_operand,
297        rip_relative_operand_index,
298    )?))
299}
300
301fn relocate_mem_rip_insn(
302    decoded_insn: &DecodedInsnInfo,
303    decoded_insn_offset: usize,
304    rip_relative_operand: &zydis_sys::ZydisDecodedOperand_,
305    rip_relative_operand_index: usize,
306) -> Result<RelocatedInsnBytes, HookError> {
307    // if it uses the stack, we can't relocate it, since our relocation involves pushing some registers to the stack,
308    // which will corrupt the instruction's behaviour.
309    if decoded_insn.does_use_register(ZydisRegister::ZYDIS_REGISTER_RSP) {
310        return Err(HookError::UnsupportedRipRelativeInsn {
311            offset: decoded_insn_offset,
312        });
313    }
314
315    // calculate the target address of the `rip+displacement`.
316    let mem_operand = unsafe { &rip_relative_operand.__bindgen_anon_1.mem };
317    let mem_operand_disp = if mem_operand.disp.has_displacement != 0 {
318        mem_operand.disp.value
319    } else {
320        0
321    };
322    let mem_rip_target_addr = decoded_insn
323        .end_addr()
324        .wrapping_add_signed(mem_operand_disp);
325
326    // find a usable temp register
327    let tmp_reg = POSSIBLE_TMP_REGS
328        .iter()
329        .copied()
330        .find(|tmp_reg| !decoded_insn.does_use_register(*tmp_reg))
331        .expect("instruction uses all possible temporary registers");
332
333    // re-encode the instruction but replace the `rip+displacement` part with `tmp_reg`.
334    let mut encoder_req = decoded_insn.to_encoder_request(decoded_insn_offset)?;
335    encoder_req.operands[rip_relative_operand_index]
336        .mem
337        .displacement = 0;
338    encoder_req.operands[rip_relative_operand_index].mem.base = tmp_reg;
339    let re_encoded = encode_insn(&encoder_req).map_err(|_| HookError::FailedToReEncodeInsn {
340        offset: decoded_insn_offset,
341    })?;
342
343    // build the prefix and postfix
344    let prefix = RelocatedMemRipPrefix::new(tmp_reg, mem_rip_target_addr);
345    let postfix = RelocatedMemRipPostfix::new(tmp_reg);
346
347    // combine all parts into a single byte array
348    let mut relocated_insn_bytes = RelocatedInsnBytes::new();
349    relocated_insn_bytes
350        .try_extend_from_slice(as_raw_bytes(&prefix))
351        .unwrap();
352    relocated_insn_bytes
353        .try_extend_from_slice(&re_encoded)
354        .unwrap();
355    relocated_insn_bytes
356        .try_extend_from_slice(as_raw_bytes(&postfix))
357        .unwrap();
358    Ok(relocated_insn_bytes)
359}
360
361fn encode_insn(encoder_req: &ZydisEncoderRequest) -> Result<InsnBytes, ()> {
362    let mut insn_bytes = InsnBytes::new();
363    let mut insn_len = insn_bytes.capacity() as u64;
364    let status = unsafe {
365        ZydisEncoderEncodeInstruction(encoder_req, insn_bytes.as_mut_ptr().cast(), &mut insn_len)
366    };
367    zyan_check(status)?;
368    assert!(insn_len <= insn_bytes.capacity() as u64);
369    unsafe {
370        insn_bytes.set_len(insn_len as usize);
371    }
372    Ok(insn_bytes)
373}
374
375fn relocate_branch_insn(
376    decoded_insn: &DecodedInsnInfo,
377    decoded_insn_offset: usize,
378    branch_kind: BranchKind,
379    relocated_insns_addr_range: &Range<u64>,
380) -> Result<Option<RelocatedInsnBytes>, HookError> {
381    assert_eq!(decoded_insn.visible_operands().len(), 1);
382    let operand = &decoded_insn.visible_operands()[0];
383    check_jmp_insn_doesnt_jump_into_relocated_insns(
384        decoded_insn,
385        decoded_insn_offset,
386        relocated_insns_addr_range,
387    )?;
388    match operand.type_ {
389        ZydisOperandType::ZYDIS_OPERAND_TYPE_REGISTER
390        | ZydisOperandType::ZYDIS_OPERAND_TYPE_POINTER => Ok(None),
391        ZydisOperandType::ZYDIS_OPERAND_TYPE_IMMEDIATE => {
392            let imm_operand = unsafe { &operand.__bindgen_anon_1.imm };
393            if imm_operand.is_relative == 0 {
394                // if it is not relative, no need to relocate it
395                return Ok(None);
396            }
397            let insn_end_addr = decoded_insn.end_addr();
398            let branch_target = insn_end_addr.wrapping_add(unsafe { imm_operand.value.u });
399            let relocated_insn_bytes: RelocatedInsnBytes = match branch_kind {
400                BranchKind::Jmp => as_raw_bytes(&RelocatedJmpImm::new(branch_target))
401                    .try_into()
402                    .unwrap(),
403                BranchKind::CondJmp => as_raw_bytes(&RelocatedCondJmpImm::new(
404                    decoded_insn.insn.mnemonic,
405                    branch_target,
406                ))
407                .try_into()
408                .unwrap(),
409                BranchKind::Call => as_raw_bytes(&RelocatedCallImm::new(branch_target))
410                    .try_into()
411                    .unwrap(),
412            };
413            Ok(Some(relocated_insn_bytes))
414        }
415        ZydisOperandType::ZYDIS_OPERAND_TYPE_MEMORY => Err(HookError::UnsupportedRipRelativeInsn {
416            offset: decoded_insn_offset,
417        }),
418        _ => unreachable!(),
419    }
420}
421
422fn check_jmp_insn_doesnt_jump_into_relocated_insns(
423    decoded_insn: &DecodedInsnInfo,
424    decoded_insn_offset: usize,
425    relocated_insns_addr_range: &Range<u64>,
426) -> Result<(), HookError> {
427    assert_eq!(decoded_insn.visible_operands().len(), 1);
428    let operand = &decoded_insn.visible_operands()[0];
429    match operand.type_ {
430        ZydisOperandType::ZYDIS_OPERAND_TYPE_REGISTER
431        | ZydisOperandType::ZYDIS_OPERAND_TYPE_POINTER => Ok(()),
432        ZydisOperandType::ZYDIS_OPERAND_TYPE_IMMEDIATE => {
433            let imm_operand = unsafe { &operand.__bindgen_anon_1.imm };
434            let branch_target = if imm_operand.is_relative == 0 {
435                unsafe { imm_operand.value.u }
436            } else {
437                let insn_end_addr = decoded_insn.end_addr();
438                insn_end_addr.wrapping_add(unsafe { imm_operand.value.u })
439            };
440            if relocated_insns_addr_range.contains(&branch_target) {
441                let branch_target_offset = branch_target - relocated_insns_addr_range.start;
442                return Err(HookError::InsnJumpsIntoAnotherRelocatedInsn {
443                    insn_offset: decoded_insn_offset,
444                    target_offset: branch_target_offset as usize,
445                });
446            }
447            Ok(())
448        }
449        ZydisOperandType::ZYDIS_OPERAND_TYPE_MEMORY => Ok(()),
450        _ => unreachable!(),
451    }
452}
453
454fn mnemonic_is_branch(mnemonic: ZydisMnemonic) -> Option<BranchKind> {
455    match mnemonic {
456        ZydisMnemonic::ZYDIS_MNEMONIC_JMP => Some(BranchKind::Jmp),
457        ZydisMnemonic::ZYDIS_MNEMONIC_CALL => Some(BranchKind::Call),
458        ZydisMnemonic::ZYDIS_MNEMONIC_JB
459        | ZydisMnemonic::ZYDIS_MNEMONIC_JBE
460        | ZydisMnemonic::ZYDIS_MNEMONIC_JL
461        | ZydisMnemonic::ZYDIS_MNEMONIC_JLE
462        | ZydisMnemonic::ZYDIS_MNEMONIC_JNB
463        | ZydisMnemonic::ZYDIS_MNEMONIC_JNBE
464        | ZydisMnemonic::ZYDIS_MNEMONIC_JNL
465        | ZydisMnemonic::ZYDIS_MNEMONIC_JNLE
466        | ZydisMnemonic::ZYDIS_MNEMONIC_JNO
467        | ZydisMnemonic::ZYDIS_MNEMONIC_JNP
468        | ZydisMnemonic::ZYDIS_MNEMONIC_JNS
469        | ZydisMnemonic::ZYDIS_MNEMONIC_JNZ
470        | ZydisMnemonic::ZYDIS_MNEMONIC_JO
471        | ZydisMnemonic::ZYDIS_MNEMONIC_JP
472        | ZydisMnemonic::ZYDIS_MNEMONIC_JS
473        | ZydisMnemonic::ZYDIS_MNEMONIC_JZ => Some(BranchKind::CondJmp),
474        _ => None,
475    }
476}
477
478#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
479enum BranchKind {
480    Jmp,
481    CondJmp,
482    Call,
483}
484
485/// information about relocated instructions from the start of a fn
486pub struct RelocatedFnStart {
487    /// the bytes of the relocated instructions.
488    pub relocated_insns_bytes: RelocatedInsnsBytes,
489    /// the offset in the hooked function where the trampoline jumper should jump to in order to continue execution of this function.
490    pub trampoline_jumper_target_offset: usize,
491    /// the runtime address of the hooked function.
492    pub hooked_fn_runtime_addr: u64,
493}
494impl RelocatedFnStart {
495    /// returns the size of the trampoline which will be built for this hooked fn.
496    pub fn trampoline_size(&self) -> usize {
497        self.relocated_insns_bytes.len() + LONG_JUMPER_LEN
498    }
499
500    /// builds a trampoline which will be placed at the given runtime address.
501    /// the size of the trampoline can be determined by calling [`trampoline_size`].
502    ///
503    /// [`trampoline_size`]: HookInfo::trampoline_size
504    pub fn build_trampoline(&self, trampoline_runtime_addr: u64) -> TrampolineBytes {
505        let mut tramp_bytes = TrampolineBytes::new();
506        tramp_bytes
507            .try_extend_from_slice(&self.relocated_insns_bytes)
508            .unwrap();
509        let jumper = JumperKind::Long.build(
510            trampoline_runtime_addr + self.relocated_insns_bytes.len() as u64,
511            self.hooked_fn_runtime_addr + self.trampoline_jumper_target_offset as u64,
512        );
513        tramp_bytes.try_extend_from_slice(&jumper).unwrap();
514        tramp_bytes
515    }
516}
517
518struct Decoder {
519    decoder: ZydisDecoder,
520}
521impl Decoder {
522    fn new() -> Self {
523        let mut decoder: MaybeUninit<ZydisDecoder> = MaybeUninit::uninit();
524        let status = unsafe {
525            ZydisDecoderInit(
526                decoder.as_mut_ptr(),
527                ZydisMachineMode::ZYDIS_MACHINE_MODE_LONG_64,
528                ZydisStackWidth::ZYDIS_STACK_WIDTH_64,
529            )
530        };
531        zyan_check(status).expect("failed to initialize a 64-bit mode zydis decoder");
532        Self {
533            decoder: unsafe { decoder.assume_init() },
534        }
535    }
536
537    fn decode(&self, buf: &[u8], insn_runtime_addr: u64) -> Result<DecodedInsnInfo, ()> {
538        let mut insn_uninit: MaybeUninit<ZydisDecodedInstruction> = MaybeUninit::uninit();
539        let mut operands: ArrayVec<ZydisDecodedOperand, MAX_INSN_OPERANDS> = ArrayVec::new();
540
541        let status = unsafe {
542            ZydisDecoderDecodeFull(
543                &self.decoder,
544                buf.as_ptr().cast(),
545                buf.len() as u64,
546                insn_uninit.as_mut_ptr(),
547                operands.as_mut_ptr(),
548            )
549        };
550        zyan_check(status)?;
551
552        let insn = unsafe { insn_uninit.assume_init() };
553        assert!(insn.operand_count as usize <= operands.capacity());
554        unsafe { operands.set_len(insn.operand_count as usize) }
555
556        Ok(DecodedInsnInfo {
557            insn,
558            operands,
559            addr: insn_runtime_addr,
560        })
561    }
562}
563
564fn is_operand_rip_relative_mem_access(operand: &ZydisDecodedOperand) -> bool {
565    match operand.type_ {
566        ZydisOperandType::ZYDIS_OPERAND_TYPE_MEMORY => {
567            if unsafe { operand.__bindgen_anon_1.mem }.base == ZydisRegister::ZYDIS_REGISTER_RIP {
568                true
569            } else {
570                false
571            }
572        }
573        _ => false,
574    }
575}
576
577fn is_operand_rip_relative_imm(operand: &ZydisDecodedOperand) -> bool {
578    match operand.type_ {
579        ZydisOperandType::ZYDIS_OPERAND_TYPE_IMMEDIATE => {
580            unsafe { operand.__bindgen_anon_1.imm }.is_relative != 0
581        }
582        _ => false,
583    }
584}
585
586#[derive(Debug)]
587struct DecodedInsnInfo {
588    addr: u64,
589    insn: ZydisDecodedInstruction,
590    operands: ArrayVec<ZydisDecodedOperand, MAX_INSN_OPERANDS>,
591}
592impl DecodedInsnInfo {
593    fn end_addr(&self) -> u64 {
594        self.addr + self.insn.length as u64
595    }
596    fn visible_operands(&self) -> &[ZydisDecodedOperand] {
597        &self.operands[..self.insn.operand_count_visible as usize]
598    }
599    fn does_use_register(&self, reg: ZydisRegister) -> bool {
600        self.operands
601            .iter()
602            .any(|operand| does_operand_use_register(operand, reg))
603    }
604    fn does_have_rel_imm_operand(&self) -> bool {
605        self.operands
606            .iter()
607            .any(|operand| is_operand_rip_relative_imm(operand))
608    }
609    fn to_encoder_request(&self, insn_offset: usize) -> Result<ZydisEncoderRequest, HookError> {
610        let mut encoder_req_uninit: MaybeUninit<ZydisEncoderRequest> = MaybeUninit::uninit();
611        let status = unsafe {
612            ZydisEncoderDecodedInstructionToEncoderRequest(
613                &self.insn,
614                self.operands.as_ptr(),
615                self.insn.operand_count_visible,
616                encoder_req_uninit.as_mut_ptr(),
617            )
618        };
619        zyan_check(status).map_err(|_| HookError::FailedToReEncodeInsn {
620            offset: insn_offset,
621        })?;
622        Ok(unsafe { encoder_req_uninit.assume_init() })
623    }
624}
625
626fn does_operand_use_register(operand: &ZydisDecodedOperand, reg: ZydisRegister) -> bool {
627    match operand.type_ {
628        ZydisOperandType::ZYDIS_OPERAND_TYPE_REGISTER => {
629            do_regs_collide(unsafe { operand.__bindgen_anon_1.reg }.value, reg)
630        }
631        ZydisOperandType::ZYDIS_OPERAND_TYPE_IMMEDIATE
632        | ZydisOperandType::ZYDIS_OPERAND_TYPE_POINTER => false,
633        ZydisOperandType::ZYDIS_OPERAND_TYPE_MEMORY => {
634            let mem_operand = unsafe { &operand.__bindgen_anon_1.mem };
635            do_regs_collide(mem_operand.segment, reg)
636                || do_regs_collide(mem_operand.base, reg)
637                || do_regs_collide(mem_operand.index, reg)
638        }
639        _ => unreachable!(),
640    }
641}
642
643fn do_regs_collide(reg_a: ZydisRegister, reg_b: ZydisRegister) -> bool {
644    if reg_a == reg_b {
645        return true;
646    }
647    let largest_enclosing_reg_a = unsafe {
648        ZydisRegisterGetLargestEnclosing(ZydisMachineMode::ZYDIS_MACHINE_MODE_LONG_64, reg_a)
649    };
650    if largest_enclosing_reg_a == ZydisRegister::ZYDIS_REGISTER_NONE {
651        return false;
652    }
653    let largest_enclosing_reg_b = unsafe {
654        ZydisRegisterGetLargestEnclosing(ZydisMachineMode::ZYDIS_MACHINE_MODE_LONG_64, reg_b)
655    };
656    if largest_enclosing_reg_b == ZydisRegister::ZYDIS_REGISTER_NONE {
657        return false;
658    }
659    largest_enclosing_reg_a == largest_enclosing_reg_b
660}
661
662/// an error which occured while trying to relocate instructions.
663#[derive(Debug, Error)]
664pub enum HookError {
665    #[error("failed to decode instruction at offset {offset}")]
666    FailedToDecodeInsn { offset: usize },
667
668    #[error("can't relocate rip relative instruction at offset {offset}")]
669    UnsupportedRipRelativeInsn { offset: usize },
670
671    #[error("function size {fn_size} is to small for jumper of size {jumper_size}")]
672    FnTooSmallTooHook { fn_size: usize, jumper_size: usize },
673
674    #[error("instruction at offset {insn_offset} jumps into a relocated instruction at offset {target_offset}")]
675    InsnJumpsIntoAnotherRelocatedInsn {
676        insn_offset: usize,
677        target_offset: usize,
678    },
679
680    #[error("failed to re-encode instruction at offset {offset} while trying to relocate it")]
681    FailedToReEncodeInsn { offset: usize },
682}
683
684/// the different kinds of jumper available
685#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
686pub enum JumperKind {
687    /// a short relative jumper, which is a 32 bit relative jump.
688    ShortRel,
689    /// a short jumper, which is a push 32 bit address followed by a ret instruction.
690    Short,
691    /// a long jumper, which is a jmp to rip followed by the raw 64 bit address.
692    Long,
693}
694impl JumperKind {
695    /// returns the size of the jumper in bytes
696    pub fn size_in_bytes(&self) -> usize {
697        match self {
698            JumperKind::ShortRel => SHORT_REL_JUMPER_LEN,
699            JumperKind::Short => SHORT_JUMPER_LEN,
700            JumperKind::Long => LONG_JUMPER_LEN,
701        }
702    }
703    /// builds the jumper into an array of bytes.
704    pub fn build(&self, jumper_addr: u64, target_addr: u64) -> JumperBytes {
705        match self {
706            JumperKind::ShortRel => {
707                let jmp_insn_end_addr = jumper_addr + SHORT_REL_JUMPER_LEN as u64;
708                let displacement = target_addr.wrapping_sub(jmp_insn_end_addr) as i64;
709                let displacement_i32 = i32::try_from(displacement)
710                    .expect("tried to use a short relative jumper for but the distance from the hooked fn to the hook does not fit in 32 bits");
711                let jumper = ShortRelJumper::new(displacement_i32);
712                as_raw_bytes(&jumper).try_into().unwrap()
713            }
714            JumperKind::Short => {
715                let hook_fn_addr_u32 = u32::try_from(target_addr).expect(
716                    "tried to use a short jumper but the hook fn address does not fit in 32 bits",
717                );
718                let jumper = ShortJumper::new(hook_fn_addr_u32);
719                as_raw_bytes(&jumper).try_into().unwrap()
720            }
721            JumperKind::Long => {
722                let jumper = LongJumper::new(target_addr);
723                as_raw_bytes(&jumper).try_into().unwrap()
724            }
725        }
726    }
727}
728
729fn as_raw_bytes<T>(value: &T) -> &[u8] {
730    unsafe {
731        core::slice::from_raw_parts(value as *const T as *const u8, core::mem::size_of::<T>())
732    }
733}
734
735#[allow(dead_code)]
736#[repr(C, packed)]
737#[derive(Clone, Copy)]
738struct RelocatedMemRipPrefix {
739    push_tmp_reg: PushGpr,
740    mov_tmp_reg: MovGpr64BitImm,
741}
742impl RelocatedMemRipPrefix {
743    fn new(tmp_reg: ZydisRegister, target_addr: u64) -> Self {
744        Self {
745            push_tmp_reg: PushGpr::new(tmp_reg),
746            mov_tmp_reg: MovGpr64BitImm::new(tmp_reg, target_addr),
747        }
748    }
749}
750
751#[allow(dead_code)]
752#[repr(C, packed)]
753#[derive(Clone, Copy)]
754struct RelocatedMemRipPostfix {
755    pop_tmp_reg: PopGpr,
756}
757impl RelocatedMemRipPostfix {
758    fn new(tmp_reg: ZydisRegister) -> Self {
759        Self {
760            pop_tmp_reg: PopGpr::new(tmp_reg),
761        }
762    }
763}
764
765#[allow(dead_code)]
766#[repr(C, packed)]
767#[derive(Clone, Copy)]
768struct RelocatedJmpImm {
769    jumper: LongJumper,
770}
771impl RelocatedJmpImm {
772    fn new(target_addr: u64) -> Self {
773        Self {
774            jumper: LongJumper::new(target_addr),
775        }
776    }
777}
778
779#[allow(dead_code)]
780#[repr(C, packed)]
781#[derive(Clone, Copy)]
782struct JmpMemRipPlus {
783    opcode: [u8; 2],
784    rip_plus: i32,
785}
786impl JmpMemRipPlus {
787    fn new(rip_plus: i32) -> Self {
788        Self {
789            opcode: [0xff, 0x25],
790            rip_plus: rip_plus.to_le(),
791        }
792    }
793}
794
795#[allow(dead_code)]
796#[repr(C, packed)]
797#[derive(Clone, Copy)]
798struct RelJmp8Bit {
799    code: [u8; 2],
800}
801impl RelJmp8Bit {
802    fn new(jmp_mnemonic: ZydisMnemonic, displacement: i8) -> Self {
803        let mut encoder_request: ZydisEncoderRequest = unsafe { core::mem::zeroed() };
804        encoder_request.machine_mode = ZydisMachineMode::ZYDIS_MACHINE_MODE_LONG_64;
805        encoder_request.mnemonic = jmp_mnemonic;
806        encoder_request.operand_count = 1;
807        encoder_request.operands[0].type_ = ZydisOperandType::ZYDIS_OPERAND_TYPE_IMMEDIATE;
808        encoder_request.operands[0].imm.s = displacement as i64;
809        let mut code = [0u8; 2];
810        let mut code_len = code.len() as u64;
811        let status = unsafe {
812            ZydisEncoderEncodeInstruction(&encoder_request, code.as_mut_ptr().cast(), &mut code_len)
813        };
814        zyan_check(status).expect("failed to encode 2 byte relative conditional jump");
815        assert_eq!(code_len, 2);
816        Self { code }
817    }
818}
819
820#[allow(dead_code)]
821#[repr(C, packed)]
822#[derive(Clone, Copy)]
823struct PushGpr {
824    code: u8,
825}
826impl PushGpr {
827    fn new(gpr: ZydisRegister) -> Self {
828        let mut encoder_request: ZydisEncoderRequest = unsafe { core::mem::zeroed() };
829        encoder_request.machine_mode = ZydisMachineMode::ZYDIS_MACHINE_MODE_LONG_64;
830        encoder_request.mnemonic = ZydisMnemonic::ZYDIS_MNEMONIC_PUSH;
831        encoder_request.operand_count = 1;
832        encoder_request.operands[0].type_ = ZydisOperandType::ZYDIS_OPERAND_TYPE_REGISTER;
833        encoder_request.operands[0].reg.value = gpr;
834        let mut code = [0u8; 1];
835        let mut code_len = code.len() as u64;
836        let status = unsafe {
837            ZydisEncoderEncodeInstruction(&encoder_request, code.as_mut_ptr().cast(), &mut code_len)
838        };
839        zyan_check(status).expect("failed to encode 1 byte push gpr");
840        assert_eq!(code_len, 1);
841        Self { code: code[0] }
842    }
843}
844
845#[allow(dead_code)]
846#[repr(C, packed)]
847#[derive(Clone, Copy)]
848struct PopGpr {
849    code: u8,
850}
851impl PopGpr {
852    fn new(gpr: ZydisRegister) -> Self {
853        let mut encoder_request: ZydisEncoderRequest = unsafe { core::mem::zeroed() };
854        encoder_request.machine_mode = ZydisMachineMode::ZYDIS_MACHINE_MODE_LONG_64;
855        encoder_request.mnemonic = ZydisMnemonic::ZYDIS_MNEMONIC_POP;
856        encoder_request.operand_count = 1;
857        encoder_request.operands[0].type_ = ZydisOperandType::ZYDIS_OPERAND_TYPE_REGISTER;
858        encoder_request.operands[0].reg.value = gpr;
859        let mut code = [0u8; 1];
860        let mut code_len = code.len() as u64;
861        let status = unsafe {
862            ZydisEncoderEncodeInstruction(&encoder_request, code.as_mut_ptr().cast(), &mut code_len)
863        };
864        zyan_check(status).expect("failed to encode 1 byte pop gpr");
865        assert_eq!(code_len, 1);
866        Self { code: code[0] }
867    }
868}
869
870#[allow(dead_code)]
871#[repr(C, packed)]
872#[derive(Clone, Copy)]
873struct MovGpr64BitImm {
874    code: [u8; 10],
875}
876impl MovGpr64BitImm {
877    fn new(gpr: ZydisRegister, value: u64) -> Self {
878        let mut encoder_request: ZydisEncoderRequest = unsafe { core::mem::zeroed() };
879        encoder_request.machine_mode = ZydisMachineMode::ZYDIS_MACHINE_MODE_LONG_64;
880        encoder_request.mnemonic = ZydisMnemonic::ZYDIS_MNEMONIC_MOV;
881        encoder_request.operand_count = 2;
882        encoder_request.operands[0].type_ = ZydisOperandType::ZYDIS_OPERAND_TYPE_REGISTER;
883        encoder_request.operands[0].reg.value = gpr;
884        encoder_request.operands[1].type_ = ZydisOperandType::ZYDIS_OPERAND_TYPE_IMMEDIATE;
885        encoder_request.operands[1].imm.u = value;
886        let mut code = [0u8; 10];
887        let mut code_len = code.len() as u64;
888        let status = unsafe {
889            ZydisEncoderEncodeInstruction(&encoder_request, code.as_mut_ptr().cast(), &mut code_len)
890        };
891        zyan_check(status).expect("failed to encode 10 byte mov 64 bit immediate into gpr");
892        assert_eq!(code_len, 10);
893        Self { code }
894    }
895}
896
897#[allow(dead_code)]
898#[repr(C, packed)]
899#[derive(Clone, Copy)]
900struct RelocatedCondJmpImm {
901    original_jmp: RelJmp8Bit,
902    jmp_not_taken_jmp: RelJmp8Bit,
903    jmp_taken_jumper: LongJumper,
904}
905impl RelocatedCondJmpImm {
906    fn new(cond_jmp_mnemonic: ZydisMnemonic, target_addr: u64) -> Self {
907        Self {
908            original_jmp: RelJmp8Bit::new(
909                cond_jmp_mnemonic,
910                // we want it to skip the jmp not taken jmp and go to the jmp taken jumper
911                core::mem::size_of::<RelJmp8Bit>() as i8,
912            ),
913            jmp_not_taken_jmp: RelJmp8Bit::new(
914                ZydisMnemonic::ZYDIS_MNEMONIC_JMP,
915                // we want it to skip the jmp taken jumper
916                LONG_JUMPER_LEN as i8,
917            ),
918            jmp_taken_jumper: LongJumper::new(target_addr),
919        }
920    }
921}
922
923#[allow(dead_code)]
924#[repr(C, packed)]
925#[derive(Clone, Copy)]
926struct RelocatedCallImm {
927    push_rip_insn: [u8; PUSH_RIP_INSN_LEN],
928    add_to_mem_rsp: Add8BitImmToMemRsp,
929    jumper: LongJumper,
930}
931impl RelocatedCallImm {
932    fn new(target_addr: u64) -> Self {
933        Self {
934            push_rip_insn: PUSH_RIP_INSN,
935            // the pushed rip points to this `add` instruction, we want it to point after the entire relocated call, so skip both the
936            // add and the jumper.
937            add_to_mem_rsp: Add8BitImmToMemRsp::new(
938                (core::mem::size_of::<Add8BitImmToMemRsp>() + LONG_JUMPER_LEN) as i8,
939            ),
940            jumper: LongJumper::new(target_addr),
941        }
942    }
943}
944
945#[allow(dead_code)]
946#[repr(C, packed)]
947#[derive(Clone, Copy)]
948struct Add8BitImmToMemRsp {
949    opcode: [u8; 4],
950    add_value: i8,
951}
952impl Add8BitImmToMemRsp {
953    fn new(add_value: i8) -> Self {
954        Self {
955            opcode: [0x48, 0x83, 0x04, 0x24],
956            add_value,
957        }
958    }
959}
960
961#[allow(dead_code)]
962#[repr(C, packed)]
963#[derive(Clone, Copy)]
964struct ShortRelJumper {
965    jump_opcode: u8,
966    displacement: i32,
967}
968impl ShortRelJumper {
969    fn new(displacement: i32) -> Self {
970        Self {
971            jump_opcode: 0xe9,
972            displacement: displacement.to_le(),
973        }
974    }
975}
976
977#[allow(dead_code)]
978#[repr(C, packed)]
979#[derive(Clone, Copy)]
980struct ShortJumper {
981    push: Push32BitImm,
982    ret: u8,
983}
984impl ShortJumper {
985    fn new(target_addr: u32) -> Self {
986        Self {
987            push: Push32BitImm::new(target_addr),
988            ret: RET_INSN,
989        }
990    }
991}
992
993#[allow(dead_code)]
994#[repr(C, packed)]
995#[derive(Clone, Copy)]
996struct Push32BitImm {
997    push_opcode: u8,
998    pushed_value: u32,
999}
1000impl Push32BitImm {
1001    fn new(pushed_value: u32) -> Self {
1002        Self {
1003            push_opcode: 0x68,
1004            pushed_value: pushed_value.to_le(),
1005        }
1006    }
1007}
1008
1009#[allow(dead_code)]
1010#[repr(C, packed)]
1011#[derive(Clone, Copy)]
1012struct LongJumper {
1013    jmp_rip: JmpMemRipPlus,
1014    target_addr: u64,
1015}
1016impl LongJumper {
1017    fn new(target_addr: u64) -> Self {
1018        Self {
1019            jmp_rip: JmpMemRipPlus::new(0),
1020            target_addr: target_addr.to_le(),
1021        }
1022    }
1023}
1024
1025#[allow(dead_code)]
1026#[repr(C, packed)]
1027#[derive(Clone, Copy)]
1028union JumperUnion {
1029    short_rel: ShortRelJumper,
1030    short: ShortJumper,
1031    long: LongJumper,
1032}
1033
1034fn zyan_is_err(status: ZyanStatus) -> bool {
1035    status & ZYAN_IS_ERROR_BIT_MASK != 0
1036}
1037fn zyan_check(status: ZyanStatus) -> Result<(), ()> {
1038    if zyan_is_err(status) {
1039        Err(())
1040    } else {
1041        Ok(())
1042    }
1043}