#![allow(deprecated)]
#![doc = include_str!("../README.md")]
#[cfg(not(any(
all(any(target_os = "macos", target_os = "ios"), target_arch = "aarch64"),
all(target_os = "macos", target_arch = "x86_64"),
all(
any(target_os = "linux", target_os = "android"),
target_arch = "aarch64"
),
all(target_os = "linux", target_arch = "x86_64")
)))]
compile_error!(
"sighook only supports Apple aarch64/x86_64 (macOS), Apple aarch64 (iOS), Linux/Android aarch64, and Linux x86_64."
);
#[cfg(all(
feature = "patch_asm",
any(
all(target_os = "macos", target_arch = "aarch64"),
all(target_os = "macos", target_arch = "x86_64"),
all(target_os = "linux", target_arch = "aarch64"),
all(target_os = "linux", target_arch = "x86_64")
)
))]
mod asm;
mod constants;
mod context;
mod error;
mod memory;
#[cfg(target_arch = "aarch64")]
mod replay;
mod signal;
mod state;
mod trampoline;
#[cfg(all(target_arch = "x86_64", any(target_os = "linux", target_os = "macos")))]
pub use context::{
FpRegisters, HookContext, InstrumentCallback, StRegisters, StRegistersNamed, XmmRegisters,
XmmRegistersNamed, YmmHiRegisters, YmmHiRegistersNamed,
};
#[cfg(target_arch = "aarch64")]
pub use context::{
FpRegisters, HookContext, InstrumentCallback, VRegisters, VRegistersNamed, XRegisters,
XRegistersNamed,
};
pub use error::SigHookError;
#[cfg(any(
target_arch = "aarch64",
all(target_arch = "x86_64", any(target_os = "linux", target_os = "macos"))
))]
pub fn patchcode(address: u64, new_opcode: u32) -> Result<u32, SigHookError> {
#[cfg(all(target_arch = "x86_64", any(target_os = "linux", target_os = "macos")))]
{
let instruction_len = memory::instruction_width(address)? as usize;
let opcode_bytes = new_opcode.to_le_bytes();
let patch_len = effective_x86_patch_len(&opcode_bytes);
if patch_len > instruction_len {
return Err(SigHookError::PatchTooLong {
patch_len,
instruction_len,
});
}
let original4 = memory::read_bytes(address, 4)?;
let mut opcode = [0u8; 4];
opcode.copy_from_slice(&original4);
let original_opcode = u32::from_le_bytes(opcode);
let mut patch = vec![0x90u8; instruction_len];
patch[..patch_len].copy_from_slice(&opcode_bytes[..patch_len]);
let _ = memory::patch_bytes_public(address, &patch)?;
unsafe {
state::cache_original_opcode(address, original_opcode);
}
Ok(original_opcode)
}
#[cfg(target_arch = "aarch64")]
let original = memory::patch_u32(address, new_opcode)?;
#[cfg(target_arch = "aarch64")]
unsafe {
state::cache_original_opcode(address, original);
}
#[cfg(target_arch = "aarch64")]
Ok(original)
}
#[cfg(all(target_arch = "x86_64", any(target_os = "linux", target_os = "macos")))]
fn effective_x86_patch_len(opcode_bytes: &[u8; 4]) -> usize {
let mut patch_len = opcode_bytes.len();
while patch_len > 0 && opcode_bytes[patch_len - 1] == 0x90 {
patch_len -= 1;
}
patch_len.max(1)
}
#[cfg(all(
feature = "patch_asm",
any(
all(target_os = "macos", target_arch = "aarch64"),
all(target_os = "macos", target_arch = "x86_64"),
all(target_os = "linux", target_arch = "aarch64"),
all(target_os = "linux", target_arch = "x86_64")
)
))]
pub fn patch_asm(address: u64, asm: &str) -> Result<u32, SigHookError> {
#[cfg(all(target_arch = "x86_64", any(target_os = "linux", target_os = "macos")))]
{
use crate::asm::assemble_bytes;
let mut patch = assemble_bytes(address, asm)?;
let instruction_len = memory::instruction_width(address)? as usize;
if patch.len() > instruction_len {
return Err(SigHookError::PatchTooLong {
patch_len: patch.len(),
instruction_len,
});
}
let original4 = memory::read_bytes(address, 4)?;
let mut opcode = [0u8; 4];
opcode.copy_from_slice(&original4);
let original_opcode = u32::from_le_bytes(opcode);
patch.resize(instruction_len, 0x90);
let _ = memory::patch_bytes_public(address, &patch)?;
unsafe {
state::cache_original_opcode(address, original_opcode);
}
return Ok(original_opcode);
}
#[cfg(any(
all(target_os = "macos", target_arch = "aarch64"),
all(target_os = "linux", target_arch = "aarch64")
))]
{
let opcode = asm::assemble_patch_opcode(address, asm)?;
patchcode(address, opcode)
}
}
pub fn instrument(address: u64, callback: InstrumentCallback) -> Result<u32, SigHookError> {
instrument_internal(
address,
callback,
true,
false,
InstrumentInstallMode::RuntimePatch,
)
}
pub fn instrument_no_original(
address: u64,
callback: InstrumentCallback,
) -> Result<u32, SigHookError> {
instrument_internal(
address,
callback,
false,
false,
InstrumentInstallMode::RuntimePatch,
)
}
#[derive(Copy, Clone)]
enum InstrumentInstallMode {
RuntimePatch,
Prepatched,
}
fn ensure_prepatched_trap(address: u64) -> Result<(), SigHookError> {
if address == 0 {
return Err(SigHookError::InvalidAddress);
}
#[cfg(target_arch = "aarch64")]
{
let opcode = memory::read_u32(address);
if !memory::is_brk(opcode) {
return Err(SigHookError::InvalidAddress);
}
}
#[cfg(all(target_arch = "x86_64", any(target_os = "linux", target_os = "macos")))]
{
let opcode = memory::read_u8(address);
if !memory::is_int3(opcode) {
return Err(SigHookError::InvalidAddress);
}
}
Ok(())
}
pub mod prepatched {
#[cfg(target_arch = "aarch64")]
use super::state;
use super::{InstrumentCallback, InstrumentInstallMode, SigHookError, instrument_internal};
pub fn instrument(address: u64, callback: InstrumentCallback) -> Result<u32, SigHookError> {
instrument_internal(
address,
callback,
true,
false,
InstrumentInstallMode::Prepatched,
)
}
pub fn instrument_no_original(
address: u64,
callback: InstrumentCallback,
) -> Result<u32, SigHookError> {
instrument_internal(
address,
callback,
false,
false,
InstrumentInstallMode::Prepatched,
)
}
pub fn inline_hook(addr: u64, callback: InstrumentCallback) -> Result<u32, SigHookError> {
instrument_internal(
addr,
callback,
false,
true,
InstrumentInstallMode::Prepatched,
)
}
#[cfg(target_arch = "aarch64")]
pub fn cache_original_opcode(address: u64, original_opcode: u32) -> Result<(), SigHookError> {
if address == 0 || (address & 0b11) != 0 {
return Err(SigHookError::InvalidAddress);
}
unsafe {
state::cache_original_opcode(address, original_opcode);
}
Ok(())
}
}
fn instrument_internal(
address: u64,
callback: InstrumentCallback,
execute_original: bool,
return_to_caller: bool,
install_mode: InstrumentInstallMode,
) -> Result<u32, SigHookError> {
unsafe {
if let Some((bytes, len)) = state::original_bytes_by_address(address) {
#[cfg(target_arch = "aarch64")]
let original_opcode = state::cached_original_opcode_by_address(address)
.or_else(|| state::original_opcode_by_address(address))
.ok_or(SigHookError::InvalidAddress)?;
#[cfg(target_arch = "aarch64")]
let replay_plan =
replay::decode_replay_plan(address, original_opcode, execute_original);
state::register_slot(
address,
&bytes[..len as usize],
len,
callback,
#[cfg(target_arch = "aarch64")]
replay_plan,
execute_original,
return_to_caller,
matches!(install_mode, InstrumentInstallMode::RuntimePatch),
)?;
#[cfg(not(target_arch = "aarch64"))]
let original_opcode = state::cached_original_opcode_by_address(address)
.or_else(|| state::original_opcode_by_address(address))
.ok_or(SigHookError::InvalidAddress)?;
return Ok(original_opcode);
}
signal::ensure_handlers_installed()?;
let step_len: u8 = memory::instruction_width(address)?;
let (original_bytes, original_opcode, runtime_patch_installed) = match install_mode {
InstrumentInstallMode::RuntimePatch => {
#[cfg(target_arch = "aarch64")]
{
let original = memory::patch_u32(address, constants::BRK_OPCODE)?;
(original.to_le_bytes().to_vec(), original, true)
}
#[cfg(all(target_arch = "x86_64", any(target_os = "linux", target_os = "macos")))]
{
let original_bytes = memory::read_bytes(address, step_len as usize)?;
let original4 = memory::read_bytes(address, 4)?;
let mut trap_patch = vec![0x90u8; step_len as usize];
trap_patch[0] = memory::int3_opcode();
let _ = memory::patch_bytes_public(address, &trap_patch)?;
let mut opcode = [0u8; 4];
opcode.copy_from_slice(&original4);
(original_bytes, u32::from_le_bytes(opcode), true)
}
}
InstrumentInstallMode::Prepatched => {
ensure_prepatched_trap(address)?;
#[cfg(target_arch = "aarch64")]
{
if execute_original {
let original_opcode = state::cached_original_opcode_by_address(address)
.ok_or(SigHookError::UnsupportedOperation)?;
(
original_opcode.to_le_bytes().to_vec(),
original_opcode,
false,
)
} else {
let original_bytes = memory::read_bytes(address, step_len as usize)?;
let mut opcode = [0u8; 4];
opcode.copy_from_slice(&original_bytes[..4]);
(original_bytes, u32::from_le_bytes(opcode), false)
}
}
#[cfg(all(target_arch = "x86_64", any(target_os = "linux", target_os = "macos")))]
{
if execute_original {
return Err(SigHookError::UnsupportedOperation);
}
let original_bytes = memory::read_bytes(address, step_len as usize)?;
let original4 = memory::read_bytes(address, 4)?;
let mut opcode = [0u8; 4];
opcode.copy_from_slice(&original4);
(original_bytes, u32::from_le_bytes(opcode), false)
}
}
};
#[cfg(target_arch = "aarch64")]
let replay_plan = replay::decode_replay_plan(address, original_opcode, execute_original);
let register_result = state::register_slot(
address,
&original_bytes,
step_len,
callback,
#[cfg(target_arch = "aarch64")]
replay_plan,
execute_original,
return_to_caller,
runtime_patch_installed,
);
if let Err(err) = register_result {
if runtime_patch_installed {
#[cfg(target_arch = "aarch64")]
{
let mut bytes = [0u8; 4];
bytes.copy_from_slice(&original_bytes[..4]);
let original_opcode = u32::from_le_bytes(bytes);
let _ = memory::patch_u32(address, original_opcode);
}
#[cfg(all(target_arch = "x86_64", any(target_os = "linux", target_os = "macos")))]
{
let _ = memory::patch_bytes_public(address, &original_bytes);
}
}
return Err(err);
}
if runtime_patch_installed {
state::cache_original_opcode(address, original_opcode);
}
Ok(original_opcode)
}
}
pub fn inline_hook(addr: u64, callback: InstrumentCallback) -> Result<u32, SigHookError> {
instrument_internal(
addr,
callback,
false,
true,
InstrumentInstallMode::RuntimePatch,
)
}
pub fn inline_hook_jump(addr: u64, replace_fn: u64) -> Result<u32, SigHookError> {
#[cfg(target_arch = "aarch64")]
{
let patch = match memory::encode_b(addr, replace_fn) {
Ok(b_opcode) => b_opcode.to_le_bytes().to_vec(),
Err(SigHookError::BranchOutOfRange) => {
let mut bytes = [0u8; 16];
bytes[0..4].copy_from_slice(&constants::LDR_X16_LITERAL_8.to_le_bytes());
bytes[4..8].copy_from_slice(&constants::BR_X16.to_le_bytes());
bytes[8..16].copy_from_slice(&replace_fn.to_le_bytes());
bytes.to_vec()
}
Err(err) => return Err(err),
};
let original = memory::read_bytes(addr, 16)?;
let inserted = unsafe { state::cache_inline_patch(addr, &original)? };
if let Err(err) = memory::patch_bytes_public(addr, &patch) {
if inserted {
unsafe {
state::remove_inline_patch(addr);
}
}
return Err(err);
}
if original.len() < 4 {
return Err(SigHookError::InvalidAddress);
}
let mut opcode = [0u8; 4];
opcode.copy_from_slice(&original[..4]);
let original_opcode = u32::from_le_bytes(opcode);
unsafe {
state::cache_original_opcode(addr, original_opcode);
}
Ok(original_opcode)
}
#[cfg(all(target_arch = "x86_64", any(target_os = "linux", target_os = "macos")))]
{
let patch = if let Ok(jmp) = memory::encode_jmp_rel32(addr, replace_fn) {
jmp.to_vec()
} else {
memory::encode_absolute_jump(replace_fn).to_vec()
};
let original = memory::read_bytes(addr, memory::encode_absolute_jump(0).len())?;
let inserted = unsafe { state::cache_inline_patch(addr, &original)? };
if let Err(err) = memory::patch_bytes_public(addr, &patch) {
if inserted {
unsafe {
state::remove_inline_patch(addr);
}
}
return Err(err);
}
if original.len() < 4 {
return Err(SigHookError::InvalidAddress);
}
let mut opcode = [0u8; 4];
opcode.copy_from_slice(&original[..4]);
let original_opcode = u32::from_le_bytes(opcode);
unsafe {
state::cache_original_opcode(addr, original_opcode);
}
Ok(original_opcode)
}
}
pub fn unhook(address: u64) -> Result<(), SigHookError> {
if address == 0 {
return Err(SigHookError::InvalidAddress);
}
unsafe {
if let Some(slot) = state::slot_by_address(address) {
if slot.original_len == 0 {
return Err(SigHookError::InvalidAddress);
}
if slot.runtime_patch_installed {
memory::patch_bytes_public(
address,
&slot.original_bytes[..slot.original_len as usize],
)?;
}
if let Some(removed_slot) = state::remove_slot(address) {
if removed_slot.trampoline_pc != 0 {
trampoline::free_original_trampoline(removed_slot.trampoline_pc);
}
}
state::remove_cached_original_opcode(address);
return Ok(());
}
if let Some((bytes, len)) = state::inline_patch_by_address(address) {
if len == 0 {
return Err(SigHookError::InvalidAddress);
}
memory::patch_bytes_public(address, &bytes[..len as usize])?;
state::remove_inline_patch(address);
state::remove_cached_original_opcode(address);
return Ok(());
}
}
Err(SigHookError::HookNotFound)
}
pub fn original_opcode(address: u64) -> Option<u32> {
unsafe {
state::cached_original_opcode_by_address(address)
.or_else(|| state::original_opcode_by_address(address))
}
}
pub fn patch_bytes(address: u64, bytes: &[u8]) -> Result<Vec<u8>, SigHookError> {
let original = memory::patch_bytes_public(address, bytes)?;
if original.len() >= 4 {
let mut opcode = [0u8; 4];
opcode.copy_from_slice(&original[..4]);
let original_opcode = u32::from_le_bytes(opcode);
unsafe {
state::cache_original_opcode(address, original_opcode);
}
}
Ok(original)
}
#[cfg(test)]
mod tests {
#[cfg(all(target_arch = "x86_64", any(target_os = "linux", target_os = "macos")))]
use super::effective_x86_patch_len;
#[cfg(all(target_arch = "x86_64", any(target_os = "linux", target_os = "macos")))]
#[test]
fn effective_x86_patch_len_trims_trailing_nops() {
assert_eq!(effective_x86_patch_len(&[0x0f, 0xaf, 0xc2, 0x90]), 3);
assert_eq!(effective_x86_patch_len(&[0x90, 0x90, 0x90, 0x90]), 1);
assert_eq!(effective_x86_patch_len(&[0x0f, 0xaf, 0xc2, 0x00]), 4);
}
}