wraith/manipulation/inline_hook/hook/
hotpatch.rs1use crate::error::{Result, WraithError};
8use crate::util::memory::ProtectionGuard;
9use crate::manipulation::inline_hook::arch::Architecture;
10use crate::manipulation::inline_hook::guard::HookGuard;
11use crate::manipulation::inline_hook::trampoline::ExecutableMemory;
12use super::Hook;
13use core::marker::PhantomData;
14
15const PAGE_EXECUTE_READWRITE: u32 = 0x40;
16
17pub struct HotPatchHook<A: Architecture> {
22 target: usize,
23 detour: usize,
24 _arch: PhantomData<A>,
25}
26
27impl<A: Architecture> HotPatchHook<A> {
28 pub fn new(target: usize, detour: usize) -> Self {
30 Self {
31 target,
32 detour,
33 _arch: PhantomData,
34 }
35 }
36
37 pub fn is_patchable(target: usize) -> bool {
43 let pre_bytes = unsafe {
45 core::slice::from_raw_parts((target - 5) as *const u8, 5)
46 };
47 let entry_bytes = unsafe {
48 core::slice::from_raw_parts(target as *const u8, 2)
49 };
50
51 let has_padding = pre_bytes.iter().all(|&b| b == 0xCC || b == 0x90);
53
54 let has_nop_entry = entry_bytes == [0x8B, 0xFF] || entry_bytes == [0x66, 0x90] || entry_bytes == [0x89, 0xFF]; has_padding && has_nop_entry
60 }
61
62 pub fn install(self) -> Result<HookGuard<A>> {
64 if !Self::is_patchable(self.target) {
66 return Err(WraithError::HookDetectionFailed {
67 function: format!("{:#x}", self.target),
68 reason: "function is not hot-patchable".into(),
69 });
70 }
71
72 let original_bytes = unsafe {
74 let ptr = (self.target - 5) as *const u8;
75 core::slice::from_raw_parts(ptr, 7).to_vec()
76 };
77
78 let mut trampoline = ExecutableMemory::allocate_near(self.target, 32)?;
81
82 let entry_bytes = &original_bytes[5..7];
84 let mut trampoline_code = Vec::with_capacity(16);
85 trampoline_code.extend_from_slice(entry_bytes);
86
87 let continuation = self.target + 2;
89 let trampoline_jmp_loc = trampoline.base() + trampoline_code.len();
90
91 if let Some(jmp_bytes) = A::encode_jmp_rel(trampoline_jmp_loc, continuation) {
92 trampoline_code.extend_from_slice(&jmp_bytes);
93 } else {
94 let jmp_bytes = A::encode_jmp_abs(continuation);
95 trampoline_code.extend_from_slice(&jmp_bytes);
96 }
97
98 trampoline.write(&trampoline_code)?;
99 trampoline.flush_icache()?;
100
101 let long_jmp_addr = self.target - 5;
106
107 {
109 let _guard = ProtectionGuard::new(
110 long_jmp_addr,
111 7,
112 PAGE_EXECUTE_READWRITE,
113 )?;
114
115 let long_jmp = A::encode_jmp_rel(long_jmp_addr, self.detour)
117 .or_else(|| Some(A::encode_jmp_abs(self.detour)))
118 .unwrap();
119
120 unsafe {
122 core::ptr::copy_nonoverlapping(
123 long_jmp.as_ptr(),
124 long_jmp_addr as *mut u8,
125 5.min(long_jmp.len()),
126 );
127 }
128
129 unsafe {
133 let short_jmp: u16 = 0xF9EB; core::ptr::write_volatile(self.target as *mut u16, short_jmp);
135 }
136 }
137
138 flush_icache(long_jmp_addr, 7)?;
140
141 Ok(HookGuard::new(
143 long_jmp_addr,
144 self.detour,
145 original_bytes,
146 Some(trampoline),
147 ))
148 }
149}
150
151impl<A: Architecture> Hook for HotPatchHook<A> {
152 type Guard = HookGuard<A>;
153
154 fn install(self) -> Result<Self::Guard> {
155 HotPatchHook::install(self)
156 }
157
158 fn target(&self) -> usize {
159 self.target
160 }
161
162 fn detour(&self) -> usize {
163 self.detour
164 }
165}
166
167pub fn is_hot_patchable(target: usize) -> bool {
169 let pre_bytes = unsafe {
171 core::slice::from_raw_parts((target - 5) as *const u8, 5)
172 };
173 let entry_bytes = unsafe {
174 core::slice::from_raw_parts(target as *const u8, 2)
175 };
176
177 let has_padding = pre_bytes.iter().all(|&b| b == 0xCC || b == 0x90);
178 let has_nop_entry = entry_bytes == [0x8B, 0xFF]
179 || entry_bytes == [0x66, 0x90]
180 || entry_bytes == [0x89, 0xFF];
181
182 has_padding && has_nop_entry
183}
184
185pub fn hotpatch<A: Architecture>(target: usize, detour: usize) -> Result<HookGuard<A>> {
187 HotPatchHook::<A>::new(target, detour).install()
188}
189
190fn flush_icache(address: usize, size: usize) -> Result<()> {
191 let result = unsafe {
192 FlushInstructionCache(
193 GetCurrentProcess(),
194 address as *const _,
195 size,
196 )
197 };
198
199 if result == 0 {
200 Err(WraithError::from_last_error("FlushInstructionCache"))
201 } else {
202 Ok(())
203 }
204}
205
206#[link(name = "kernel32")]
207extern "system" {
208 fn FlushInstructionCache(
209 hProcess: *mut core::ffi::c_void,
210 lpBaseAddress: *const core::ffi::c_void,
211 dwSize: usize,
212 ) -> i32;
213
214 fn GetCurrentProcess() -> *mut core::ffi::c_void;
215}