walter 0.1.13

A simple Rust library for 32 and 64 bit hooking.
#![allow(clippy::needless_doctest_main)]

//! # A simple Rust library for 32 and 64 bit hooking.
//!
//! `walter` is a simple 32 and 64 bit hooking library.

mod bindings {
    windows::include_bindings!();
}

use std::{
    ffi::c_void,
    ptr::{
        write_bytes,
        copy_nonoverlapping,
    },
};
use bindings::Windows::Win32::System::Memory::{
    MEM_COMMIT,
    MEM_RELEASE,
    VirtualFree,
    MEM_RESERVE,
    VirtualAlloc,
    VirtualProtect,
    PAGE_PROTECTION_FLAGS,
    PAGE_EXECUTE_READWRITE,
};

const JUMP_CODES: [u8; 14] = [
    0xFF, 0x25, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
];

#[derive(Debug)]
pub enum HookError {
    Error(String),
}

trait Hook {
    fn unhook(&mut self) -> Result<(), HookError>;
    fn is_hooked(&self) -> bool;
}

/// A 32 bit trampoline hook.
///
/// After creating a `TrampolineHook32` by [`hook`]ing a method, it redirects the flow of execution.
///
/// The method will be unhooked when the value is dropped.
///
/// [`hook`]: #method.hook
#[derive(Debug)]
pub struct TrampolineHook32 {
    gateway: *mut c_void,
    hook: Hook32,
}

impl TrampolineHook32 {
    /// Creates a new `TrampolineHook32` which will be hooking the specified src.
    pub fn hook(src: *mut c_void, dst: *mut c_void, len: usize) -> Result<Self, HookError> {
        if len < 5 {
            return Err(HookError::Error("Len to small".to_owned()));
        }

        let gateway = unsafe {
            VirtualAlloc(
                0 as *mut c_void,
                len + 5,
                MEM_COMMIT | MEM_RESERVE,
                PAGE_EXECUTE_READWRITE
            )
        };

        unsafe { copy_nonoverlapping(src, gateway, len); }

        unsafe { *(((gateway as *mut usize) as usize + len) as *mut usize) = 0xE9; }
        unsafe {
            *(((gateway as *mut usize) as usize + len + 1) as *mut usize) =
                (((src as *mut isize) as isize - (gateway as *mut isize) as isize) - 5) as usize;
        }

        let hook = Hook32::hook(src, dst, len)?;

        Ok(Self { gateway, hook })
    }

    /// Returns the gateway containing the overridden bytes.
    pub fn gateway(&self) -> *mut c_void {
        self.gateway
    }
}

impl Hook for TrampolineHook32 {
    fn unhook(&mut self) -> Result<(), HookError> {
        if !self.is_hooked() {
            return Ok(());
        }

        let res = unsafe {
            VirtualFree(
                self.gateway,
                0,
                MEM_RELEASE
            )
        };

        if res.as_bool() {
            self.hook.unhook()?;
            Ok(())
        } else {
            Err(HookError::Error("Region not freed".to_owned()))
        }
    }

    fn is_hooked(&self) -> bool {
        self.hook.is_hooked()
    }
}

unsafe impl Sync for TrampolineHook32 { }
unsafe impl Send for TrampolineHook32 { }

impl Drop for TrampolineHook32 {
    fn drop(&mut self) {
        let _ = self.unhook();
    }
}

/// A 32 bit hook.
///
/// After creating a `Hook32` by [`hook`]ing a method, it redirects the flow of execution.
///
/// The method will be unhooked when the value is dropped.
///
/// [`hook`]: #method.hook
#[derive(Debug)]
pub struct Hook32 {
    src: *mut c_void,
    len: usize,
    orig_codes: Vec<u8>,
    hooked: bool,
}

