wraith/manipulation/inline_hook/hook/
hotpatch.rs1#[cfg(all(not(feature = "std"), feature = "alloc"))]
8use alloc::{format, string::String, vec::Vec};
9
10#[cfg(feature = "std")]
11use std::{format, string::String, vec::Vec};
12
13use crate::error::{Result, WraithError};
14use crate::util::memory::ProtectionGuard;
15use crate::manipulation::inline_hook::arch::Architecture;
16use crate::manipulation::inline_hook::guard::HookGuard;
17use crate::manipulation::inline_hook::trampoline::ExecutableMemory;
18use super::Hook;
19use core::marker::PhantomData;
20
21const PAGE_EXECUTE_READWRITE: u32 = 0x40;
22
23pub struct HotPatchHook<A: Architecture> {
28 target: usize,
29 detour: usize,
30 _arch: PhantomData<A>,
31}
32
33impl<A: Architecture> HotPatchHook<A> {
34 pub fn new(target: usize, detour: usize) -> Self {
36 Self {
37 target,
38 detour,
39 _arch: PhantomData,
40 }
41 }
42
43 pub fn is_patchable(target: usize) -> bool {
49 let pre_bytes = unsafe {
51 core::slice::from_raw_parts((target - 5) as *const u8, 5)
52 };
53 let entry_bytes = unsafe {
54 core::slice::from_raw_parts(target as *const u8, 2)
55 };
56
57 let has_padding = pre_bytes.iter().all(|&b| b == 0xCC || b == 0x90);
59
60 let has_nop_entry = entry_bytes == [0x8B, 0xFF] || entry_bytes == [0x66, 0x90] || entry_bytes == [0x89, 0xFF]; has_padding && has_nop_entry
66 }
67
68 pub fn install(self) -> Result<HookGuard<A>> {
70 if !Self::is_patchable(self.target) {
72 return Err(WraithError::HookDetectionFailed {
73 function: format!("{:#x}", self.target),
74 reason: "function is not hot-patchable".into(),
75 });
76 }
77
78 let original_bytes = unsafe {
80 let ptr = (self.target - 5) as *const u8;
81 core::slice::from_raw_parts(ptr, 7).to_vec()
82 };
83
84 let mut trampoline = ExecutableMemory::allocate_near(self.target, 32)?;
87
88 let entry_bytes = &original_bytes[5..7];
90 let mut trampoline_code = Vec::with_capacity(16);
91 trampoline_code.extend_from_slice(entry_bytes);
92
93 let continuation = self.target + 2;
95 let trampoline_jmp_loc = trampoline.base() + trampoline_code.len();
96
97 if let Some(jmp_bytes) = A::encode_jmp_rel(trampoline_jmp_loc, continuation) {
98 trampoline_code.extend_from_slice(&jmp_bytes);
99 } else {
100 let jmp_bytes = A::encode_jmp_abs(continuation);
101 trampoline_code.extend_from_slice(&jmp_bytes);
102 }
103
104 trampoline.write(&trampoline_code)?;
105 trampoline.flush_icache()?;
106
107 let long_jmp_addr = self.target - 5;
112
113 {
115 let _guard = ProtectionGuard::new(
116 long_jmp_addr,
117 7,
118 PAGE_EXECUTE_READWRITE,
119 )?;
120
121 let long_jmp = A::encode_jmp_rel(long_jmp_addr, self.detour)
123 .or_else(|| Some(A::encode_jmp_abs(self.detour)))
124 .unwrap();
125
126 unsafe {
128 core::ptr::copy_nonoverlapping(
129 long_jmp.as_ptr(),
130 long_jmp_addr as *mut u8,
131 5.min(long_jmp.len()),
132 );
133 }
134
135 unsafe {
139 let short_jmp: u16 = 0xF9EB; core::ptr::write_volatile(self.target as *mut u16, short_jmp);
141 }
142 }
143
144 flush_icache(long_jmp_addr, 7)?;
146
147 Ok(HookGuard::new(
149 long_jmp_addr,
150 self.detour,
151 original_bytes,
152 Some(trampoline),
153 ))
154 }
155}
156
157impl<A: Architecture> Hook for HotPatchHook<A> {
158 type Guard = HookGuard<A>;
159
160 fn install(self) -> Result<Self::Guard> {
161 HotPatchHook::install(self)
162 }
163
164 fn target(&self) -> usize {
165 self.target
166 }
167
168 fn detour(&self) -> usize {
169 self.detour
170 }
171}
172
173pub fn is_hot_patchable(target: usize) -> bool {
175 let pre_bytes = unsafe {
177 core::slice::from_raw_parts((target - 5) as *const u8, 5)
178 };
179 let entry_bytes = unsafe {
180 core::slice::from_raw_parts(target as *const u8, 2)
181 };
182
183 let has_padding = pre_bytes.iter().all(|&b| b == 0xCC || b == 0x90);
184 let has_nop_entry = entry_bytes == [0x8B, 0xFF]
185 || entry_bytes == [0x66, 0x90]
186 || entry_bytes == [0x89, 0xFF];
187
188 has_padding && has_nop_entry
189}
190
191pub fn hotpatch<A: Architecture>(target: usize, detour: usize) -> Result<HookGuard<A>> {
193 HotPatchHook::<A>::new(target, detour).install()
194}
195
196fn flush_icache(address: usize, size: usize) -> Result<()> {
197 let result = unsafe {
198 FlushInstructionCache(
199 GetCurrentProcess(),
200 address as *const _,
201 size,
202 )
203 };
204
205 if result == 0 {
206 Err(WraithError::from_last_error("FlushInstructionCache"))
207 } else {
208 Ok(())
209 }
210}
211
212#[link(name = "kernel32")]
213extern "system" {
214 fn FlushInstructionCache(
215 hProcess: *mut core::ffi::c_void,
216 lpBaseAddress: *const core::ffi::c_void,
217 dwSize: usize,
218 ) -> i32;
219
220 fn GetCurrentProcess() -> *mut core::ffi::c_void;
221}