wraith/manipulation/inline_hook/arch/
x64.rs1#[cfg(all(not(feature = "std"), feature = "alloc"))]
4use alloc::vec::Vec;
5
6#[cfg(feature = "std")]
7use std::vec::Vec;
8
9use super::Architecture;
10use crate::manipulation::inline_hook::asm::{
11 iced_decoder::InstructionDecoder,
12 iced_relocator::InstructionRelocator,
13};
14
15pub struct X64;
17
18impl Architecture for X64 {
19 const JMP_REL_SIZE: usize = 5;
21
22 const JMP_ABS_SIZE: usize = 14;
24
25 const PTR_SIZE: usize = 8;
26 const CODE_ALIGNMENT: usize = 16;
27
28 const MIN_HOOK_SIZE: usize = 5;
30
31 fn encode_jmp_rel(source: usize, target: usize) -> Option<Vec<u8>> {
32 let offset = (target as i64) - (source as i64) - 5;
34
35 if offset < i32::MIN as i64 || offset > i32::MAX as i64 {
37 return None;
38 }
39
40 let mut bytes = Vec::with_capacity(5);
41 bytes.push(0xE9); bytes.extend_from_slice(&(offset as i32).to_le_bytes());
43 Some(bytes)
44 }
45
46 fn encode_jmp_abs(target: usize) -> Vec<u8> {
47 let mut bytes = Vec::with_capacity(14);
50 bytes.extend_from_slice(&[0xFF, 0x25, 0x00, 0x00, 0x00, 0x00]);
51 bytes.extend_from_slice(&(target as u64).to_le_bytes());
52 bytes
53 }
54
55 fn encode_call_rel(source: usize, target: usize) -> Option<Vec<u8>> {
56 let offset = (target as i64) - (source as i64) - 5;
57
58 if offset < i32::MIN as i64 || offset > i32::MAX as i64 {
59 return None;
60 }
61
62 let mut bytes = Vec::with_capacity(5);
63 bytes.push(0xE8); bytes.extend_from_slice(&(offset as i32).to_le_bytes());
65 Some(bytes)
66 }
67
68 fn encode_nop_sled(size: usize) -> Vec<u8> {
69 let mut bytes = Vec::with_capacity(size);
71 let mut remaining = size;
72
73 while remaining > 0 {
74 match remaining {
75 1 => {
76 bytes.push(0x90); remaining -= 1;
78 }
79 2 => {
80 bytes.extend_from_slice(&[0x66, 0x90]); remaining -= 2;
82 }
83 3 => {
84 bytes.extend_from_slice(&[0x0F, 0x1F, 0x00]); remaining -= 3;
86 }
87 4 => {
88 bytes.extend_from_slice(&[0x0F, 0x1F, 0x40, 0x00]); remaining -= 4;
90 }
91 5 => {
92 bytes.extend_from_slice(&[0x0F, 0x1F, 0x44, 0x00, 0x00]); remaining -= 5;
94 }
95 6 => {
96 bytes.extend_from_slice(&[0x66, 0x0F, 0x1F, 0x44, 0x00, 0x00]); remaining -= 6;
98 }
99 7 => {
100 bytes.extend_from_slice(&[0x0F, 0x1F, 0x80, 0x00, 0x00, 0x00, 0x00]); remaining -= 7;
102 }
103 _ => {
104 bytes.extend_from_slice(&[0x0F, 0x1F, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00]);
106 remaining -= 8;
107 }
108 }
109 }
110
111 bytes
112 }
113
114 fn find_instruction_boundary(code: &[u8], required_size: usize) -> Option<usize> {
115 let decoder = InstructionDecoder::x64();
117 decoder.find_boundary(0, code, required_size)
118 }
119
120 fn relocate_instruction(
121 instruction: &[u8],
122 old_address: usize,
123 new_address: usize,
124 ) -> Option<Vec<u8>> {
125 if instruction.is_empty() {
126 return None;
127 }
128
129 let relocator = InstructionRelocator::x64();
131 let result = relocator.relocate_instruction(
132 instruction,
133 old_address as u64,
134 new_address as u64,
135 );
136
137 if result.success {
138 Some(result.bytes)
139 } else {
140 None
141 }
142 }
143
144 fn needs_relocation(instruction: &[u8]) -> bool {
145 if instruction.is_empty() {
146 return false;
147 }
148
149 crate::manipulation::inline_hook::asm::iced_relocator::instruction_needs_relocation(
151 instruction,
152 0,
153 )
154 }
155}
156
157#[cfg(test)]
158mod tests {
159 use super::*;
160
161 #[test]
162 fn test_encode_jmp_rel_near() {
163 let bytes = X64::encode_jmp_rel(0x1000, 0x1100).unwrap();
165 assert_eq!(bytes.len(), 5);
166 assert_eq!(bytes[0], 0xE9);
167 let offset = i32::from_le_bytes(bytes[1..5].try_into().unwrap());
169 assert_eq!(offset, 0xFB);
170 }
171
172 #[test]
173 fn test_encode_jmp_rel_far() {
174 let result = X64::encode_jmp_rel(0x0000_0000_0000_1000, 0x0000_0001_0000_0000);
176 assert!(result.is_none());
177 }
178
179 #[test]
180 fn test_encode_jmp_abs() {
181 let bytes = X64::encode_jmp_abs(0xDEADBEEF12345678);
182 assert_eq!(bytes.len(), 14);
183 assert_eq!(&bytes[0..6], &[0xFF, 0x25, 0x00, 0x00, 0x00, 0x00]);
184 let addr = u64::from_le_bytes(bytes[6..14].try_into().unwrap());
185 assert_eq!(addr, 0xDEADBEEF12345678);
186 }
187
188 #[test]
189 fn test_nop_sled() {
190 for size in 1..=16 {
191 let bytes = X64::encode_nop_sled(size);
192 assert_eq!(bytes.len(), size);
193 }
194 }
195
196 #[test]
197 fn test_find_instruction_boundary() {
198 let code = [0x55, 0x48, 0x89, 0xE5, 0x48, 0x83, 0xEC, 0x28];
200
201 let boundary = X64::find_instruction_boundary(&code, 5).unwrap();
203 assert!(boundary >= 5);
204 assert!(boundary <= 8);
205 }
206
207 #[test]
208 fn test_relocate_jmp_rel32() {
209 let jmp = [0xE9, 0x00, 0x01, 0x00, 0x00];
211
212 let result = X64::relocate_instruction(&jmp, 0x1000, 0x2000).unwrap();
214 assert_eq!(result.len(), 5);
215 assert_eq!(result[0], 0xE9);
216
217 let new_offset = i32::from_le_bytes(result[1..5].try_into().unwrap());
219 assert_eq!(new_offset, -0xF00);
220 }
221
222 #[test]
223 fn test_relocate_non_relative() {
224 let push = [0x55];
226 let result = X64::relocate_instruction(&push, 0x1000, 0x2000).unwrap();
227 assert_eq!(result, vec![0x55]);
228 }
229
230 #[test]
231 fn test_needs_relocation() {
232 assert!(X64::needs_relocation(&[0xE9, 0x00, 0x00, 0x00, 0x00]));
234
235 assert!(X64::needs_relocation(&[0xE8, 0x00, 0x00, 0x00, 0x00]));
237
238 assert!(!X64::needs_relocation(&[0x55]));
240
241 assert!(!X64::needs_relocation(&[0x90]));
243 }
244}