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
60const RELOCATED_MEM_RIP_INSN_ADDED_LEN: usize =
62 core::mem::size_of::<RelocatedMemRipPrefix>() + core::mem::size_of::<RelocatedMemRipPostfix>();
63
64const MAX_RELOCATED_MEM_RIP_INSN_LEN: usize = MAX_INSN_LEN + RELOCATED_MEM_RIP_INSN_ADDED_LEN;
66
67pub const MAX_RELOCATED_INSN_LEN: usize = const_max!(
69 max_size!(RelocatedJmpImm, RelocatedCallImm, RelocatedCondJmpImm),
70 MAX_RELOCATED_MEM_RIP_INSN_LEN
71);
72
73pub const MAX_MAYBE_RELOCATED_INSN_LEN: usize = const_max!(MAX_RELOCATED_INSN_LEN, MAX_INSN_LEN);
75
76pub const SHORT_REL_JUMPER_LEN: usize = core::mem::size_of::<ShortRelJumper>();
78
79pub const SHORT_JUMPER_LEN: usize = core::mem::size_of::<ShortJumper>();
81
82pub const LONG_JUMPER_LEN: usize = core::mem::size_of::<LongJumper>();
84
85pub const MAX_JUMPER_LEN: usize = core::mem::size_of::<JumperUnion>();
87
88pub const MAX_TRAMPOLINE_LEN: usize = {
90 MAX_RELOCATED_INSNS_LEN + MAX_JUMPER_LEN
92};
93
94pub const MAX_RELOCATED_INSNS_AMOUNT: usize = {
96 MAX_JUMPER_LEN
98};
99
100pub const MAX_RELOCATED_INSNS_LEN: usize = {
102 MAX_RELOCATED_INSNS_AMOUNT * MAX_MAYBE_RELOCATED_INSN_LEN
105};
106
107pub type JumperBytes = ArrayVec<u8, MAX_JUMPER_LEN>;
109
110pub type RelocatedInsnBytes = ArrayVec<u8, MAX_RELOCATED_INSN_LEN>;
112
113pub type RelocatedInsnsBytes = ArrayVec<u8, MAX_RELOCATED_INSNS_LEN>;
115
116pub type TrampolineBytes = ArrayVec<u8, MAX_TRAMPOLINE_LEN>;
118
119type InsnBytes = ArrayVec<u8, MAX_INSN_LEN>;
121
122pub 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
136pub struct HookInfo {
138 jumper: JumperBytes,
139 relocated_fn: RelocatedFnStart,
140}
141impl HookInfo {
142 pub fn jumper(&self) -> &JumperBytes {
145 &self.jumper
146 }
147 pub fn trampoline_size(&self) -> usize {
149 self.relocated_fn.trampoline_size()
150 }
151 pub fn build_trampoline(&self, trampoline_runtime_addr: u64) -> TrampolineBytes {
156 self.relocated_fn.build_trampoline(trampoline_runtime_addr)
157 }
158}
159
160pub 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
173pub 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
182pub 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 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 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 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 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 return Ok(None);
289 };
290 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 decoded_insn.does_use_register(ZydisRegister::ZYDIS_REGISTER_RSP) {
310 return Err(HookError::UnsupportedRipRelativeInsn {
311 offset: decoded_insn_offset,
312 });
313 }
314
315 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 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 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 let prefix = RelocatedMemRipPrefix::new(tmp_reg, mem_rip_target_addr);
345 let postfix = RelocatedMemRipPostfix::new(tmp_reg);
346
347 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 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
485pub struct RelocatedFnStart {
487 pub relocated_insns_bytes: RelocatedInsnsBytes,
489 pub trampoline_jumper_target_offset: usize,
491 pub hooked_fn_runtime_addr: u64,
493}
494impl RelocatedFnStart {
495 pub fn trampoline_size(&self) -> usize {
497 self.relocated_insns_bytes.len() + LONG_JUMPER_LEN
498 }
499
500 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#[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#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
686pub enum JumperKind {
687 ShortRel,
689 Short,
691 Long,
693}
694impl JumperKind {
695 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 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 core::mem::size_of::<RelJmp8Bit>() as i8,
912 ),
913 jmp_not_taken_jmp: RelJmp8Bit::new(
914 ZydisMnemonic::ZYDIS_MNEMONIC_JMP,
915 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 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}