impl Hook32 {
    /// Creates a new `Hook32` which will be hooking the specified src.
    pub fn hook(src: *mut c_void, dst: *mut c_void, len: usize) -> Result<Self, HookError> {
        if len < 5 {
            return Err(HookError::Error("Len to small".to_owned()));
        }

        let mut init_protection = PAGE_PROTECTION_FLAGS::default();
        let res = unsafe {
            VirtualProtect(
                src,
                len,
                PAGE_EXECUTE_READWRITE,
                &mut init_protection
            )
        };

        if !res.as_bool() {
            return Err(HookError::Error("Protection not changed".to_owned()));
        }

        let mut orig_codes: Vec<u8> = vec![0x90; len];
        unsafe { copy_nonoverlapping(src, orig_codes.as_mut_ptr() as *mut c_void, len); }

        unsafe { write_bytes(src, 0x90, len); }

        unsafe { *(src as *mut usize) = 0xE9; }
        unsafe {
            *(((src as *mut usize) as usize + 1) as *mut usize) =
                (((dst as *mut isize) as isize - (src as *mut isize) as isize) - 5) as usize;
        }

        let res = unsafe {
            VirtualProtect(
                src,
                len,
                init_protection,
                &mut init_protection
            )
        };

        if res.as_bool() {
            Ok(Self { src, len, orig_codes, hooked: true })
        } else {
            Err(HookError::Error("Protection not changed".to_owned()))
        }
    }
}

impl Hook for Hook32 {
    fn unhook(&mut self) -> Result<(), HookError> {
        if !self.hooked {
            return Ok(());
        }

        let mut init_protection = PAGE_PROTECTION_FLAGS::default();
        let res = unsafe {
            VirtualProtect(
                self.src,
                self.len,
                PAGE_EXECUTE_READWRITE,
                &mut init_protection
            )
        };

        if !res.as_bool() {
            return Err(HookError::Error("Protection not changed".to_owned()));
        }

        unsafe {
            copy_nonoverlapping(
                self.orig_codes.as_ptr() as *mut c_void,
                self.src,
                self.len
            );
        }

        let res = unsafe {
            VirtualProtect(
                self.src,
                self.len,
                init_protection,
                &mut init_protection
            )
        };

        if res.as_bool() {
            self.hooked = false;
            Ok(())
        } else {
            Err(HookError::Error("Protection not changed".to_owned()))
        }
    }

    fn is_hooked(&self) -> bool {
        self.hooked
    }
}

unsafe impl Sync for Hook32 { }
unsafe impl Send for Hook32 { }

impl Drop for Hook32 {
    fn drop(&mut self) {
        let _ = self.unhook();
    }
}

/// A 64 bit trampoline hook.
///
/// After creating a `TrampolineHook64` by [`hook`]ing a method, it redirects the flow of execution.
///
/// The method will be unhooked when the value is dropped.
///
/// [`hook`]: #method.hook
#[derive(Debug)]
pub struct TrampolineHook64 {
    gateway: *mut c_void,
    hook: Hook64,
}

impl TrampolineHook64 {
    /// Creates a new `TrampolineHook64` which will be hooking the specified src.
    pub fn hook(src: *mut c_void, dst: *mut c_void, len: usize) -> Result<Self, HookError> {
        if len < 14 {
            return Err(HookError::Error("Len to small".to_owned()));
        }

        let mut jump_codes = JUMP_CODES.clone();
        let jump_codes_ptr = jump_codes.as_mut_ptr() as *mut c_void;

        let gateway = unsafe {
            VirtualAlloc(
                0 as *mut c_void,
                len + jump_codes.len(),
                MEM_COMMIT | MEM_RESERVE,
                PAGE_EXECUTE_READWRITE
            )
        };

        unsafe {
            copy_nonoverlapping(
                ((&((src as usize) + len)) as *const usize) as *mut c_void,
                jump_codes_ptr.offset(6),
                8
            );
        }

        unsafe { copy_nonoverlapping(src, gateway, len); }

        unsafe {
            copy_nonoverlapping(
                jump_codes_ptr,
                ((gateway as usize) + len) as *mut c_void,
                jump_codes.len()
            );
        }

        let hook = Hook64::hook(src, dst, len)?;

        Ok(Self { gateway, hook })
    }

    pub fn gateway(&self) -> *mut c_void {
        self.gateway
    }
}

