├── .github └── workflows │ └── rust.yml ├── .gitignore ├── .travis.yml ├── Cargo.toml ├── LICENSE ├── README.md └── src ├── err.rs ├── lib.rs ├── x64.rs ├── x64 ├── fixed_memory_unix.rs ├── fixed_memory_win.rs ├── move_inst.rs └── tests.rs └── x86.rs /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: 4 | push: 5 | branches: [ "master" ] 6 | pull_request: 7 | branches: [ "master" ] 8 | workflow_dispatch: 9 | 10 | env: 11 | CARGO_TERM_COLOR: always 12 | 13 | jobs: 14 | linux-64-test: 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v3 19 | - name: Build 20 | run: cargo build -r --verbose 21 | - name: Run tests 22 | run: cargo test -r --verbose -- --test-threads=1 23 | 24 | win-64-test: 25 | runs-on: windows-latest 26 | 27 | steps: 28 | - uses: actions/checkout@v3 29 | - name: Build 30 | run: cargo build -r --verbose 31 | - name: Run tests 32 | run: cargo test -r --verbose -- --test-threads=1 33 | 34 | linux-32-test: 35 | runs-on: ubuntu-latest 36 | 37 | steps: 38 | - uses: actions/checkout@v3 39 | - name: Setup 32-bit env 40 | run: rustup target add i686-unknown-linux-musl 41 | - name: Build 42 | run: cargo build --target i686-unknown-linux-musl -r --verbose 43 | - name: Run tests 44 | run: cargo test --target i686-unknown-linux-musl -r --verbose -- --test-threads=1 45 | 46 | win-32-test: 47 | runs-on: windows-latest 48 | 49 | steps: 50 | - uses: actions/checkout@v3 51 | - name: Setup 32-bit env 52 | run: rustup target add i686-pc-windows-msvc 53 | - name: Build 54 | run: cargo build --target i686-pc-windows-msvc -r --verbose 55 | - name: Run tests 56 | run: cargo test --target i686-pc-windows-msvc -r --verbose -- --test-threads=1 57 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | Cargo.lock 4 | /.idea/ 5 | /.vscode/ -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | cache: cargo 3 | 4 | matrix: 5 | include: 6 | - rust: stable 7 | env: TARGET=i686-pc-windows-msvc 8 | os: windows 9 | - rust: stable 10 | env: TARGET=i686-unknown-linux-gnu 11 | os: linux 12 | dist: trusty 13 | addons: 14 | apt: 15 | packages: 16 | - libc6-dev-i386 17 | - libc6:i386 18 | - libstdc++6:i386 19 | - g++-multilib 20 | - rust: stable 21 | env: TARGET=x86_64-pc-windows-msvc 22 | os: windows 23 | - rust: stable 24 | env: TARGET=x86_64-unknown-linux-gnu 25 | os: linux 26 | dist: trusty 27 | install: rustup target add ${TARGET} 28 | script: 29 | - cargo build --verbose --target=${TARGET} 30 | - cargo test --verbose --target=${TARGET} -- --test-threads=1 31 | branches: 32 | only: 33 | - master -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ilhook" 3 | description = "A library that provides methods to inline hook binary codes in x86 and x86_64 architecture" 4 | version = "2.1.1" 5 | authors = ["regomne "] 6 | edition = "2021" 7 | license = "MIT" 8 | readme = "README.md" 9 | repository = "https://github.com/regomne/ilhook-rs" 10 | keywords = ["hook", "assemble", "disassemble"] 11 | categories = ["hardware-support"] 12 | 13 | [dependencies] 14 | iced-x86 = { version = "1.20", default-features = false, features = ["std", "decoder", "block_encoder", "instr_info"] } 15 | thiserror = "1.0" 16 | bitflags = "2.0" 17 | 18 | [target.'cfg(unix)'.dependencies] 19 | libc = "0.2" 20 | regex = "1" 21 | lazy_static = "1" 22 | 23 | [target.'cfg(windows)'.dependencies] 24 | windows-sys = { version = "0.42.0", features = ["Win32_Foundation", "Win32_System_Memory"] } 25 | 26 | [profile.release] 27 | lto = true 28 | codegen-units = 1 29 | opt-level = 3 -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 regomne 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ilhook 2 | ---- 3 | 4 | [![](http://meritbadge.herokuapp.com/ilhook)](https://crates.io/crates/ilhook) 5 | 6 | This crate provides methods to inline hook binary codes of `x86` and `x64` instruction sets. 7 | 8 | HOOK is a mechanism that intercepts function calls and handles them by user-defined code. 9 | 10 | # Installation 11 | 12 | This crate works with Cargo and is on 13 | [crates.io](https://crates.io/crates/ilhook). Add it to your `Cargo.toml` 14 | like so: 15 | 16 | ```toml 17 | [dependencies] 18 | ilhook = "2" 19 | ``` 20 | 21 | # Hook Types 22 | 23 | Ilhook supports 4 types of hooking. 24 | 25 | ## Jmp-back hook 26 | 27 | This type is used when you want to get some information, or modify some values 28 | (parameters, stack vars, heap vars, etc.) at the specified timing. 29 | 30 | Assume we have a C++ function: 31 | 32 | ```cpp 33 | void check_serial_number(std::string& sn){ 34 | uint32_t machine_hash = get_machine_hash(); 35 | uint32_t sn_hash = calc_hash(sn); 36 | 37 | // we want to modify the result of this comparison. 38 | if (sn_hash == machine_hash) { 39 | // success 40 | } 41 | // fail 42 | } 43 | ``` 44 | 45 | And it compiles to the asm code: 46 | 47 | ```asm 48 | 0x401054 call get_machine_hash ;get_machine_hash() 49 | 0x401059 mov ebx, eax 50 | 51 | ; ... 52 | 53 | 0x401070 lea eax, sn 54 | 0x401076 push eax 55 | 0x401077 call calc_hash ;calc_hash(sn) 56 | 0x40107C add esp, 4 57 | 0x40107F cmp eax, ebx ;we want to modify the eax here! 58 | 0x401081 jnz _check_fail 59 | 60 | ; check_success 61 | ``` 62 | 63 | Now let's start: 64 | 65 | ```rust 66 | use ilhook::x86::{Hooker, HookType, Registers, CallbackOption, HookFlags}; 67 | 68 | unsafe extern "C" fn on_check_sn( 69 | reg: *mut Registers, 70 | _: usize 71 | ) { 72 | println!("m_hash: {}, sn_hash: {}", (*reg).ebx, (*reg).eax); 73 | (*reg).eax = (*reg).ebx; //we modify the sn_hash! 74 | } 75 | 76 | let hooker = Hooker::new( 77 | 0x40107F, 78 | HookType::JmpBack(on_check_sn), 79 | CallbackOption::None, 80 | 0, 81 | HookFlags::empty(), 82 | ); 83 | hooker.hook().unwrap(); 84 | ``` 85 | 86 | Then `check_serial_number` will always go to the successful path. 87 | 88 | ## Function hook 89 | 90 | This type is used when you want to replace a function with your customized 91 | function. Note that you should only hook at the beginning of a function. 92 | 93 | Assume we have a function: 94 | 95 | ```rust 96 | fn foo(x: u64) -> u64 { 97 | x * x 98 | } 99 | 100 | assert_eq!(foo(5), 25); 101 | ``` 102 | 103 | And you want to let it return `x*x+3`, which means foo(5)==28. 104 | 105 | Now let's hook: 106 | 107 | ```rust 108 | use ilhook::x64::{Hooker, HookType, Registers, CallbackOption, HookFlags}; 109 | 110 | unsafe extern "win64" fn new_foo( 111 | reg: *mut Registers, 112 | _ :usize, 113 | _ :usize 114 | ) -> usize { 115 | let x = (&*reg).rdi as usize; 116 | x * x + 3 117 | } 118 | 119 | let hooker = Hooker::new( 120 | foo as usize, 121 | HookType::Retn(new_foo), 122 | CallbackOption::None, 123 | 0, 124 | HookFlags::empty(), 125 | ); 126 | unsafe { hooker.hook().unwrap() }; 127 | assert_eq!(foo(5), 28); 128 | ``` 129 | 130 | ## Jmp-addr hook 131 | 132 | This type is used when you want to change the original run path to any other you wanted. 133 | 134 | The first element of the enum `HookType::JmpToAddr` indicates where you want the EIP jump 135 | to after the callback routine returns. 136 | 137 | ## Jmp-ret hook 138 | 139 | This type is used when you want to change the original run path to any other you wanted, and 140 | the destination address may change by the input arguments. 141 | 142 | The EIP will jump to the value the callback routine returns. 143 | 144 | # Notes 145 | 146 | This crate is not thread-safe if you don't specify `HookFlags::NOT_MODIFY_MEMORY_PROTECT`. Of course, 147 | you need to modify memory protection of the destination address by yourself if you specify that. 148 | 149 | As rust's test run parrallelly, it may crash if not specify `--test-threads=1`. 150 | -------------------------------------------------------------------------------- /src/err.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | use thiserror::Error; 3 | 4 | /// Hook errors. 5 | #[derive(Error, Debug)] 6 | pub enum HookError { 7 | /// Invalid parameter 8 | #[error("invalid parameter")] 9 | InvalidParameter, 10 | 11 | /// Error occurs when modifying the memory protect 12 | #[error("memory protect error, code:{0}")] 13 | MemoryProtect(u32), 14 | 15 | /// Can't allocate memory 16 | #[error("memory allocation error, code:{0}")] 17 | MemoryAllocation(u32), 18 | 19 | /// Can't allocate a memory block between +/-2GB of hooking address 20 | #[error("searching memory failed")] 21 | MemorySearching, 22 | 23 | /// Can't get memory layout from /proc/${PID}/maps (only in linux) 24 | #[error("memory layout format error")] 25 | MemoryLayoutFormat, 26 | 27 | /// Can't disassemble in the specified address 28 | #[error("disassemble error")] 29 | Disassemble, 30 | 31 | /// Can't move code 32 | #[error("moving code error")] 33 | MoveCode, 34 | 35 | /// Not supported moving code 36 | #[error("not supported moving code")] 37 | MovingCodeNotSupported, 38 | 39 | /// The pre-hook callback failed 40 | #[error("pre hook failed")] 41 | PreHook, 42 | 43 | /// Some io error 44 | #[error("io error")] 45 | Io(#[from] io::Error), 46 | 47 | /// Unknown error 48 | #[error("unknown error")] 49 | Unknown, 50 | } 51 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | /*! 2 | This crate provides methods to inline hook binary codes of `x86` and `x64` instruction sets. 3 | 4 | HOOK is a mechanism that intercepts function calls and handles them by user-defined code. 5 | 6 | # Installation 7 | 8 | This crate works with Cargo and is on 9 | [crates.io](https://crates.io/crates/ilhook). Add it to your `Cargo.toml` 10 | like so: 11 | 12 | ```toml 13 | [dependencies] 14 | ilhook = "2" 15 | ``` 16 | 17 | # Hook Types 18 | 19 | Ilhook supports 4 types of hooking. 20 | 21 | ## Jmp-back hook 22 | 23 | This type is used when you want to get some information, or modify some values 24 | (parameters, stack vars, heap vars, etc.) at the specified timing. 25 | 26 | Assume we have a C++ function: 27 | 28 | ```cpp 29 | void check_serial_number(std::string& sn){ 30 | uint32_t machine_hash = get_machine_hash(); 31 | uint32_t sn_hash = calc_hash(sn); 32 | 33 | // we want to modify the result of this comparison. 34 | if (sn_hash == machine_hash) { 35 | // success 36 | } 37 | // fail 38 | } 39 | ``` 40 | 41 | And it compiles to the asm code: 42 | 43 | ```asm 44 | 0x401054 call get_machine_hash ;get_machine_hash() 45 | 0x401059 mov ebx, eax 46 | 47 | ; ... 48 | 49 | 0x401070 lea eax, sn 50 | 0x401076 push eax 51 | 0x401077 call calc_hash ;calc_hash(sn) 52 | 0x40107C add esp, 4 53 | 0x40107F cmp eax, ebx ;we want to modify the eax here! 54 | 0x401081 jnz _check_fail 55 | 56 | ; check_success 57 | ``` 58 | 59 | Now let's start: 60 | 61 | ```rust 62 | # #[cfg(target_arch = "x86")] 63 | use ilhook::x86::{Hooker, HookType, Registers, CallbackOption, HookFlags}; 64 | 65 | # #[cfg(target_arch = "x86")] 66 | unsafe extern "C" fn on_check_sn(reg:*mut Registers, _:usize){ 67 | println!("machine_hash: {}, sn_hash: {}", (*reg).ebx, (*reg).eax); 68 | (*reg).eax = (*reg).ebx; //we modify the sn_hash! 69 | } 70 | 71 | # #[cfg(target_arch = "x86")] 72 | let hooker=Hooker::new(0x40107F, HookType::JmpBack(on_check_sn), CallbackOption::None, 0, HookFlags::empty()); 73 | //hooker.hook().unwrap(); //commented as hooking is not supported in doc tests 74 | ``` 75 | 76 | Then `check_serial_number` will always go to the successful path. 77 | 78 | ## Function hook 79 | 80 | This type is used when you want to replace a function with your customized 81 | function. Note that you should only hook at the beginning of a function. 82 | 83 | Assume we have a function: 84 | 85 | ```rust 86 | fn foo(x: u64) -> u64 { 87 | x * x 88 | } 89 | 90 | assert_eq!(foo(5), 25); 91 | ``` 92 | 93 | And you want to let it return `x*x+3`, which means foo(5)==28. 94 | 95 | Now let's hook: 96 | 97 | ```rust 98 | # #[cfg(target_arch = "x86_64")] 99 | use ilhook::x64::{Hooker, HookType, Registers, CallbackOption, HookFlags}; 100 | # #[cfg(target_arch = "x86_64")] 101 | # fn foo(x: u64) -> u64 { 102 | # x * x 103 | # } 104 | # #[cfg(target_arch = "x86_64")] 105 | unsafe extern "win64" fn new_foo(reg:*mut Registers, _:usize, _:usize)->usize{ 106 | let x = (&*reg).rdi as usize; 107 | x*x+3 108 | } 109 | 110 | # #[cfg(target_arch = "x86_64")] 111 | let hooker=Hooker::new(foo as usize, HookType::Retn(new_foo), CallbackOption::None, 0, HookFlags::empty()); 112 | unsafe{hooker.hook().unwrap()}; 113 | //assert_eq!(foo(5), 28); //commented as hooking is not supported in doc tests 114 | ``` 115 | 116 | ## Jmp-addr hook 117 | 118 | This type is used when you want to change the original run path to any other you wanted. 119 | 120 | The first element of the enum `HookType::JmpToAddr` indicates where you want the EIP jump 121 | to after the callback routine returns. 122 | 123 | ## Jmp-ret hook 124 | 125 | This type is used when you want to change the original run path to any other you wanted, and 126 | the destination address may change by the input arguments. 127 | 128 | The EIP will jump to the value the callback routine returns. 129 | 130 | # Notes 131 | 132 | This crate is not thread-safe if you don't specify `HookFlags::NOT_MODIFY_MEMORY_PROTECT`. Of course, 133 | you need to modify memory protection of the destination address by yourself if you specify that. 134 | 135 | As rust's test run parrallelly, it may crash if not specify `--test-threads=1`. 136 | 137 | */ 138 | 139 | #![warn(missing_docs)] 140 | 141 | mod err; 142 | 143 | pub use err::HookError; 144 | 145 | /// The x86 hooker 146 | pub mod x86; 147 | 148 | /// The x64 hooker 149 | pub mod x64; 150 | -------------------------------------------------------------------------------- /src/x64.rs: -------------------------------------------------------------------------------- 1 | mod move_inst; 2 | #[cfg(target_arch = "x86_64")] 3 | mod tests; 4 | 5 | use std::io::{Cursor, Seek, SeekFrom, Write}; 6 | use std::slice; 7 | 8 | #[cfg(windows)] 9 | use core::ffi::c_void; 10 | #[cfg(unix)] 11 | use libc::{__errno_location, c_void, mprotect, sysconf}; 12 | #[cfg(windows)] 13 | use windows_sys::Win32::Foundation::GetLastError; 14 | #[cfg(windows)] 15 | use windows_sys::Win32::System::Memory::VirtualProtect; 16 | 17 | use bitflags::bitflags; 18 | use iced_x86::{Decoder, DecoderOptions, Instruction}; 19 | 20 | use crate::HookError; 21 | use move_inst::move_code_to_addr; 22 | 23 | const MAX_INST_LEN: usize = 15; 24 | 25 | const TRAMPOLINE_MAX_LEN: usize = 1024; 26 | 27 | /// This is the routine used in a `jmp-back hook`, which means the RIP will jump back to the 28 | /// original position after the routine has finished running. 29 | /// 30 | /// # Parameters 31 | /// 32 | /// * `regs` - The registers. 33 | /// * `user_data` - User data that was previously passed to [`Hooker::new`]. 34 | pub type JmpBackRoutine = unsafe extern "win64" fn(regs: *mut Registers, user_data: usize); 35 | 36 | /// This is the routine used in a `function hook`, which means the routine will replace the 37 | /// original function and the RIP will `retn` directly instead of jumping back. 38 | /// Note that the address being hooked must be the start of a function. 39 | /// 40 | /// # Parameters 41 | /// 42 | /// * `regs` - The registers. 43 | /// * `ori_func_ptr` - The original function pointer. Call this after converting it to the original function type. 44 | /// * `user_data` - User data that was previously passed to [`Hooker::new`]. 45 | /// 46 | /// # Return value 47 | /// 48 | /// Returns the new return value of the replaced function. 49 | pub type RetnRoutine = 50 | unsafe extern "win64" fn(regs: *mut Registers, ori_func_ptr: usize, user_data: usize) -> usize; 51 | 52 | /// This is the routine used in a `jmp-addr hook`, which means the RIP will jump to the specified 53 | /// address after the routine has finished running. 54 | /// 55 | /// # Parameters 56 | /// 57 | /// * `regs` - The registers. 58 | /// * `ori_func_ptr` - The original function pointer. Call this after converting it to the original function type. 59 | /// * `user_data` - User data that was previously passed to [`Hooker::new`]. 60 | pub type JmpToAddrRoutine = 61 | unsafe extern "win64" fn(regs: *mut Registers, ori_func_ptr: usize, user_data: usize); 62 | 63 | /// This is the routine used in a `jmp-ret hook`, which means the RIP will jump to the return 64 | /// value of the routine. 65 | /// 66 | /// # Parameters 67 | /// 68 | /// * `regs` - The registers. 69 | /// * `ori_func_ptr` - The original function pointer. Call this after converting it to the original function type. 70 | /// * `user_data` - User data that was previously passed to [`Hooker::new`]. 71 | /// 72 | /// # Return value 73 | /// 74 | /// Returns the address you want to jump to. 75 | pub type JmpToRetRoutine = 76 | unsafe extern "win64" fn(regs: *mut Registers, ori_func_ptr: usize, user_data: usize) -> usize; 77 | 78 | /// The hooking type. 79 | pub enum HookType { 80 | /// Used in a jmp-back hook 81 | JmpBack(JmpBackRoutine), 82 | 83 | /// Used in a function hook 84 | Retn(RetnRoutine), 85 | 86 | /// Used in a jmp-addr hook. The first element is the destination address 87 | JmpToAddr(usize, JmpToAddrRoutine), 88 | 89 | /// Used in a jmp-ret hook. 90 | JmpToRet(JmpToRetRoutine), 91 | } 92 | 93 | /// Jmp type that the `jmp` instruction use. 94 | pub enum JmpType { 95 | /// Direct long jump. `jmp` instruction use 5 bytes, but may fail as memory allocation near the 2GB space may fail. 96 | /// `jmp 0xXXXXXXXX` 97 | Direct, 98 | 99 | /// Mov rax and jump. Use 11 bytes. 100 | /// `mov rax, 0xXXXXXXXXXXXXXXXX; jmp rax;` 101 | MovJmp, 102 | 103 | /// Use 2 jmp instructions to jump. You have to specify the position of the second jmp. 104 | /// `jmp 0xXXXXXXXX; some codes; mov rax, 0xXXXXXXXX; jmp rax;` 105 | TrampolineJmp(usize), 106 | } 107 | 108 | /// The common registers. 109 | #[repr(C)] 110 | #[derive(Debug, Clone)] 111 | pub struct Registers { 112 | /// The xmm0 register 113 | pub xmm0: u128, 114 | /// The xmm1 register 115 | pub xmm1: u128, 116 | /// The xmm2 register 117 | pub xmm2: u128, 118 | /// The xmm3 register 119 | pub xmm3: u128, 120 | /// The r15 register 121 | pub r15: u64, 122 | /// The r14 register 123 | pub r14: u64, 124 | /// The r13 register 125 | pub r13: u64, 126 | /// The r12 register 127 | pub r12: u64, 128 | /// The r11 register 129 | pub r11: u64, 130 | /// The r10 register 131 | pub r10: u64, 132 | /// The r9 register 133 | pub r9: u64, 134 | /// The r8 register 135 | pub r8: u64, 136 | /// The rbp register 137 | pub rbp: u64, 138 | /// The rdi register 139 | pub rdi: u64, 140 | /// The rsi register 141 | pub rsi: u64, 142 | /// The rdx register 143 | pub rdx: u64, 144 | /// The rcx register 145 | pub rcx: u64, 146 | /// The rbx register 147 | pub rbx: u64, 148 | /// The rsp register 149 | pub rsp: u64, 150 | /// The flags register 151 | pub rflags: u64, 152 | /// Unused var 153 | pub _no_use: u64, 154 | /// The rax register 155 | pub rax: u64, 156 | } 157 | 158 | impl Registers { 159 | /// Get the value by index. 160 | /// 161 | /// # Parameters 162 | /// 163 | /// * cnt - The index of the arguments. 164 | /// 165 | /// # Safety 166 | /// 167 | /// Process may crash if register `rsp` does not point to a valid stack. 168 | #[must_use] 169 | pub unsafe fn get_stack(&self, cnt: usize) -> u64 { 170 | *((self.rsp as usize + cnt * 8) as *mut u64) 171 | } 172 | } 173 | 174 | /// The trait which is called before and after the modifying of the `jmp` instruction. 175 | /// Usually is used to suspend and resume all other threads, to avoid instruction colliding. 176 | pub trait ThreadCallback { 177 | /// the callback before modifying `jmp` instruction, should return true if success. 178 | fn pre(&self) -> bool; 179 | /// the callback after modifying `jmp` instruction 180 | fn post(&self); 181 | } 182 | 183 | /// Option for thread callback 184 | pub enum CallbackOption { 185 | /// Valid callback 186 | Some(Box), 187 | /// No callback 188 | None, 189 | } 190 | 191 | bitflags! { 192 | /// Hook flags 193 | pub struct HookFlags:u32 { 194 | /// If set, will not modify the memory protection of the destination address, so that 195 | /// the `hook` function could be ALMOST thread-safe. 196 | const NOT_MODIFY_MEMORY_PROTECT = 0x1; 197 | } 198 | } 199 | 200 | /// The entry struct in ilhook. 201 | /// Please read the main doc to view usage. 202 | pub struct Hooker { 203 | addr: usize, 204 | hook_type: HookType, 205 | thread_cb: CallbackOption, 206 | user_data: usize, 207 | jmp_inst_size: usize, 208 | flags: HookFlags, 209 | } 210 | 211 | /// The hook result returned by `Hooker::hook`. 212 | pub struct HookPoint { 213 | addr: usize, 214 | #[allow(dead_code)] // we only use the drop trait of the trampoline 215 | trampoline: Box<[u8; TRAMPOLINE_MAX_LEN]>, 216 | trampoline_prot: u32, 217 | origin: Vec, 218 | thread_cb: CallbackOption, 219 | jmp_inst_size: usize, 220 | flags: HookFlags, 221 | } 222 | 223 | #[cfg(not(target_arch = "x86_64"))] 224 | fn env_lock() { 225 | panic!("This crate should only be used in arch x86_32!") 226 | } 227 | #[cfg(target_arch = "x86_64")] 228 | fn env_lock() {} 229 | 230 | impl Hooker { 231 | /// Create a new Hooker. 232 | /// 233 | /// # Parameters 234 | /// 235 | /// * `addr` - The being-hooked address. 236 | /// * `hook_type` - The hook type and callback routine. 237 | /// * `thread_cb` - The callbacks before and after hooking. 238 | /// * `flags` - Hook flags 239 | #[must_use] 240 | pub fn new( 241 | addr: usize, 242 | hook_type: HookType, 243 | thread_cb: CallbackOption, 244 | user_data: usize, 245 | flags: HookFlags, 246 | ) -> Self { 247 | env_lock(); 248 | Self { 249 | addr, 250 | hook_type, 251 | thread_cb, 252 | user_data, 253 | jmp_inst_size: 14, 254 | flags, 255 | } 256 | } 257 | 258 | /// Consumes self and execute hooking. Return the `HookPoint`. 259 | /// 260 | /// # Safety 261 | /// 262 | /// Process may crash (instead of panic!) if: 263 | /// 264 | /// 1. addr is not an accessible memory address, or is not long enough. 265 | /// 2. addr points to an incorrect position. (At the middle of an instruction, or where after it other instructions may jump to) 266 | /// 3. Set `NOT_MODIFY_MEMORY_PROTECT` where it should not be set. 267 | /// 4. hook or unhook from 2 or more threads at the same time without `HookFlags::NOT_MODIFY_MEMORY_PROTECT`. Because of memory protection colliding. 268 | /// 5. Other unpredictable errors. 269 | pub unsafe fn hook(self) -> Result { 270 | let (moving_insts, origin) = get_moving_insts(self.addr, self.jmp_inst_size)?; 271 | let trampoline = 272 | generate_trampoline(&self, moving_insts, origin.len() as u8, self.user_data)?; 273 | let trampoline_prot = modify_mem_protect(trampoline.as_ptr() as usize, trampoline.len())?; 274 | if !self.flags.contains(HookFlags::NOT_MODIFY_MEMORY_PROTECT) { 275 | let old_prot = modify_mem_protect(self.addr, self.jmp_inst_size)?; 276 | let ret = modify_jmp_with_thread_cb(&self, trampoline.as_ptr() as usize); 277 | recover_mem_protect(self.addr, self.jmp_inst_size, old_prot); 278 | ret?; 279 | } else { 280 | modify_jmp_with_thread_cb(&self, trampoline.as_ptr() as usize)?; 281 | } 282 | Ok(HookPoint { 283 | addr: self.addr, 284 | trampoline, 285 | trampoline_prot, 286 | origin, 287 | thread_cb: self.thread_cb, 288 | jmp_inst_size: self.jmp_inst_size, 289 | flags: self.flags, 290 | }) 291 | } 292 | } 293 | 294 | impl HookPoint { 295 | /// Consume self and unhook the address. 296 | pub unsafe fn unhook(self) -> Result<(), HookError> { 297 | self.unhook_by_ref() 298 | } 299 | 300 | fn unhook_by_ref(&self) -> Result<(), HookError> { 301 | let ret: Result<(), HookError>; 302 | if !self.flags.contains(HookFlags::NOT_MODIFY_MEMORY_PROTECT) { 303 | let old_prot = modify_mem_protect(self.addr, self.jmp_inst_size)?; 304 | ret = recover_jmp_with_thread_cb(self); 305 | recover_mem_protect(self.addr, self.jmp_inst_size, old_prot); 306 | } else { 307 | ret = recover_jmp_with_thread_cb(self) 308 | } 309 | recover_mem_protect( 310 | self.trampoline.as_ptr() as usize, 311 | self.trampoline.len(), 312 | self.trampoline_prot, 313 | ); 314 | ret 315 | } 316 | } 317 | 318 | // When the HookPoint drops, it should unhook automatically. 319 | impl Drop for HookPoint { 320 | fn drop(&mut self) { 321 | self.unhook_by_ref().unwrap_or_default(); 322 | } 323 | } 324 | 325 | fn get_moving_insts( 326 | addr: usize, 327 | min_bytes: usize, 328 | ) -> Result<(Vec, Vec), HookError> { 329 | let code_slice = unsafe { slice::from_raw_parts(addr as *const u8, MAX_INST_LEN * 2) }; 330 | let mut decoder = Decoder::new(64, code_slice, DecoderOptions::NONE); 331 | decoder.set_ip(addr as u64); 332 | 333 | let mut total_bytes = 0; 334 | let mut ori_insts: Vec = vec![]; 335 | for inst in &mut decoder { 336 | if inst.is_invalid() { 337 | return Err(HookError::Disassemble); 338 | } 339 | ori_insts.push(inst); 340 | total_bytes += inst.len(); 341 | if total_bytes >= min_bytes { 342 | break; 343 | } 344 | } 345 | 346 | Ok((ori_insts, code_slice[0..decoder.position()].into())) 347 | } 348 | 349 | fn write_trampoline_prolog(buf: &mut impl Write) -> Result { 350 | // push rsp 351 | // pushfq 352 | // test rsp,8 353 | // je _stack_aligned_16 354 | // ; stack not aligned to 16 355 | // push rax 356 | // sub rsp,0x10 357 | // mov rax, [rsp+0x20] # rsp 358 | // mov [rsp], rax 359 | // mov rax, [rsp+0x18] # rflags 360 | // mov [rsp+8], rax 361 | // mov rax, [rsp+0x10] # rax 362 | // mov [rsp+0x18], rax 363 | // mov dword ptr [rsp+0x10],1 # stack flag 364 | // jmp _other_registers 365 | // _stack_aligned_16: 366 | // push rax 367 | // push rax 368 | // mov rax, [rsp+0x18] # rsp 369 | // mov [rsp], rax 370 | // mov rax, [rsp+8] # rax 371 | // mov [rsp+0x18], rax 372 | // mov rax,[rsp+0x10] # rflags 373 | // mov [rsp+8], rax 374 | // mov dword ptr [rsp+0x10], 0 # stack flag 375 | // _other_registers: 376 | // push rbx 377 | // push rcx 378 | // push rdx 379 | // push rsi 380 | // push rdi 381 | // push rbp 382 | // push r8 383 | // push r9 384 | // push r10 385 | // push r11 386 | // push r12 387 | // push r13 388 | // push r14 389 | // push r15 390 | // sub rsp,0x40 391 | // movaps xmmword ptr ss:[rsp],xmm0 392 | // movaps xmmword ptr ss:[rsp+0x10],xmm1 393 | // movaps xmmword ptr ss:[rsp+0x20],xmm2 394 | // movaps xmmword ptr ss:[rsp+0x30],xmm3 395 | buf.write(&[ 396 | 0x54, 0x9C, 0x48, 0xF7, 0xC4, 0x08, 0x00, 0x00, 0x00, 0x74, 0x2C, 0x50, 0x48, 0x83, 0xEC, 397 | 0x10, 0x48, 0x8B, 0x44, 0x24, 0x20, 0x48, 0x89, 0x04, 0x24, 0x48, 0x8B, 0x44, 0x24, 0x18, 398 | 0x48, 0x89, 0x44, 0x24, 0x08, 0x48, 0x8B, 0x44, 0x24, 0x10, 0x48, 0x89, 0x44, 0x24, 0x18, 399 | 0xC7, 0x44, 0x24, 0x10, 0x01, 0x00, 0x00, 0x00, 0xEB, 0x27, 0x50, 0x50, 0x48, 0x8B, 0x44, 400 | 0x24, 0x18, 0x48, 0x89, 0x04, 0x24, 0x48, 0x8B, 0x44, 0x24, 0x08, 0x48, 0x89, 0x44, 0x24, 401 | 0x18, 0x48, 0x8B, 0x44, 0x24, 0x10, 0x48, 0x89, 0x44, 0x24, 0x08, 0xC7, 0x44, 0x24, 0x10, 402 | 0x00, 0x00, 0x00, 0x00, 0x53, 0x51, 0x52, 0x56, 0x57, 0x55, 0x41, 0x50, 0x41, 0x51, 0x41, 403 | 0x52, 0x41, 0x53, 0x41, 0x54, 0x41, 0x55, 0x41, 0x56, 0x41, 0x57, 0x48, 0x83, 0xEC, 0x40, 404 | 0x0F, 0x29, 0x04, 0x24, 0x0F, 0x29, 0x4C, 0x24, 0x10, 0x0F, 0x29, 0x54, 0x24, 0x20, 0x0F, 405 | 0x29, 0x5C, 0x24, 0x30, 406 | ]) 407 | } 408 | 409 | fn write_trampoline_epilog1(buf: &mut impl Write) -> Result { 410 | // movaps xmm0,xmmword ptr ss:[rsp] 411 | // movaps xmm1,xmmword ptr ss:[rsp+0x10] 412 | // movaps xmm2,xmmword ptr ss:[rsp+0x20] 413 | // movaps xmm3,xmmword ptr ss:[rsp+0x30] 414 | // add rsp,0x40 415 | // pop r15 416 | // pop r14 417 | // pop r13 418 | // pop r12 419 | // pop r11 420 | // pop r10 421 | // pop r9 422 | // pop r8 423 | // pop rbp 424 | // pop rdi 425 | // pop rsi 426 | // pop rdx 427 | // pop rcx 428 | // pop rbx 429 | // add rsp,8 430 | buf.write(&[ 431 | 0x0F, 0x28, 0x04, 0x24, 0x0F, 0x28, 0x4C, 0x24, 0x10, 0x0F, 0x28, 0x54, 0x24, 0x20, 0x0F, 432 | 0x28, 0x5C, 0x24, 0x30, 0x48, 0x83, 0xC4, 0x40, 0x41, 0x5F, 0x41, 0x5E, 0x41, 0x5D, 0x41, 433 | 0x5C, 0x41, 0x5B, 0x41, 0x5A, 0x41, 0x59, 0x41, 0x58, 0x5D, 0x5F, 0x5E, 0x5A, 0x59, 0x5B, 434 | 0x48, 0x83, 0xC4, 0x08, 435 | ]) 436 | } 437 | 438 | fn write_trampoline_epilog2_common(buf: &mut impl Write) -> Result { 439 | // test dword ptr ss:[rsp+0x8],1 440 | // je _branch1 441 | // mov rax, [rsp+0x10] 442 | // mov [rsp+0x18], rax 443 | // popfq 444 | // pop rax 445 | // pop rax 446 | // pop rax 447 | // jmp _branch2 448 | // _branch1: 449 | // popfq 450 | // pop rax 451 | // pop rax 452 | // _branch2: 453 | buf.write(&[ 454 | 0xF7, 0x44, 0x24, 0x08, 0x01, 0x00, 0x00, 0x00, 0x74, 0x10, 0x48, 0x8B, 0x44, 0x24, 0x10, 455 | 0x48, 0x89, 0x44, 0x24, 0x18, 0x9D, 0x58, 0x58, 0x58, 0xEB, 0x03, 0x9D, 0x58, 0x58, 456 | ]) 457 | } 458 | 459 | fn write_trampoline_epilog2_jmp_ret(buf: &mut impl Write) -> Result { 460 | // test dword ptr ss:[rsp+8],1 461 | // je _branch1 462 | // popfq 463 | // mov [rsp], rax 464 | // pop rax 465 | // pop rax 466 | // lea rsp, [rsp+8] # Not use 'add rsp, 8' as it will modify the rflags 467 | // jmp _branch2 468 | // _branch1: 469 | // popfq 470 | // mov [rsp-8],rax 471 | // pop rax 472 | // pop rax 473 | // _branch2: 474 | // jmp qword ptr ss:[rsp-0x18] 475 | buf.write(&[ 476 | 0xF7, 0x44, 0x24, 0x08, 0x01, 0x00, 0x00, 0x00, 0x74, 0x0E, 0x9D, 0x48, 0x89, 0x04, 0x24, 477 | 0x58, 0x58, 0x48, 0x8D, 0x64, 0x24, 0x08, 0xEB, 0x08, 0x9D, 0x48, 0x89, 0x44, 0x24, 0xF8, 478 | 0x58, 0x58, 0xFF, 0x64, 0x24, 0xE8 479 | ]) 480 | } 481 | 482 | fn jmp_addr(addr: u64, buf: &mut T) -> Result<(), HookError> { 483 | buf.write(&[0xff, 0x25, 0, 0, 0, 0])?; 484 | buf.write(&addr.to_le_bytes())?; 485 | Ok(()) 486 | } 487 | 488 | fn write_ori_func_addr(buf: &mut T, ori_func_addr_off: u64, ori_func_off: u64) { 489 | let pos = buf.stream_position().unwrap(); 490 | buf.seek(SeekFrom::Start(ori_func_addr_off)).unwrap(); 491 | buf.write(&ori_func_off.to_le_bytes()).unwrap(); 492 | buf.seek(SeekFrom::Start(pos)).unwrap(); 493 | } 494 | 495 | fn generate_jmp_back_trampoline( 496 | buf: &mut T, 497 | trampoline_base_addr: u64, 498 | moving_code: &Vec, 499 | ori_addr: usize, 500 | cb: JmpBackRoutine, 501 | ori_len: u8, 502 | user_data: usize, 503 | ) -> Result<(), HookError> { 504 | // mov rdx, user_data 505 | buf.write(&[0x48, 0xba])?; 506 | buf.write(&(user_data as u64).to_le_bytes())?; 507 | // mov rcx, rsp 508 | // sub rsp, 0x10 509 | // mov rax, cb 510 | buf.write(&[0x48, 0x89, 0xe1, 0x48, 0x83, 0xec, 0x10, 0x48, 0xb8])?; 511 | buf.write(&(cb as usize as u64).to_le_bytes())?; 512 | // call rax 513 | // add rsp, 0x10 514 | buf.write(&[0xff, 0xd0, 0x48, 0x83, 0xc4, 0x10])?; 515 | write_trampoline_epilog1(buf)?; 516 | write_trampoline_epilog2_common(buf)?; 517 | 518 | let cur_pos = buf.stream_position().unwrap(); 519 | buf.write(&move_code_to_addr( 520 | moving_code, 521 | trampoline_base_addr + cur_pos, 522 | )?)?; 523 | 524 | jmp_addr(ori_addr as u64 + u64::from(ori_len), buf)?; 525 | Ok(()) 526 | } 527 | 528 | fn generate_retn_trampoline( 529 | buf: &mut T, 530 | trampoline_base_addr: u64, 531 | moving_code: &Vec, 532 | ori_addr: usize, 533 | cb: RetnRoutine, 534 | ori_len: u8, 535 | user_data: usize, 536 | ) -> Result<(), HookError> { 537 | // mov r8, user_data 538 | buf.write(&[0x49, 0xb8])?; 539 | buf.write(&(user_data as u64).to_le_bytes())?; 540 | let ori_func_addr_off = buf.stream_position().unwrap() + 2; 541 | // mov rdx, ori_func 542 | // mov rcx, rsp 543 | // sub rsp,0x20 544 | // mov rax, cb 545 | buf.write(&[ 546 | 0x48, 0xba, 0, 0, 0, 0, 0, 0, 0, 0, 0x48, 0x89, 0xe1, 0x48, 0x83, 0xec, 0x20, 0x48, 0xb8, 547 | ])?; 548 | buf.write(&(cb as usize as u64).to_le_bytes())?; 549 | // call rax 550 | // add rsp, 0x20 551 | // mov [rsp + 0xc8], rax 552 | buf.write(&[ 553 | 0xff, 0xd0, 0x48, 0x83, 0xc4, 0x20, 0x48, 0x89, 0x84, 0x24, 0xc8, 0x00, 0x00, 0x00, 554 | ])?; 555 | write_trampoline_epilog1(buf)?; 556 | write_trampoline_epilog2_common(buf)?; 557 | // ret 558 | buf.write(&[0xc3])?; 559 | 560 | let ori_func_off = buf.stream_position().unwrap(); 561 | buf.write(&move_code_to_addr( 562 | moving_code, 563 | trampoline_base_addr + ori_func_off, 564 | )?)?; 565 | jmp_addr(ori_addr as u64 + u64::from(ori_len), buf)?; 566 | 567 | write_ori_func_addr(buf, ori_func_addr_off, trampoline_base_addr + ori_func_off); 568 | 569 | Ok(()) 570 | } 571 | 572 | fn generate_jmp_addr_trampoline( 573 | buf: &mut T, 574 | trampoline_base_addr: u64, 575 | moving_code: &Vec, 576 | ori_addr: usize, 577 | dest_addr: usize, 578 | cb: JmpToAddrRoutine, 579 | ori_len: u8, 580 | user_data: usize, 581 | ) -> Result<(), HookError> { 582 | // mov r8, user_data 583 | buf.write(&[0x49, 0xb8])?; 584 | buf.write(&(user_data as u64).to_le_bytes())?; 585 | let ori_func_addr_off = buf.stream_position().unwrap() + 2; 586 | // mov rdx, ori_func 587 | // mov rcx, rsp 588 | // sub rsp,0x20 589 | // mov rax, cb 590 | buf.write(&[ 591 | 0x48, 0xba, 0, 0, 0, 0, 0, 0, 0, 0, 0x48, 0x89, 0xe1, 0x48, 0x83, 0xec, 0x20, 0x48, 0xb8, 592 | ])?; 593 | buf.write(&(cb as usize as u64).to_le_bytes())?; 594 | // call rax 595 | // add rsp, 0x20 596 | buf.write(&[0xff, 0xd0, 0x48, 0x83, 0xc4, 0x20])?; 597 | write_trampoline_epilog1(buf)?; 598 | write_trampoline_epilog2_common(buf)?; 599 | jmp_addr(dest_addr as u64, buf)?; 600 | 601 | let ori_func_off = buf.stream_position().unwrap(); 602 | buf.write(&move_code_to_addr( 603 | moving_code, 604 | trampoline_base_addr + ori_func_off, 605 | )?)?; 606 | jmp_addr(ori_addr as u64 + u64::from(ori_len), buf)?; 607 | 608 | write_ori_func_addr(buf, ori_func_addr_off, trampoline_base_addr + ori_func_off); 609 | 610 | Ok(()) 611 | } 612 | 613 | fn generate_jmp_ret_trampoline( 614 | buf: &mut T, 615 | trampoline_base_addr: u64, 616 | moving_code: &Vec, 617 | ori_addr: usize, 618 | cb: JmpToRetRoutine, 619 | ori_len: u8, 620 | user_data: usize, 621 | ) -> Result<(), HookError> { 622 | // mov r8, user_data 623 | buf.write(&[0x49, 0xb8])?; 624 | buf.write(&(user_data as u64).to_le_bytes())?; 625 | let ori_func_addr_off = buf.stream_position().unwrap() + 2; 626 | // mov rdx, ori_func 627 | // mov rcx, rsp 628 | // sub rsp,0x20 629 | // mov rax, cb 630 | buf.write(&[ 631 | 0x48, 0xba, 0, 0, 0, 0, 0, 0, 0, 0, 0x48, 0x89, 0xe1, 0x48, 0x83, 0xec, 0x20, 0x48, 0xb8, 632 | ])?; 633 | buf.write(&(cb as usize as u64).to_le_bytes())?; 634 | // call rax 635 | // add rsp, 0x20 636 | buf.write(&[0xff, 0xd0, 0x48, 0x83, 0xc4, 0x20])?; 637 | write_trampoline_epilog1(buf)?; 638 | write_trampoline_epilog2_jmp_ret(buf)?; 639 | 640 | let ori_func_off = buf.stream_position().unwrap(); 641 | buf.write(&move_code_to_addr( 642 | moving_code, 643 | trampoline_base_addr + ori_func_off, 644 | )?)?; 645 | jmp_addr(ori_addr as u64 + u64::from(ori_len), buf)?; 646 | 647 | write_ori_func_addr(buf, ori_func_addr_off, trampoline_base_addr + ori_func_off); 648 | 649 | Ok(()) 650 | } 651 | 652 | fn generate_trampoline( 653 | hooker: &Hooker, 654 | moving_code: Vec, 655 | ori_len: u8, 656 | user_data: usize, 657 | ) -> Result, HookError> { 658 | let mut trampoline_buffer = Box::new([0u8; TRAMPOLINE_MAX_LEN]); 659 | let trampoline_addr = trampoline_buffer.as_ptr() as u64; 660 | let mut buf = Cursor::new(&mut trampoline_buffer[..]); 661 | 662 | write_trampoline_prolog(&mut buf)?; 663 | 664 | match hooker.hook_type { 665 | HookType::JmpBack(cb) => generate_jmp_back_trampoline( 666 | &mut buf, 667 | trampoline_addr, 668 | &moving_code, 669 | hooker.addr, 670 | cb, 671 | ori_len, 672 | user_data, 673 | ), 674 | HookType::Retn(cb) => generate_retn_trampoline( 675 | &mut buf, 676 | trampoline_addr, 677 | &moving_code, 678 | hooker.addr, 679 | cb, 680 | ori_len, 681 | user_data, 682 | ), 683 | HookType::JmpToAddr(dest_addr, cb) => generate_jmp_addr_trampoline( 684 | &mut buf, 685 | trampoline_addr, 686 | &moving_code, 687 | hooker.addr, 688 | dest_addr, 689 | cb, 690 | ori_len, 691 | user_data, 692 | ), 693 | HookType::JmpToRet(cb) => generate_jmp_ret_trampoline( 694 | &mut buf, 695 | trampoline_addr, 696 | &moving_code, 697 | hooker.addr, 698 | cb, 699 | ori_len, 700 | user_data, 701 | ), 702 | }?; 703 | 704 | Ok(trampoline_buffer) 705 | } 706 | 707 | #[cfg(windows)] 708 | fn modify_mem_protect(addr: usize, len: usize) -> Result { 709 | let mut old_prot: u32 = 0; 710 | let old_prot_ptr = std::ptr::addr_of_mut!(old_prot); 711 | // PAGE_EXECUTE_READWRITE = 0x40 712 | let ret = unsafe { VirtualProtect(addr as *const c_void, len, 0x40, old_prot_ptr) }; 713 | if ret == 0 { 714 | Err(HookError::MemoryProtect(unsafe { GetLastError() })) 715 | } else { 716 | Ok(old_prot) 717 | } 718 | } 719 | 720 | #[cfg(unix)] 721 | fn modify_mem_protect(addr: usize, len: usize) -> Result { 722 | let page_size = unsafe { sysconf(30) }; //_SC_PAGESIZE == 30 723 | if len > page_size.try_into().unwrap() { 724 | Err(HookError::InvalidParameter) 725 | } else { 726 | //(PROT_READ | PROT_WRITE | PROT_EXEC) == 7 727 | let ret = unsafe { 728 | mprotect( 729 | (addr & !(page_size as usize - 1)) as *mut c_void, 730 | page_size as usize, 731 | 7, 732 | ) 733 | }; 734 | if ret != 0 { 735 | let err = unsafe { *(__errno_location()) }; 736 | Err(HookError::MemoryProtect(err as u32)) 737 | } else { 738 | // it's too complex to get the original memory protection 739 | Ok(7) 740 | } 741 | } 742 | } 743 | #[cfg(windows)] 744 | fn recover_mem_protect(addr: usize, len: usize, old: u32) { 745 | let mut old_prot: u32 = 0; 746 | let old_prot_ptr = std::ptr::addr_of_mut!(old_prot); 747 | unsafe { VirtualProtect(addr as *const c_void, len, old, old_prot_ptr) }; 748 | } 749 | 750 | #[cfg(unix)] 751 | fn recover_mem_protect(addr: usize, _: usize, old: u32) { 752 | let page_size = unsafe { sysconf(30) }; //_SC_PAGESIZE == 30 753 | unsafe { 754 | mprotect( 755 | (addr & !(page_size as usize - 1)) as *mut c_void, 756 | page_size as usize, 757 | old as i32, 758 | ) 759 | }; 760 | } 761 | fn modify_jmp(dest_addr: usize, trampoline_addr: usize) { 762 | let buf = unsafe { slice::from_raw_parts_mut(dest_addr as *mut u8, 14) }; 763 | let distance = trampoline_addr as i64 - (dest_addr as i64 + 5); 764 | if distance.abs() <= 0x7fff_ffff { 765 | // jmp xxx 766 | buf[0] = 0xe9; 767 | buf[1..5].copy_from_slice(&(distance as i32).to_le_bytes()); 768 | } else { 769 | // jmp qword ptr [rip+0] 770 | buf[0..6].copy_from_slice(&[0xff, 0x25, 0, 0, 0, 0]); 771 | buf[6..14].copy_from_slice(&(trampoline_addr as u64).to_le_bytes()); 772 | } 773 | } 774 | 775 | fn modify_jmp_with_thread_cb(hook: &Hooker, trampoline_addr: usize) -> Result<(), HookError> { 776 | if let CallbackOption::Some(cbs) = &hook.thread_cb { 777 | if !cbs.pre() { 778 | return Err(HookError::PreHook); 779 | } 780 | modify_jmp(hook.addr, trampoline_addr); 781 | cbs.post(); 782 | Ok(()) 783 | } else { 784 | modify_jmp(hook.addr, trampoline_addr); 785 | Ok(()) 786 | } 787 | } 788 | 789 | fn recover_jmp(dest_addr: usize, origin: &[u8]) { 790 | let buf = unsafe { slice::from_raw_parts_mut(dest_addr as *mut u8, origin.len()) }; 791 | // jmp trampoline_addr 792 | buf.copy_from_slice(origin); 793 | } 794 | 795 | fn recover_jmp_with_thread_cb(hook: &HookPoint) -> Result<(), HookError> { 796 | if let CallbackOption::Some(cbs) = &hook.thread_cb { 797 | if !cbs.pre() { 798 | return Err(HookError::PreHook); 799 | } 800 | recover_jmp(hook.addr, &hook.origin); 801 | cbs.post(); 802 | } else { 803 | recover_jmp(hook.addr, &hook.origin); 804 | } 805 | Ok(()) 806 | } 807 | -------------------------------------------------------------------------------- /src/x64/fixed_memory_unix.rs: -------------------------------------------------------------------------------- 1 | use super::{cmp, HookError}; 2 | use lazy_static::lazy_static; 3 | use regex::Regex; 4 | use std::format; 5 | use std::fs::File; 6 | use std::io::{BufRead, BufReader}; 7 | use std::process; 8 | 9 | use libc::{ 10 | __errno_location, c_void, mmap, munmap, sysconf, MAP_ANONYMOUS, MAP_FIXED_NOREPLACE, 11 | MAP_PRIVATE, 12 | }; 13 | 14 | pub(super) struct FixedMemory { 15 | pub addr: u64, 16 | pub len: u32, 17 | } 18 | 19 | impl Drop for FixedMemory { 20 | fn drop(&mut self) { 21 | unsafe { munmap(self.addr as *mut c_void, self.len as usize) }; 22 | } 23 | } 24 | 25 | impl FixedMemory { 26 | pub fn allocate(hook_addr: u64) -> Result { 27 | let bound = Bound::new(hook_addr); 28 | let block = MemoryLayout::read_self_mem_layout()?.find_memory_with_bound(&bound)?; 29 | let len = block.end - block.begin; 30 | let mut addr = unsafe { 31 | mmap( 32 | block.begin as *mut c_void, 33 | len as usize, 34 | 7, 35 | MAP_PRIVATE | MAP_FIXED_NOREPLACE | MAP_ANONYMOUS, 36 | -1, 37 | 0, 38 | ) 39 | } as usize as u64; 40 | // If kernel doesn't support MAP_FIXED_NOREPLACE 41 | if addr == u64::MAX && unsafe { *(__errno_location()) } == 95 { 42 | addr = unsafe { 43 | mmap( 44 | block.begin as *mut c_void, 45 | len as usize, 46 | 7, 47 | MAP_PRIVATE | MAP_ANONYMOUS, 48 | -1, 49 | 0, 50 | ) 51 | } as usize as u64; 52 | } 53 | match addr { 54 | 0xffff_ffff_ffff_ffff => Err(HookError::MemoryProtect( 55 | unsafe { *(__errno_location()) } as u32, 56 | )), 57 | x if x == block.begin || (x >= bound.min && x + len <= bound.max) => Ok(Self { 58 | addr, 59 | len: len as u32, 60 | }), 61 | _ => Err(HookError::MemoryProtect(0)), 62 | } 63 | } 64 | } 65 | 66 | struct MemoryLayout(Vec); 67 | 68 | impl MemoryLayout { 69 | fn read_self_mem_layout() -> Result { 70 | let maps = File::open(format!("/proc/{}/maps", process::id()))?; 71 | BufReader::new(maps) 72 | .lines() 73 | .map(|line| { 74 | line.map_err(|_| HookError::MemoryLayoutFormat) 75 | .and_then(MemoryBlock::from_string) 76 | }) 77 | .collect::, _>>() 78 | .map(Self) 79 | } 80 | 81 | fn find_memory_with_bound(&self, bnd: &Bound) -> Result { 82 | //@todo fix: find memory block from middle to edge 83 | let page_size = unsafe { sysconf(30) } as u64; //_SC_PAGESIZE == 30 84 | let blocks = &self.0; 85 | if blocks.is_empty() { 86 | return Err(HookError::MemoryAllocation(0)); 87 | } 88 | // test the first block 89 | if blocks[0].begin > page_size * 2 && bnd.min <= page_size { 90 | return Ok(MemoryBlock { 91 | begin: page_size, 92 | end: page_size * 2, 93 | }); 94 | } 95 | for i in 1..blocks.len() { 96 | let gap = blocks[i].begin - blocks[i - 1].end; 97 | if gap >= page_size && blocks[i - 1].end >= bnd.min && blocks[i].begin < bnd.max { 98 | return Ok(MemoryBlock { 99 | begin: blocks[i - 1].end, 100 | end: blocks[i - 1].end + page_size, 101 | }); 102 | } 103 | } 104 | Err(HookError::MemorySearching) 105 | } 106 | } 107 | 108 | #[derive(Debug)] 109 | struct MemoryBlock { 110 | begin: u64, 111 | end: u64, 112 | } 113 | impl MemoryBlock { 114 | fn from_string(s: String) -> Result { 115 | lazy_static! { 116 | static ref RE: Regex = Regex::new("^([a-fA-F0-9]+)-([a-fA-F0-9]+)").unwrap(); 117 | } 118 | //let RE = Regex::new("").unwrap(); 119 | RE.captures(&s) 120 | .ok_or(HookError::MemoryLayoutFormat) 121 | .and_then(|cap| { 122 | let begin = cap.get(1).unwrap().as_str(); 123 | let end = cap.get(2).unwrap().as_str(); 124 | Ok(Self { 125 | begin: u64::from_str_radix(begin, 16).or(Err(HookError::MemoryLayoutFormat))?, 126 | end: u64::from_str_radix(end, 16).or(Err(HookError::MemoryLayoutFormat))?, 127 | }) 128 | }) 129 | } 130 | } 131 | 132 | struct Bound { 133 | min: u64, 134 | max: u64, 135 | } 136 | 137 | impl Bound { 138 | fn new(init_addr: u64) -> Self { 139 | Self { 140 | min: init_addr.saturating_sub(i32::MAX as u64), 141 | max: init_addr.saturating_add(i32::MAX as u64), 142 | } 143 | } 144 | 145 | fn _to_new(self, dest: u64) -> Self { 146 | Self { 147 | min: cmp::max(self.min, dest.saturating_sub(i32::MAX as u64)), 148 | max: cmp::min(self.max, dest.saturating_add(i32::MAX as u64)), 149 | } 150 | } 151 | 152 | fn _check(&self) -> Result<(), HookError> { 153 | if self.min > self.max { 154 | Err(HookError::InvalidParameter) 155 | } else { 156 | Ok(()) 157 | } 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /src/x64/fixed_memory_win.rs: -------------------------------------------------------------------------------- 1 | use super::{cmp, HookError}; 2 | 3 | use core::ffi::c_void; 4 | use std::mem::{size_of, MaybeUninit}; 5 | use windows_sys::Win32::Foundation::{GetLastError, ERROR_INVALID_PARAMETER}; 6 | use windows_sys::Win32::System::Memory::{VirtualAlloc, VirtualFree, VirtualQuery}; 7 | use windows_sys::Win32::System::Memory::{ 8 | MEMORY_BASIC_INFORMATION, MEM_COMMIT, MEM_FREE, MEM_RELEASE, MEM_RESERVE, 9 | PAGE_EXECUTE_READWRITE, 10 | }; 11 | 12 | enum QueryResult { 13 | Success(u64), 14 | NotUsable(u64, u64), 15 | OverLimit, 16 | Fail(u32), 17 | } 18 | 19 | pub(super) struct FixedMemory { 20 | pub addr: u64, 21 | pub len: u32, 22 | } 23 | 24 | impl Drop for FixedMemory { 25 | fn drop(&mut self) { 26 | unsafe { VirtualFree(self.addr as *mut c_void, 0, MEM_RELEASE) }; 27 | } 28 | } 29 | impl FixedMemory { 30 | pub fn allocate(hook_addr: u64) -> Result { 31 | let addr = FixedMemory::allocate_internal(&Bound::new(hook_addr))?; 32 | 33 | Ok(Self { addr, len: 4096 }) 34 | } 35 | 36 | fn query_and_alloc(addr: u64) -> QueryResult { 37 | #[allow(invalid_value)] 38 | let mut mbi: MEMORY_BASIC_INFORMATION = unsafe { MaybeUninit::uninit().assume_init() }; 39 | let ret = unsafe { 40 | VirtualQuery( 41 | addr as *mut c_void, 42 | &mut mbi, 43 | size_of::(), 44 | ) 45 | }; 46 | if ret == 0 { 47 | let last_err = unsafe { GetLastError() }; 48 | if last_err == ERROR_INVALID_PARAMETER { 49 | // ERROR_INVALID_PARAMETER means lpAddress specifies an address above 50 | // the highest memory address accessible to the process. (from MSDN) 51 | QueryResult::OverLimit 52 | } else { 53 | QueryResult::Fail(last_err) 54 | } 55 | } else if mbi.State == MEM_FREE && mbi.RegionSize >= 4096 { 56 | let mem = unsafe { 57 | VirtualAlloc( 58 | mbi.BaseAddress, 59 | 4096, 60 | MEM_COMMIT | MEM_RESERVE, 61 | PAGE_EXECUTE_READWRITE, 62 | ) 63 | }; 64 | if mem == std::ptr::null_mut() { 65 | QueryResult::NotUsable(mbi.BaseAddress as usize as u64, mbi.RegionSize as u64) 66 | } else { 67 | QueryResult::Success(mem as usize as u64) 68 | } 69 | } else { 70 | QueryResult::NotUsable(mbi.BaseAddress as usize as u64, mbi.RegionSize as u64) 71 | } 72 | } 73 | 74 | fn allocate_internal(bnd: &Bound) -> Result { 75 | let mut cur_addr = bnd.middle(); 76 | while cur_addr < bnd.max { 77 | match FixedMemory::query_and_alloc(cur_addr) { 78 | QueryResult::Success(addr) => { 79 | return Ok(addr); 80 | } 81 | QueryResult::NotUsable(_, size) => { 82 | cur_addr += if size > 0 { size } else { 4096 }; 83 | } 84 | QueryResult::OverLimit => { 85 | break; 86 | } 87 | QueryResult::Fail(e) => { 88 | return Err(HookError::MemoryAllocation(e)); 89 | } 90 | } 91 | } 92 | cur_addr = bnd.middle(); 93 | while cur_addr > bnd.min { 94 | match FixedMemory::query_and_alloc(cur_addr) { 95 | QueryResult::Success(addr) => { 96 | return Ok(addr); 97 | } 98 | QueryResult::NotUsable(base, _) => { 99 | cur_addr = base.saturating_sub(4096); 100 | } 101 | QueryResult::Fail(e) => { 102 | return Err(HookError::MemoryAllocation(e)); 103 | } 104 | QueryResult::OverLimit => { 105 | return Err(HookError::MemoryAllocation(0)); 106 | } 107 | } 108 | } 109 | Err(HookError::MemorySearching) 110 | } 111 | } 112 | 113 | struct Bound { 114 | min: u64, 115 | max: u64, 116 | } 117 | 118 | impl Bound { 119 | fn new(init_addr: u64) -> Self { 120 | Self { 121 | min: init_addr.saturating_sub(i32::MAX as u64), 122 | max: init_addr.saturating_add(i32::MAX as u64), 123 | } 124 | } 125 | 126 | fn _to_new(self, dest: u64) -> Self { 127 | Self { 128 | min: cmp::max(self.min, dest.saturating_sub(i32::MAX as u64)), 129 | max: cmp::min(self.max, dest.saturating_add(i32::MAX as u64)), 130 | } 131 | } 132 | 133 | fn _check(&self) -> Result<(), HookError> { 134 | if self.min > self.max { 135 | Err(HookError::InvalidParameter) 136 | } else { 137 | Ok(()) 138 | } 139 | } 140 | 141 | fn middle(&self) -> u64 { 142 | self.min / 2 + self.max / 2 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /src/x64/move_inst.rs: -------------------------------------------------------------------------------- 1 | use std::io::{Cursor, Seek, SeekFrom, Write}; 2 | 3 | use iced_x86::{ 4 | BlockEncoder, BlockEncoderOptions, Code, Encoder, FlowControl, Instruction, InstructionBlock, 5 | MemoryOperand, Mnemonic, Register, 6 | }; 7 | 8 | use crate::HookError; 9 | 10 | struct JmpOffsetInfo { 11 | disp_offset: u64, 12 | relative_start_offset: u64, 13 | dest_addr: u64, 14 | } 15 | 16 | struct JmpRelocationInfo { 17 | offset_of_addr_to_relocate: u64, 18 | relative_start_offset: u64, 19 | is_jmp_to_new_insts: bool, 20 | dest_addr: u64, // index of new insts if jmps to new insts 21 | } 22 | 23 | struct NewInstInfo { 24 | offset: u64, 25 | reloc_info: Option, 26 | } 27 | 28 | pub(super) fn move_code_to_addr( 29 | ori_insts: &Vec, 30 | dest_addr: u64, 31 | ) -> Result, HookError> { 32 | if ori_insts[0].ip().abs_diff(dest_addr) < 0x7fff_f000 { 33 | // use iced_x86 to relocate instructions when new address is in +/- 2GB 34 | let block = InstructionBlock::new(ori_insts, dest_addr); 35 | let encoded = BlockEncoder::encode(64, block, BlockEncoderOptions::NONE) 36 | .map_err(|_| HookError::MoveCode)?; 37 | Ok(encoded.code_buffer) 38 | } else { 39 | let mut new_inst_info: Vec = vec![]; 40 | let mut buf = Cursor::new(Vec::::with_capacity(100)); 41 | for inst in ori_insts { 42 | let cur_pos = buf.stream_position().unwrap(); 43 | let off_info = relocate_inst_addr(inst, dest_addr + cur_pos, &mut buf)?; 44 | let reloc_info = if let Some(off_info) = off_info { 45 | let last_inst = ori_insts.last().unwrap(); 46 | let (is_jmp_to_new_insts, dest_addr) = if off_info.dest_addr >= ori_insts[0].ip() 47 | && off_info.dest_addr < last_inst.ip() + last_inst.len() as u64 48 | { 49 | let idx = ori_insts 50 | .iter() 51 | .position(|i| i.ip() == off_info.dest_addr) 52 | .ok_or(HookError::MovingCodeNotSupported)?; 53 | (true, idx as u64) 54 | } else { 55 | (false, off_info.dest_addr) 56 | }; 57 | Some(JmpRelocationInfo { 58 | offset_of_addr_to_relocate: cur_pos + off_info.disp_offset, 59 | relative_start_offset: cur_pos + off_info.relative_start_offset, 60 | is_jmp_to_new_insts, 61 | dest_addr, 62 | }) 63 | } else { 64 | None 65 | }; 66 | new_inst_info.push(NewInstInfo { 67 | offset: cur_pos, 68 | reloc_info, 69 | }); 70 | } 71 | let need_relocating_cnt = new_inst_info 72 | .iter() 73 | .filter(|i| i.reloc_info.is_some()) 74 | .count(); 75 | 76 | if need_relocating_cnt != 0 { 77 | // align the addresses to 8-byte 78 | let cur_addr = dest_addr + buf.stream_position().unwrap(); 79 | let padding_cnt = ((cur_addr + 5 + 7) & !7) - (cur_addr + 5); // 5 bytes for jmp near below 80 | 81 | // jmp over the relocation address table 82 | let jmp_over_len = padding_cnt + need_relocating_cnt as u64 * 8; 83 | buf.write(&[0xe9])?; 84 | buf.write(&(jmp_over_len as u32).to_le_bytes())?; 85 | 86 | buf.write(&vec![0xCCu8; padding_cnt as usize])?; 87 | 88 | // write relocation table and relocate 89 | let mut table_offset = buf.stream_position().unwrap(); 90 | for new_inst in &new_inst_info { 91 | if let Some(reloc_info) = &new_inst.reloc_info { 92 | buf.seek(SeekFrom::Start(reloc_info.offset_of_addr_to_relocate)) 93 | .unwrap(); 94 | let disp = (table_offset - reloc_info.relative_start_offset) as u32; 95 | buf.write(&disp.to_le_bytes()).unwrap(); 96 | buf.seek(SeekFrom::Start(table_offset)).unwrap(); 97 | if reloc_info.is_jmp_to_new_insts { 98 | let real_dest_addr = 99 | dest_addr + new_inst_info[reloc_info.dest_addr as usize].offset; 100 | buf.write(&real_dest_addr.to_le_bytes())?; 101 | } else { 102 | buf.write(&reloc_info.dest_addr.to_le_bytes())?; 103 | } 104 | table_offset += 8; 105 | } 106 | } 107 | } 108 | Ok(buf.into_inner()) 109 | } 110 | } 111 | 112 | fn relocate_inst_addr( 113 | inst: &Instruction, 114 | dest_addr: u64, 115 | buf: &mut T, 116 | ) -> Result, HookError> { 117 | let mut encoder = Encoder::new(64); 118 | match inst.flow_control() { 119 | FlowControl::UnconditionalBranch => { 120 | // origin: jmp xxx 121 | // new: jmp qword ptr [rip+xxx] 122 | buf.write(&[0xff, 0x25, 0, 0, 0, 0])?; 123 | Ok(Some(JmpOffsetInfo { 124 | disp_offset: 2, 125 | relative_start_offset: 6, 126 | dest_addr: inst.near_branch_target(), 127 | })) 128 | } 129 | FlowControl::IndirectBranch if inst.is_ip_rel_memory_operand() => { 130 | // origin: jmp qword ptr [rip+xxx] 131 | // new: 132 | // mov [rsp-0x10], rax; 133 | // mov rax, xxx; 134 | // push [rax]; 135 | // mov rax, [rsp-8]; 136 | // ret 137 | buf.write(&[0x48, 0x89, 0x44, 0x24, 0xf0, 0x48, 0xb8])?; 138 | buf.write(&inst.ip_rel_memory_address().to_le_bytes())?; 139 | buf.write(&[0xff, 0x30, 0x48, 0x8b, 0x44, 0x24, 0xf8, 0xc3])?; 140 | Ok(None) 141 | } 142 | FlowControl::ConditionalBranch if inst.is_jcc_short_or_near() => { 143 | // origin: je a 144 | // new: jne @+6; jmp a; 145 | let mut new_inst = inst.clone(); 146 | new_inst.negate_condition_code(); 147 | new_inst.set_near_branch64(dest_addr + 8); 148 | new_inst.as_short_branch(); 149 | encoder 150 | .encode(&new_inst, dest_addr) 151 | .map_err(|_| HookError::MoveCode)?; 152 | buf.write(&encoder.take_buffer())?; 153 | buf.write(&[0xff, 0x25, 0, 0, 0, 0])?; 154 | Ok(Some(JmpOffsetInfo { 155 | disp_offset: 4, 156 | relative_start_offset: 8, 157 | dest_addr: inst.near_branch_target(), 158 | })) 159 | } 160 | FlowControl::ConditionalBranch 161 | if inst.is_jcx_short() || inst.is_loop() || inst.is_loopcc() => 162 | { 163 | // origin: jrcxz a 164 | // new: jrcxz @+2; jmp @+6; jmp a; 165 | let mut new_inst = inst.clone(); 166 | new_inst.set_near_branch64(dest_addr + 4); 167 | encoder 168 | .encode(&new_inst, dest_addr) 169 | .map_err(|_| HookError::MoveCode)?; 170 | buf.write(&encoder.take_buffer())?; 171 | buf.write(&[0xeb, 0x06, 0xff, 0x25, 0, 0, 0, 0])?; 172 | Ok(Some(JmpOffsetInfo { 173 | disp_offset: 6, 174 | relative_start_offset: 10, 175 | dest_addr: inst.near_branch_target(), 176 | })) 177 | } 178 | FlowControl::Call if inst.is_call_near() || inst.is_call_far() => { 179 | // origin: call a 180 | // new: call qword ptr [rip+xxx] 181 | buf.write(&[0xff, 0x15, 0, 0, 0, 0])?; 182 | Ok(Some(JmpOffsetInfo { 183 | disp_offset: 2, 184 | relative_start_offset: 6, 185 | dest_addr: inst.near_branch_target(), 186 | })) 187 | } 188 | FlowControl::IndirectCall if inst.is_ip_rel_memory_operand() => { 189 | // origin: call qword ptr [rip+xxx] 190 | // new: 191 | // mov [rsp-0x18], rax 192 | // mov rax, xxx 193 | // push @retn_lower 194 | // mov dword ptr [rsp+4], @retn_higher 195 | // push qword ptr [rax] 196 | // mov rax, [rsp-8] 197 | // ret 198 | buf.write(&[0x48, 0x89, 0x44, 0x24, 0xe8, 0x48, 0xb8])?; 199 | buf.write(&inst.ip_rel_memory_address().to_le_bytes())?; 200 | let retn_addr = dest_addr + 0x24; 201 | buf.write(&[0x68])?; 202 | buf.write(&((retn_addr & 0xffffffff) as u32).to_le_bytes())?; 203 | buf.write(&[0xc7, 0x44, 0x24, 0x04])?; 204 | buf.write(&((retn_addr >> 32) as u32).to_le_bytes())?; 205 | buf.write(&[0xff, 0x30, 0x48, 0x8b, 0x44, 0x24, 0xf8, 0xc3])?; 206 | Ok(None) 207 | } 208 | _ if inst.is_ip_rel_memory_operand() => { 209 | if let Register::RSP = inst.op0_register() { 210 | // not support instructions writing rsp, like: 211 | // add rsp, qword ptr [rip+xxx] 212 | return Err(HookError::MovingCodeNotSupported); 213 | } 214 | let encoded = relocate_ip_rel_memory_inst(inst, dest_addr); 215 | buf.write(&encoded)?; 216 | Ok(None) 217 | } 218 | _ => { 219 | encoder.encode(inst, dest_addr).unwrap(); 220 | buf.write(&encoder.take_buffer())?; 221 | Ok(None) 222 | } 223 | } 224 | } 225 | 226 | fn relocate_ip_rel_memory_inst(inst: &Instruction, dest_addr: u64) -> Vec { 227 | if let Mnemonic::Lea = inst.mnemonic() { 228 | // origin: lea eax, [rip+xxx] 229 | // new: mov eax, xxx 230 | let inst = Instruction::with2( 231 | Code::Mov_r64_imm64, 232 | inst.op0_register(), 233 | inst.ip_rel_memory_address(), 234 | ) 235 | .unwrap(); 236 | let mut encoder = Encoder::new(64); 237 | encoder.encode(&inst, dest_addr).unwrap(); 238 | encoder.take_buffer() 239 | } else { 240 | // origin: add dword ptr [rip+xxx], rbx 241 | // new: 242 | // mov [rsp-0x10], r8 243 | // mov r8, xxx 244 | // add dword ptr [r8], rbx 245 | // mov r8, [rsp-0x10] 246 | let middle_register = if inst_not_use_rbx(inst) { 247 | Register::RBX 248 | } else if inst_not_use_r8(inst) { 249 | Register::R8 250 | } else { 251 | Register::R9 // An instruction uses rel memory operand may not use 3 registers 252 | }; 253 | let new_inst1 = Instruction::with2( 254 | Code::Mov_rm64_r64, 255 | MemoryOperand::with_base_displ(Register::RSP, -16), 256 | middle_register, 257 | ) 258 | .unwrap(); 259 | let new_inst2 = Instruction::with2( 260 | Code::Mov_r64_imm64, 261 | middle_register, 262 | inst.ip_rel_memory_address(), 263 | ) 264 | .unwrap(); 265 | let mut new_inst3 = inst.clone(); 266 | new_inst3.set_memory_base(middle_register); 267 | new_inst3.set_memory_displacement32(0); 268 | new_inst3.set_memory_displ_size(0); 269 | 270 | let stack_inc = inst.stack_pointer_increment() as i64; 271 | let new_inst4 = Instruction::with2( 272 | Code::Mov_r64_rm64, 273 | middle_register, 274 | MemoryOperand::with_base_displ(Register::RSP, -16 - stack_inc), 275 | ) 276 | .unwrap(); 277 | let new_insts = [new_inst1, new_inst2, new_inst3, new_inst4]; 278 | let block = InstructionBlock::new(&new_insts, dest_addr); 279 | let encoded = BlockEncoder::encode(64, block, BlockEncoderOptions::NONE).unwrap(); 280 | encoded.code_buffer 281 | } 282 | } 283 | 284 | fn inst_not_use_rbx(inst: &Instruction) -> bool { 285 | (0..inst.op_count()).map(|i| inst.op_register(i)).all(|r| { 286 | !matches!(r, Register::BL) 287 | && !matches!(r, Register::BH) 288 | && !matches!(r, Register::BX) 289 | && !matches!(r, Register::EBX) 290 | && !matches!(r, Register::RBX) 291 | }) 292 | } 293 | fn inst_not_use_r8(inst: &Instruction) -> bool { 294 | (0..inst.op_count()).map(|i| inst.op_register(i)).all(|r| { 295 | !matches!(r, Register::R8D) 296 | && !matches!(r, Register::R8L) 297 | && !matches!(r, Register::R8W) 298 | && !matches!(r, Register::R8) 299 | }) 300 | } 301 | -------------------------------------------------------------------------------- /src/x64/tests.rs: -------------------------------------------------------------------------------- 1 | #[allow(unused_imports)] 2 | use super::*; 3 | 4 | #[cfg(test)] 5 | fn move_inst(inst: &[u8], ori_base_addr: u64, new_base_addr: u64) -> Vec { 6 | let mut decoder = Decoder::new(64, inst, DecoderOptions::NONE); 7 | decoder.set_ip(ori_base_addr); 8 | let insts: Vec = decoder.iter().collect(); 9 | let moved = move_code_to_addr(&insts, new_base_addr); 10 | assert_eq!(moved.is_ok(), true); 11 | moved.unwrap() 12 | } 13 | 14 | #[test] 15 | fn test_move_inst_short_1() { 16 | // jmp @+2 17 | let inst = [0xeb, 0x02]; 18 | let addr = inst.as_ptr() as u64; 19 | let new_inst = move_inst(&inst, addr, addr + 300); 20 | assert_eq!(new_inst, [0xe9, 0xd3, 0xfe, 0xff, 0xff]); 21 | } 22 | 23 | #[test] 24 | fn test_move_inst_short_2() { 25 | // call @+10 26 | let inst = [0xe8, 0xa, 0, 0, 0]; 27 | let addr = inst.as_ptr() as u64; 28 | let new_inst = move_inst(&inst, addr, addr - 0x3333); 29 | assert_eq!(new_inst, [0xe8, 0x3d, 0x33, 0x0, 0x0]) 30 | } 31 | 32 | #[test] 33 | fn test_move_inst_short_3() { 34 | // mov rbx, [rip + 0x00000001] 35 | let inst = [0x48, 0x8b, 0x1d, 0x01, 0x00, 0x00, 0x00]; 36 | let addr = inst.as_ptr() as u64; 37 | let new_inst = move_inst(&inst, addr, addr + 0x4000); 38 | assert_eq!(new_inst, [0x48, 0x8b, 0x1d, 0x1, 0xc0, 0xff, 0xff]); 39 | } 40 | 41 | #[test] 42 | fn test_move_inst_long_1() { 43 | // jmp @+0 44 | let inst = [0xeb, 0x00]; 45 | let addr = 0x40_0000; 46 | let new_inst = move_inst(&inst, addr, addr + 0x1_0000_0000); 47 | // jmp [rip@0x400002] 48 | // jmp @+13 49 | assert_eq!( 50 | new_inst, 51 | [ 52 | 0xff, 0x25, 0x0a, 0x00, 0x00, 0x00, 0xe9, 0x0d, 0x00, 0x00, 0x00, 0xcc, 0xcc, 0xcc, 53 | 0xcc, 0xcc, 0x02, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00 54 | ] 55 | ); 56 | } 57 | 58 | #[test] 59 | fn test_move_inst_long_2() { 60 | // jmp qword ptr [rip@400006] 61 | let inst = [0xff, 0x25, 0, 0, 0, 0]; 62 | let addr = 0x40_0000; 63 | let new_inst = move_inst(&inst, addr, addr + 0x1_0000_0000); 64 | // mov [rsp-0x10], rax; 65 | // mov rax, 400006; 66 | // push [rax]; 67 | // mov rax, [rsp-8]; 68 | // ret 69 | assert_eq!( 70 | new_inst, 71 | [ 72 | 0x48, 0x89, 0x44, 0x24, 0xf0, 0x48, 0xb8, 0x06, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 73 | 0x00, 0xff, 0x30, 0x48, 0x8b, 0x44, 0x24, 0xf8, 0xc3 74 | ] 75 | ); 76 | } 77 | 78 | #[test] 79 | fn test_move_inst_long_3() { 80 | // jne @+0 81 | let inst = [0x75, 0x00]; 82 | let addr = 0x40_0000; 83 | let new_inst = move_inst(&inst, addr, addr + 0x1_0000_0000); 84 | // je @+6 85 | // jmp [rip@0x400002] 86 | // jmp @+11 87 | assert_eq!( 88 | new_inst, 89 | [ 90 | 0x74, 0x06, 0xff, 0x25, 0x08, 0x00, 0x00, 0x00, 0xe9, 0x0b, 0x00, 0x00, 0x00, 0xcc, 91 | 0xcc, 0xcc, 0x02, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00 92 | ] 93 | ); 94 | } 95 | 96 | #[test] 97 | fn test_move_inst_long_4() { 98 | // jrcxz @+0 99 | let inst = [0xe3, 0x00]; 100 | let addr = 0x40_0000; 101 | let new_inst = move_inst(&inst, addr, addr + 0x1_0000_0000); 102 | // jrcxz @+2 103 | // jmp @+6 104 | // jmp [rip@400002] 105 | // jmp @+9 106 | assert_eq!( 107 | new_inst, 108 | [ 109 | 0xe3, 0x02, 0xeb, 0x06, 0xff, 0x25, 0x06, 0x00, 0x00, 0x00, 0xe9, 0x09, 0x00, 0x00, 110 | 0x00, 0xcc, 0x02, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00 111 | ] 112 | ); 113 | } 114 | 115 | #[test] 116 | fn test_move_inst_long_5() { 117 | // call @+0 118 | let inst = [0xe8, 0, 0, 0, 0]; 119 | let addr = 0x40_0000; 120 | let new_inst = move_inst(&inst, addr, addr + 0x1_0000_0000); 121 | // call [rip@400005] 122 | // jmp @+13 123 | assert_eq!( 124 | new_inst, 125 | [ 126 | 0xff, 0x15, 0x0a, 0x00, 0x00, 0x00, 0xe9, 0x0d, 0x00, 0x00, 0x00, 0xcc, 0xcc, 0xcc, 127 | 0xcc, 0xcc, 0x05, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00 128 | ] 129 | ); 130 | } 131 | 132 | #[test] 133 | fn test_move_inst_long_6() { 134 | // call [rip@400006] 135 | let inst = [0xff, 0x15, 0, 0, 0, 0]; 136 | let addr = 0x40_0000; 137 | let new_inst = move_inst(&inst, addr, addr + 0x1_0000_0000); 138 | // mov [rsp-0x18], rax 139 | // mov rax, 400006 140 | // push 400024 141 | // mov dword ptr [rsp+4], 1 142 | // push qword ptr [rax] 143 | // mov rax, [rsp-8] 144 | // ret 145 | assert_eq!( 146 | new_inst, 147 | [ 148 | 0x48, 0x89, 0x44, 0x24, 0xe8, 0x48, 0xb8, 0x06, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 149 | 0x00, 0x68, 0x24, 0x00, 0x40, 0x00, 0xc7, 0x44, 0x24, 0x04, 0x01, 0x00, 0x00, 0x00, 150 | 0xff, 0x30, 0x48, 0x8b, 0x44, 0x24, 0xf8, 0xc3 151 | ] 152 | ); 153 | } 154 | 155 | #[test] 156 | fn test_move_inst_long_7() { 157 | // lea r11, [rip@400007] 158 | let inst = [0x4c, 0x8d, 0x1d, 0, 0, 0, 0]; 159 | let addr = 0x40_0000; 160 | let new_inst = move_inst(&inst, addr, addr + 0x1_0000_0000); 161 | // mov r11, 400007 162 | assert_eq!( 163 | new_inst, 164 | [0x49, 0xbb, 0x07, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00] 165 | ); 166 | } 167 | 168 | #[test] 169 | fn test_move_inst_long_8() { 170 | // add dword ptr [rip@400006], ebx 171 | let inst = [0x01, 0x1d, 0, 0, 0, 0]; 172 | let addr = 0x40_0000; 173 | let new_inst = move_inst(&inst, addr, addr + 0x1_0000_0000); 174 | // mov [rsp-0x10], r8 175 | // mov r8, 400006 176 | // add [r8], ebx 177 | // mov r8, [rsp-0x10] 178 | assert_eq!( 179 | new_inst, 180 | [ 181 | 0x4c, 0x89, 0x44, 0x24, 0xf0, 0x49, 0xb8, 0x06, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 182 | 0x00, 0x41, 0x01, 0x18, 0x4c, 0x8b, 0x44, 0x24, 0xf0 183 | ] 184 | ); 185 | } 186 | 187 | #[test] 188 | fn test_move_inst_long_9() { 189 | // push qword ptr [rip@400006] 190 | let inst = [0xff, 0x35, 0, 0, 0, 0]; 191 | let addr = 0x40_0000; 192 | let new_inst = move_inst(&inst, addr, addr + 0x1_0000_0000); 193 | // mov [rsp-0x10], rbx 194 | // mov rbx, 400006 195 | // push [rbx] 196 | // mov rbx, [rsp-8] 197 | assert_eq!( 198 | new_inst, 199 | [ 200 | 0x48, 0x89, 0x5c, 0x24, 0xf0, 0x48, 0xbb, 0x06, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 201 | 0x00, 0xff, 0x33, 0x48, 0x8b, 0x5c, 0x24, 0xf8 202 | ] 203 | ); 204 | } 205 | 206 | #[test] 207 | fn test_move_inst_long_all() { 208 | let inst = [ 209 | 0x74, 0x09, 0x48, 0x8B, 0x4D, 0x70, 0xE8, 0x72, 0x15, 0xF4, 0xFF, 0x8B, 0x1D, 0xEC, 0xFF, 210 | 0xFF, 0xFF, 211 | ]; 212 | let addr = 0x7fff_b81c_0a03; 213 | let new_inst = move_inst(&inst, addr, 0x400000); 214 | assert_eq!( 215 | new_inst, 216 | [ 217 | 0x75, 0x06, 0xff, 0x25, 0x28, 0x00, 0x00, 0x00, 0x48, 0x8b, 0x4d, 0x70, 0xff, 0x15, 218 | 0x26, 0x00, 0x00, 0x00, 0x4c, 0x89, 0x44, 0x24, 0xf0, 0x49, 0xb8, 0x00, 0x0a, 0x1c, 219 | 0xb8, 0xff, 0x7f, 0x00, 0x00, 0x41, 0x8b, 0x18, 0x4c, 0x8b, 0x44, 0x24, 0xf0, 0xe9, 220 | 0x12, 0x00, 0x00, 0x00, 0xcc, 0xcc, 0x12, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 221 | 0x80, 0x1f, 0x10, 0xb8, 0xff, 0x7f, 0x00, 0x00 222 | ] 223 | ); 224 | } 225 | 226 | #[cfg(test)] 227 | #[inline(never)] 228 | // 5 arguments to ensure using stack instead of registers to pass parameter(s) 229 | extern "win64" fn foo(x: u64, _: u64, _: u64, _: u64, y: u64) -> u64 { 230 | println!("original foo, x:{}, y:{}", x, y); 231 | x * x + y 232 | } 233 | #[cfg(test)] 234 | unsafe extern "win64" fn on_foo(reg: *mut Registers, old_func: usize, user_data: usize) -> usize { 235 | let old_func = 236 | std::mem::transmute:: u64>(old_func); 237 | let arg_y = ((*reg).rsp + 0x28) as *const u64; 238 | old_func((*reg).rcx, 0, 0, 0, *arg_y) as usize + user_data 239 | } 240 | #[test] 241 | fn test_hook_function() { 242 | assert_eq!(foo(5, 0, 0, 0, 3), 28); 243 | let hooker = Hooker::new( 244 | foo as usize, 245 | HookType::Retn(on_foo), 246 | CallbackOption::None, 247 | 100, 248 | HookFlags::empty(), 249 | ); 250 | let info = unsafe { hooker.hook().unwrap() }; 251 | assert_eq!(foo(5, 0, 0, 0, 3), 128); 252 | unsafe { info.unhook().unwrap() }; 253 | assert_eq!(foo(5, 0, 0, 0, 3), 28); 254 | } 255 | -------------------------------------------------------------------------------- /src/x86.rs: -------------------------------------------------------------------------------- 1 | use bitflags::bitflags; 2 | use iced_x86::{ 3 | BlockEncoder, BlockEncoderOptions, Decoder, DecoderOptions, Instruction, InstructionBlock, 4 | }; 5 | use std::io::{Cursor, Seek, SeekFrom, Write}; 6 | use std::slice; 7 | 8 | #[cfg(windows)] 9 | use core::ffi::c_void; 10 | #[cfg(windows)] 11 | use windows_sys::Win32::Foundation::GetLastError; 12 | #[cfg(windows)] 13 | use windows_sys::Win32::System::Memory::VirtualProtect; 14 | 15 | #[cfg(unix)] 16 | use libc::{__errno_location, c_void, mprotect, sysconf}; 17 | 18 | use crate::err::HookError; 19 | 20 | const MAX_INST_LEN: usize = 15; 21 | const JMP_INST_SIZE: usize = 5; 22 | 23 | /// This is the routine used in a `jmp-back hook`, which means the EIP will jump back to the 24 | /// original position after the routine has finished running. 25 | /// 26 | /// # Arguments 27 | /// 28 | /// * `regs` - The registers 29 | /// * `user_data` - User data that was previously passed to [`Hooker::new`]. 30 | pub type JmpBackRoutine = unsafe extern "cdecl" fn(regs: *mut Registers, user_data: usize); 31 | 32 | /// This is the routine used in a `function hook`, which means the routine will replace the 33 | /// original function and the EIP will `retn` directly instead of jumping back. 34 | /// Note that the address being hooked must be the start of a function. 35 | /// 36 | /// # Parameters 37 | /// 38 | /// * `regs` - The registers. 39 | /// * `ori_func_ptr` - The original function pointer. Call this after converting it to the original function type. 40 | /// * `user_data` - User data that was previously passed to [`Hooker::new`]. 41 | /// 42 | /// # Return value 43 | /// 44 | /// Returns the new return value of the replaced function. 45 | pub type RetnRoutine = 46 | unsafe extern "cdecl" fn(regs: *mut Registers, ori_func_ptr: usize, user_data: usize) -> usize; 47 | 48 | /// This is the routine used in a `jmp-addr hook`, which means the EIP will jump to the specified 49 | /// address after the routine has finished running. 50 | /// 51 | /// # Parameters 52 | /// 53 | /// * `regs` - The registers. 54 | /// * `ori_func_ptr` - The original function pointer. Call this after converting it to the original function type. 55 | /// * `user_data` - User data that was previously passed to [`Hooker::new`]. 56 | pub type JmpToAddrRoutine = 57 | unsafe extern "cdecl" fn(regs: *mut Registers, ori_func_ptr: usize, src_addr: usize); 58 | 59 | /// This is the routine used in a `jmp-ret hook`, which means the EIP will jump to the return 60 | /// value of the routine. 61 | /// 62 | /// # Parameters 63 | /// 64 | /// * `regs` - The registers. 65 | /// * `ori_func_ptr` - The original function pointer. Call this after converting it to the original function type. 66 | /// * `user_data` - User data that was previously passed to [`Hooker::new`]. 67 | /// 68 | /// # Return value 69 | /// 70 | /// Returns the address you want to jump to. 71 | pub type JmpToRetRoutine = 72 | unsafe extern "cdecl" fn(regs: *mut Registers, ori_func_ptr: usize, src_addr: usize) -> usize; 73 | 74 | /// The hooking type. 75 | pub enum HookType { 76 | /// Used in a jmp-back hook 77 | JmpBack(JmpBackRoutine), 78 | 79 | /// Used in a function hook. The first element is the mnemonic of the `retn` 80 | /// instruction. 81 | Retn(usize, RetnRoutine), 82 | 83 | /// Used in a jmp-addr hook. The first element is the destination address 84 | JmpToAddr(usize, JmpToAddrRoutine), 85 | 86 | /// Used in a jmp-ret hook. 87 | JmpToRet(JmpToRetRoutine), 88 | } 89 | 90 | /// The common registers. 91 | #[repr(C)] 92 | #[derive(Debug)] 93 | pub struct Registers { 94 | /// The flags register. 95 | pub eflags: u32, 96 | /// The edi register. 97 | pub edi: u32, 98 | /// The esi register. 99 | pub esi: u32, 100 | /// The ebp register. 101 | pub ebp: u32, 102 | /// The esp register. 103 | pub esp: u32, 104 | /// The ebx register. 105 | pub ebx: u32, 106 | /// The edx register. 107 | pub edx: u32, 108 | /// The ecx register. 109 | pub ecx: u32, 110 | /// The eax register. 111 | pub eax: u32, 112 | } 113 | 114 | impl Registers { 115 | /// Get the value by the index from register `esp`. 116 | /// 117 | /// # Parameters 118 | /// 119 | /// * cnt - The index of the arguments. 120 | /// 121 | /// # Safety 122 | /// 123 | /// Process may crash if register `esp` does not point to a valid stack. 124 | #[must_use] 125 | pub unsafe fn get_arg(&self, cnt: usize) -> u32 { 126 | *((self.esp as usize + cnt * 4) as *mut u32) 127 | } 128 | } 129 | 130 | /// The trait which is called before and after the modifying of the `jmp` instruction. 131 | /// Usually is used to suspend and resume all other threads, to avoid instruction colliding. 132 | pub trait ThreadCallback { 133 | /// the callback before modifying `jmp` instruction, should return true if success. 134 | fn pre(&self) -> bool; 135 | /// the callback after modifying `jmp` instruction 136 | fn post(&self); 137 | } 138 | 139 | /// Option for thread callback 140 | pub enum CallbackOption { 141 | /// Valid callback 142 | Some(Box), 143 | /// No callback 144 | None, 145 | } 146 | 147 | bitflags! { 148 | /// Hook flags 149 | pub struct HookFlags:u32 { 150 | /// If set, will not modify the memory protection of the destination address 151 | const NOT_MODIFY_MEMORY_PROTECT = 0x1; 152 | } 153 | } 154 | 155 | /// The entry struct in ilhook. 156 | /// Please read the main doc to view usage. 157 | pub struct Hooker { 158 | addr: usize, 159 | hook_type: HookType, 160 | thread_cb: CallbackOption, 161 | flags: HookFlags, 162 | user_data: usize, 163 | } 164 | 165 | /// The hook result returned by `Hooker::hook`. 166 | pub struct HookPoint { 167 | addr: usize, 168 | trampoline: Box<[u8; 100]>, 169 | trampoline_prot: u32, 170 | origin: Vec, 171 | thread_cb: CallbackOption, 172 | flags: HookFlags, 173 | } 174 | 175 | #[cfg(not(target_arch = "x86"))] 176 | fn env_lock() { 177 | panic!("This crate should only be used in arch x86_32!") 178 | } 179 | #[cfg(target_arch = "x86")] 180 | fn env_lock() {} 181 | 182 | impl Hooker { 183 | /// Create a new Hooker. 184 | /// 185 | /// # Parameters 186 | /// 187 | /// * `addr` - The being-hooked address. 188 | /// * `hook_type` - The hook type and callback routine. 189 | /// * `thread_cb` - The callbacks before and after hooking. 190 | /// * `flags` - Hook flags 191 | #[must_use] 192 | pub fn new( 193 | addr: usize, 194 | hook_type: HookType, 195 | thread_cb: CallbackOption, 196 | user_data: usize, 197 | flags: HookFlags, 198 | ) -> Self { 199 | env_lock(); 200 | Self { 201 | addr, 202 | hook_type, 203 | thread_cb, 204 | user_data, 205 | flags, 206 | } 207 | } 208 | 209 | /// Consumes self and execute hooking. Return the `HookPoint`. 210 | /// 211 | /// # Safety 212 | /// 213 | /// Process may crash (instead of panic!) if: 214 | /// 215 | /// 1. addr is not an accessible memory address, or is not long enough. 216 | /// 2. addr points to an incorrect position. (At the middle of an instruction, or where after it other instructions may jump to) 217 | /// 3. Wrong Retn-val if `hook_type` is `HookType::Retn`. i.e. A `cdecl` function with non-zero retn-val, or a `stdcall` function with wrong retn-val. 218 | /// 4. Set `NOT_MODIFY_MEMORY_PROTECT` where it should not be set. 219 | /// 5. hook or unhook from 2 or more threads at the same time without `HookFlags::NOT_MODIFY_MEMORY_PROTECT`. Because of memory protection colliding. 220 | /// 6. Other unpredictable errors. 221 | pub unsafe fn hook(self) -> Result { 222 | let (moving_insts, origin) = get_moving_insts(self.addr)?; 223 | let trampoline = 224 | generate_trampoline(&self, moving_insts, origin.len() as u8, self.user_data)?; 225 | let trampoline_prot = modify_mem_protect(trampoline.as_ptr() as usize, trampoline.len())?; 226 | if !self.flags.contains(HookFlags::NOT_MODIFY_MEMORY_PROTECT) { 227 | let old_prot = modify_mem_protect(self.addr, JMP_INST_SIZE)?; 228 | let ret = modify_jmp_with_thread_cb(&self, trampoline.as_ptr() as usize); 229 | recover_mem_protect(self.addr, JMP_INST_SIZE, old_prot); 230 | ret?; 231 | } else { 232 | modify_jmp_with_thread_cb(&self, trampoline.as_ptr() as usize)?; 233 | } 234 | Ok(HookPoint { 235 | addr: self.addr, 236 | trampoline, 237 | trampoline_prot, 238 | origin, 239 | thread_cb: self.thread_cb, 240 | flags: self.flags, 241 | }) 242 | } 243 | } 244 | 245 | impl HookPoint { 246 | /// Consume self and unhook the address. 247 | pub unsafe fn unhook(self) -> Result<(), HookError> { 248 | self.unhook_by_ref() 249 | } 250 | 251 | fn unhook_by_ref(&self) -> Result<(), HookError> { 252 | let ret: Result<(), HookError>; 253 | if !self.flags.contains(HookFlags::NOT_MODIFY_MEMORY_PROTECT) { 254 | let old_prot = modify_mem_protect(self.addr, JMP_INST_SIZE)?; 255 | ret = recover_jmp_with_thread_cb(self); 256 | recover_mem_protect(self.addr, JMP_INST_SIZE, old_prot); 257 | } else { 258 | ret = recover_jmp_with_thread_cb(self) 259 | } 260 | recover_mem_protect( 261 | self.trampoline.as_ptr() as usize, 262 | self.trampoline.len(), 263 | self.trampoline_prot, 264 | ); 265 | ret 266 | } 267 | } 268 | 269 | // When the HookPoint drops, it should unhook automatically. 270 | impl Drop for HookPoint { 271 | fn drop(&mut self) { 272 | self.unhook_by_ref().unwrap_or_default(); 273 | } 274 | } 275 | 276 | fn get_moving_insts(addr: usize) -> Result<(Vec, Vec), HookError> { 277 | let code_slice = 278 | unsafe { slice::from_raw_parts(addr as *const u8, MAX_INST_LEN * JMP_INST_SIZE) }; 279 | let mut decoder = Decoder::new(32, code_slice, DecoderOptions::NONE); 280 | decoder.set_ip(addr as u64); 281 | 282 | let mut total_bytes = 0; 283 | let mut ori_insts: Vec = vec![]; 284 | for inst in &mut decoder { 285 | if inst.is_invalid() { 286 | return Err(HookError::Disassemble); 287 | } 288 | ori_insts.push(inst); 289 | total_bytes += inst.len(); 290 | if total_bytes >= JMP_INST_SIZE { 291 | break; 292 | } 293 | } 294 | 295 | Ok((ori_insts, code_slice[0..decoder.position()].into())) 296 | } 297 | 298 | #[cfg(windows)] 299 | fn modify_mem_protect(addr: usize, len: usize) -> Result { 300 | let mut old_prot: u32 = 0; 301 | let old_prot_ptr = std::ptr::addr_of_mut!(old_prot); 302 | // PAGE_EXECUTE_READWRITE = 0x40 303 | let ret = unsafe { VirtualProtect(addr as *const c_void, len, 0x40, old_prot_ptr) }; 304 | if ret == 0 { 305 | Err(HookError::MemoryProtect(unsafe { GetLastError() })) 306 | } else { 307 | Ok(old_prot) 308 | } 309 | } 310 | 311 | #[cfg(unix)] 312 | fn modify_mem_protect(addr: usize, len: usize) -> Result { 313 | let page_size = unsafe { sysconf(30) }; //_SC_PAGESIZE == 30 314 | if len > page_size.try_into().unwrap() { 315 | Err(HookError::InvalidParameter) 316 | } else { 317 | //(PROT_READ | PROT_WRITE | PROT_EXEC) == 7 318 | let ret = unsafe { 319 | mprotect( 320 | (addr & !(page_size as usize - 1)) as *mut c_void, 321 | page_size as usize, 322 | 7, 323 | ) 324 | }; 325 | if ret != 0 { 326 | let err = unsafe { *(__errno_location()) }; 327 | Err(HookError::MemoryProtect(err as u32)) 328 | } else { 329 | // it's too complex to get the original memory protection 330 | Ok(7) 331 | } 332 | } 333 | } 334 | 335 | #[cfg(windows)] 336 | fn recover_mem_protect(addr: usize, len: usize, old: u32) { 337 | let mut old_prot: u32 = 0; 338 | let old_prot_ptr = std::ptr::addr_of_mut!(old_prot); 339 | unsafe { VirtualProtect(addr as *const c_void, len, old, old_prot_ptr) }; 340 | } 341 | 342 | #[cfg(unix)] 343 | fn recover_mem_protect(addr: usize, _: usize, old: u32) { 344 | let page_size = unsafe { sysconf(30) }; //_SC_PAGESIZE == 30 345 | unsafe { 346 | mprotect( 347 | (addr & !(page_size as usize - 1)) as *mut c_void, 348 | page_size as usize, 349 | old as i32, 350 | ) 351 | }; 352 | } 353 | 354 | fn write_relative_off( 355 | buf: &mut T, 356 | base_addr: u32, 357 | dst_addr: u32, 358 | ) -> Result<(), HookError> { 359 | let dst_addr = dst_addr as i32; 360 | let cur_pos = buf.stream_position().unwrap() as i32; 361 | let call_off = dst_addr - (base_addr as i32 + cur_pos + 4); 362 | buf.write(&call_off.to_le_bytes())?; 363 | Ok(()) 364 | } 365 | 366 | fn move_code_to_addr(ori_insts: &Vec, dest_addr: u32) -> Result, HookError> { 367 | let block = InstructionBlock::new(ori_insts, u64::from(dest_addr)); 368 | let encoded = BlockEncoder::encode(32, block, BlockEncoderOptions::NONE) 369 | .map_err(|_| HookError::MoveCode)?; 370 | Ok(encoded.code_buffer) 371 | } 372 | 373 | fn write_ori_func_addr(buf: &mut T, ori_func_addr_off: u32, ori_func_off: u32) { 374 | let pos = buf.stream_position().unwrap(); 375 | buf.seek(SeekFrom::Start(u64::from(ori_func_addr_off))) 376 | .unwrap(); 377 | buf.write(&ori_func_off.to_le_bytes()).unwrap(); 378 | buf.seek(SeekFrom::Start(pos)).unwrap(); 379 | } 380 | 381 | fn generate_jmp_back_trampoline( 382 | buf: &mut T, 383 | trampoline_base_addr: u32, 384 | moving_code: &Vec, 385 | ori_addr: u32, 386 | cb: JmpBackRoutine, 387 | ori_len: u8, 388 | user_data: usize, 389 | ) -> Result<(), HookError> { 390 | // push user_data 391 | buf.write(&[0x68])?; 392 | buf.write(&user_data.to_le_bytes())?; 393 | 394 | // push ebp (Registers) 395 | // call XXXX (dest addr) 396 | buf.write(&[0x55, 0xe8])?; 397 | write_relative_off(buf, trampoline_base_addr, cb as u32)?; 398 | 399 | // add esp, 0x8 400 | buf.write(&[0x83, 0xc4, 0x08])?; 401 | // popfd 402 | // popad 403 | buf.write(&[0x9d, 0x61])?; 404 | 405 | let cur_pos = buf.stream_position().unwrap() as u32; 406 | buf.write(&move_code_to_addr( 407 | moving_code, 408 | trampoline_base_addr + cur_pos, 409 | )?)?; 410 | // jmp back 411 | buf.write(&[0xe9])?; 412 | write_relative_off(buf, trampoline_base_addr, ori_addr + u32::from(ori_len)) 413 | } 414 | 415 | fn generate_retn_trampoline( 416 | buf: &mut T, 417 | trampoline_base_addr: u32, 418 | moving_code: &Vec, 419 | ori_addr: u32, 420 | retn_val: u16, 421 | cb: RetnRoutine, 422 | ori_len: u8, 423 | user_data: usize, 424 | ) -> Result<(), HookError> { 425 | // push user_data 426 | buf.write(&[0x68])?; 427 | buf.write(&user_data.to_le_bytes())?; 428 | 429 | // push XXXX (original function addr) 430 | // push ebp (Registers) 431 | // call XXXX (dest addr) 432 | let ori_func_addr_off = buf.stream_position().unwrap() + 1; 433 | buf.write(&[0x68, 0, 0, 0, 0, 0x55, 0xe8])?; 434 | write_relative_off(buf, trampoline_base_addr, cb as u32)?; 435 | 436 | // add esp, 0xc 437 | buf.write(&[0x83, 0xc4, 0x0c])?; 438 | // mov [esp+20h], eax 439 | buf.write(&[0x89, 0x44, 0x24, 0x20])?; 440 | // popfd 441 | // popad 442 | buf.write(&[0x9d, 0x61])?; 443 | if retn_val == 0 { 444 | // retn 445 | buf.write(&[0xc3])?; 446 | } else { 447 | // retn XX 448 | buf.write(&[0xc2])?; 449 | buf.write(&retn_val.to_le_bytes())?; 450 | } 451 | let ori_func_off = buf.stream_position().unwrap() as u32; 452 | write_ori_func_addr( 453 | buf, 454 | ori_func_addr_off as u32, 455 | trampoline_base_addr + ori_func_off, 456 | ); 457 | 458 | let cur_pos = buf.stream_position().unwrap() as u32; 459 | buf.write(&move_code_to_addr( 460 | moving_code, 461 | trampoline_base_addr + cur_pos, 462 | )?)?; 463 | 464 | // jmp ori_addr 465 | buf.write(&[0xe9])?; 466 | write_relative_off(buf, trampoline_base_addr, ori_addr + u32::from(ori_len)) 467 | } 468 | 469 | fn generate_jmp_addr_trampoline( 470 | buf: &mut T, 471 | trampoline_base_addr: u32, 472 | moving_code: &Vec, 473 | ori_addr: u32, 474 | dest_addr: u32, 475 | cb: JmpToAddrRoutine, 476 | ori_len: u8, 477 | user_data: usize, 478 | ) -> Result<(), HookError> { 479 | // push user_data 480 | buf.write(&[0x68])?; 481 | buf.write(&user_data.to_le_bytes())?; 482 | 483 | // push XXXX (original function addr) 484 | // push ebp (Registers) 485 | // call XXXX (dest addr) 486 | let ori_func_addr_off = buf.stream_position().unwrap() + 1; 487 | buf.write(&[0x68, 0, 0, 0, 0, 0x55, 0xe8])?; 488 | write_relative_off(buf, trampoline_base_addr, cb as u32)?; 489 | 490 | // add esp, 0xc 491 | buf.write(&[0x83, 0xc4, 0x0c])?; 492 | // popfd 493 | // popad 494 | buf.write(&[0x9d, 0x61])?; 495 | // jmp back 496 | buf.write(&[0xe9])?; 497 | write_relative_off(buf, trampoline_base_addr, dest_addr + u32::from(ori_len))?; 498 | 499 | let ori_func_off = buf.stream_position().unwrap() as u32; 500 | write_ori_func_addr( 501 | buf, 502 | ori_func_addr_off as u32, 503 | trampoline_base_addr + ori_func_off, 504 | ); 505 | 506 | let cur_pos = buf.stream_position().unwrap() as u32; 507 | buf.write(&move_code_to_addr( 508 | moving_code, 509 | trampoline_base_addr + cur_pos, 510 | )?)?; 511 | 512 | // jmp ori_addr 513 | buf.write(&[0xe9])?; 514 | write_relative_off(buf, trampoline_base_addr, ori_addr + u32::from(ori_len)) 515 | } 516 | 517 | fn generate_jmp_ret_trampoline( 518 | buf: &mut T, 519 | trampoline_base_addr: u32, 520 | moving_code: &Vec, 521 | ori_addr: u32, 522 | cb: JmpToRetRoutine, 523 | ori_len: u8, 524 | user_data: usize, 525 | ) -> Result<(), HookError> { 526 | // push user_data 527 | buf.write(&[0x68])?; 528 | buf.write(&user_data.to_le_bytes())?; 529 | 530 | // push XXXX (original function addr) 531 | // push ebp (Registers) 532 | // call XXXX (dest addr) 533 | let ori_func_addr_off = buf.stream_position().unwrap() + 1; 534 | buf.write(&[0x68, 0, 0, 0, 0, 0x55, 0xe8])?; 535 | write_relative_off(buf, trampoline_base_addr, cb as u32)?; 536 | 537 | // add esp, 0xc 538 | buf.write(&[0x83, 0xc4, 0x0c])?; 539 | // mov [esp-4], eax 540 | buf.write(&[0x89, 0x44, 0x24, 0xfc])?; 541 | // popfd 542 | // popad 543 | buf.write(&[0x9d, 0x61])?; 544 | // jmp dword ptr [esp-0x28] 545 | buf.write(&[0xff, 0x64, 0x24, 0xd8])?; 546 | 547 | let ori_func_off = buf.stream_position().unwrap() as u32; 548 | write_ori_func_addr( 549 | buf, 550 | ori_func_addr_off as u32, 551 | trampoline_base_addr + ori_func_off, 552 | ); 553 | 554 | let cur_pos = buf.stream_position().unwrap() as u32; 555 | buf.write(&move_code_to_addr( 556 | moving_code, 557 | trampoline_base_addr + cur_pos, 558 | )?)?; 559 | 560 | // jmp ori_addr 561 | buf.write(&[0xe9])?; 562 | write_relative_off(buf, trampoline_base_addr, ori_addr + u32::from(ori_len)) 563 | } 564 | 565 | fn generate_trampoline( 566 | hooker: &Hooker, 567 | moving_code: Vec, 568 | ori_len: u8, 569 | user_data: usize, 570 | ) -> Result, HookError> { 571 | let mut raw_buffer = Box::new([0u8; 100]); 572 | let trampoline_addr = raw_buffer.as_ptr() as u32; 573 | let mut buf = Cursor::new(&mut raw_buffer[..]); 574 | 575 | // pushad 576 | // pushfd 577 | // mov ebp, esp 578 | buf.write(&[0x60, 0x9c, 0x8b, 0xec])?; 579 | 580 | match hooker.hook_type { 581 | HookType::JmpBack(cb) => generate_jmp_back_trampoline( 582 | &mut buf, 583 | trampoline_addr, 584 | &moving_code, 585 | hooker.addr as u32, 586 | cb, 587 | ori_len, 588 | user_data, 589 | ), 590 | HookType::Retn(val, cb) => generate_retn_trampoline( 591 | &mut buf, 592 | trampoline_addr, 593 | &moving_code, 594 | hooker.addr as u32, 595 | val as u16, 596 | cb, 597 | ori_len, 598 | user_data, 599 | ), 600 | HookType::JmpToAddr(dest, cb) => generate_jmp_addr_trampoline( 601 | &mut buf, 602 | trampoline_addr, 603 | &moving_code, 604 | hooker.addr as u32, 605 | dest as u32, 606 | cb, 607 | ori_len, 608 | user_data, 609 | ), 610 | HookType::JmpToRet(cb) => generate_jmp_ret_trampoline( 611 | &mut buf, 612 | trampoline_addr, 613 | &moving_code, 614 | hooker.addr as u32, 615 | cb, 616 | ori_len, 617 | user_data, 618 | ), 619 | }?; 620 | 621 | Ok(raw_buffer) 622 | } 623 | 624 | fn modify_jmp(dest_addr: usize, trampoline_addr: usize) -> Result<(), HookError> { 625 | let buf = unsafe { slice::from_raw_parts_mut(dest_addr as *mut u8, JMP_INST_SIZE) }; 626 | // jmp trampoline_addr 627 | buf[0] = 0xe9; 628 | let rel_off = trampoline_addr as i32 - (dest_addr as i32 + 5); 629 | buf[1..5].copy_from_slice(&rel_off.to_le_bytes()); 630 | Ok(()) 631 | } 632 | 633 | fn modify_jmp_with_thread_cb(hook: &Hooker, trampoline_addr: usize) -> Result<(), HookError> { 634 | if let CallbackOption::Some(cbs) = &hook.thread_cb { 635 | if !cbs.pre() { 636 | return Err(HookError::PreHook); 637 | } 638 | let ret = modify_jmp(hook.addr, trampoline_addr); 639 | cbs.post(); 640 | ret 641 | } else { 642 | modify_jmp(hook.addr, trampoline_addr) 643 | } 644 | } 645 | 646 | fn recover_jmp(dest_addr: usize, origin: &[u8]) { 647 | let buf = unsafe { slice::from_raw_parts_mut(dest_addr as *mut u8, origin.len()) }; 648 | // jmp trampoline_addr 649 | buf.copy_from_slice(origin); 650 | } 651 | 652 | fn recover_jmp_with_thread_cb(hook: &HookPoint) -> Result<(), HookError> { 653 | if let CallbackOption::Some(cbs) = &hook.thread_cb { 654 | if !cbs.pre() { 655 | return Err(HookError::PreHook); 656 | } 657 | recover_jmp(hook.addr, &hook.origin); 658 | cbs.post(); 659 | } else { 660 | recover_jmp(hook.addr, &hook.origin); 661 | } 662 | Ok(()) 663 | } 664 | 665 | #[cfg(target_arch = "x86")] 666 | mod tests { 667 | #[allow(unused_imports)] 668 | use super::*; 669 | 670 | #[cfg(test)] 671 | #[inline(never)] 672 | fn foo(x: u32) -> u32 { 673 | println!("original foo, x:{}", x); 674 | x * x 675 | } 676 | #[cfg(test)] 677 | unsafe extern "cdecl" fn on_foo( 678 | reg: *mut Registers, 679 | old_func: usize, 680 | user_data: usize, 681 | ) -> usize { 682 | let old_func = std::mem::transmute:: u32>(old_func); 683 | old_func((*reg).get_arg(1)) as usize + user_data 684 | } 685 | 686 | #[test] 687 | fn test_hook_function_cdecl() { 688 | assert_eq!(foo(5), 25); 689 | let hooker = Hooker::new( 690 | foo as usize, 691 | HookType::Retn(0, on_foo), 692 | CallbackOption::None, 693 | 100, 694 | HookFlags::empty(), 695 | ); 696 | let info = unsafe { hooker.hook().unwrap() }; 697 | assert_eq!(foo(5), 125); 698 | unsafe { info.unhook().unwrap() }; 699 | assert_eq!(foo(5), 25); 700 | } 701 | 702 | #[cfg(test)] 703 | #[inline(never)] 704 | extern "stdcall" fn foo2(x: u32) -> u32 { 705 | println!("original foo, x:{}", x); 706 | x * x 707 | } 708 | #[cfg(test)] 709 | unsafe extern "cdecl" fn on_foo2( 710 | reg: *mut Registers, 711 | old_func: usize, 712 | user_data: usize, 713 | ) -> usize { 714 | let old_func = std::mem::transmute:: u32>(old_func); 715 | old_func((*reg).get_arg(1)) as usize + user_data 716 | } 717 | #[test] 718 | fn test_hook_function_stdcall() { 719 | assert_eq!(foo2(5), 25); 720 | let hooker = Hooker::new( 721 | foo2 as usize, 722 | HookType::Retn(4, on_foo2), 723 | CallbackOption::None, 724 | 100, 725 | HookFlags::empty(), 726 | ); 727 | let info = unsafe { hooker.hook().unwrap() }; 728 | assert_eq!(foo2(5), 125); 729 | unsafe { info.unhook().unwrap() }; 730 | assert_eq!(foo2(5), 25); 731 | } 732 | } 733 | --------------------------------------------------------------------------------