impl Hook for TrampolineHook64 {
    fn unhook(&mut self) -> Result<(), HookError> {
        if !self.is_hooked() {
            return Ok(());
        }

        let res = unsafe {
            VirtualFree(
                self.gateway,
                0,
                MEM_RELEASE
            )
        };

        if res.as_bool() {
            self.hook.unhook()?;
            Ok(())
        } else {
            Err(HookError::Error("Region not freed".to_owned()))
        }
    }

    fn is_hooked(&self) -> bool {
        self.hook.is_hooked()
    }
}

unsafe impl Sync for TrampolineHook64 { }
unsafe impl Send for TrampolineHook64 { }

impl Drop for TrampolineHook64 {
    fn drop(&mut self) {
        let _ = self.unhook();
    }
}

/// A 64 bit hook.
///
/// After creating a `Hook64` by [`hook`]ing a method, it redirects the flow of execution.
///
/// The method will be unhooked when the value is dropped.
///
/// [`hook`]: #method.hook
#[derive(Debug)]
pub struct Hook64 {
    src: *mut c_void,
    len: usize,
    orig_codes: Vec<u8>,
    hooked: bool,
}

impl Hook64 {
    /// Creates a new `Hook64` which will be hooking the specified src.
    pub fn hook(src: *mut c_void, dst: *mut c_void, len: usize) -> Result<Self, HookError> {
        if len < 14 {
            return Err(HookError::Error("Len to small".to_owned()));
        }

        let mut init_protection = PAGE_PROTECTION_FLAGS::default();
        let res = unsafe {
            VirtualProtect(
                src,
                len,
                PAGE_EXECUTE_READWRITE,
                &mut init_protection
            )
        };

        if !res.as_bool() {
            return Err(HookError::Error("Protection not changed".to_owned()));
        }

        let mut orig_codes: Vec<u8> = vec![0x90; len];
        unsafe { copy_nonoverlapping(src, orig_codes.as_mut_ptr() as *mut c_void, len); }

        unsafe { write_bytes(src, 0x90, len); }

        let mut jump_codes = JUMP_CODES.clone();
        let jump_codes_ptr = jump_codes.as_mut_ptr() as *mut c_void;

        unsafe {
            copy_nonoverlapping(
                (&(dst as usize) as *const usize) as *mut c_void,
                jump_codes_ptr.offset(6),
                8
            );
        }

        unsafe { copy_nonoverlapping(jump_codes_ptr, src, jump_codes.len()); }

        let res = unsafe {
            VirtualProtect(
                src,
                len,
                init_protection,
                &mut init_protection
            )
        };

        if res.as_bool() {
            Ok(Self { src, len, orig_codes, hooked: true })
        } else {
            Err(HookError::Error("Protection not changed".to_owned()))
        }
    }
}

impl Hook for Hook64 {
    fn unhook(&mut self) -> Result<(), HookError> {
        if !self.hooked {
            return Ok(());
        }

        let mut init_protection = PAGE_PROTECTION_FLAGS::default();
        let res = unsafe {
            VirtualProtect(
                self.src,
                self.len,
                PAGE_EXECUTE_READWRITE,
                &mut init_protection
            )
        };

        if !res.as_bool() {
            return Err(HookError::Error("Protection not changed".to_owned()));
        }

        unsafe {
            copy_nonoverlapping(
                self.orig_codes.as_ptr() as *mut c_void,
                self.src,
                self.len
            );
        }

        let res = unsafe {
            VirtualProtect(
                self.src,
                self.len,
                init_protection,
                &mut init_protection
            )
        };

        if res.as_bool() {
            self.hooked = false;
            Ok(())
        } else {
            Err(HookError::Error("Protection not changed".to_owned()))
        }
    }

    fn is_hooked(&self) -> bool {
        self.hooked
    }
}

unsafe impl Sync for Hook64 { }
unsafe impl Send for Hook64 { }

impl Drop for Hook64 {
    fn drop(&mut self) {
        let _ = self.unhook();
    }
}