<h1 align="center">Prehook</h1>
<p align="center">
  <i>
    A lightweight, flexible Rust library for x64 hooking using `LD_PRELOAD`. Built on top of the `ilhook` engine, `prehook` provides a high-level, declarative API for intercepting and modifying binary behavior at runtime.
  </i><br />
  <img src="assets/prehook.png" alt="Logo" />
  <br />
  <b>
    Binary madness
  </b>
</p>

<p align="center">
    <a href="https://github.com/OmentaElvis/prehook-rs/actions/workflows/release.yml">
        <img src="https://github.com/OmentaElvis/prehook-rs/actions/workflows/release.yml/badge.svg" alt="Build Status" />
    </a>
    <a href="https://opensource.org/licenses/MIT">
        <img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="MIT License" />
    </a>
</p>
<p align="center">
    <a href="https://rust-lang.org/learn/get-started/">
      <img src="https://img.shields.io/badge/rust-%23000000.svg?style=for-the-badge&logo=rust&logoColor=white" />
    </a>
    <a href="https://crates.io/crates/prehook">
        <img alt="Crates.io Version" src="https://img.shields.io/crates/v/prehook">
    </a>
</p>

## Features

- **[FunctionHook](#1-function-hook-retn-or-functionhook)**: Intercept at function entry, replace entirely
- **[InlineHook](#2-inline-hook-jmp_back-or-inlinehook)**: Intercept inline, modify registers, continue execution
- **[DynamicRedirect](#3-dynamic-redirection-jmp_to_ret-or-dynamicredirect)**: Conditional redirection via Target enum
- **[StaticRedirect](#4-static-redirection-jmp_to_addr-or-staticredirect)**: Unconditional jump to fixed address
- **[Library interpose](#5-interpose)**: Override shared library functions via LD_PRELOAD
- **call_original!() macro**: Call the original function from hooks
- **[Target DSL](#target-specification)**: Use symbols instead of raw offsets
- **[Reentrant Guard](#6-reentrant-guard)**: Prevent infinite recursion with guard = true
- **Symbol Resolution**: Automatic lookup via dlsym with fallback to symbol table parsing
- **Hook Management**: Registry for tracking and uninstalling hooks

> [!NOTE]
> Current support is for linux x64. Windows and other architectures such as ARM will gradually be
> added in future updates.

## 💾 Installation

Run these in your rust project:

```bash
cargo add prehook libc ctor
```

- `libc`: For FFI with C functions
- `ctor`: Registers hooks automatically when the library loads

### Setup

**cargo.toml:**

```toml
[lib]
crate-type = ["cdylib"]

[dependencies]
prehook = "1.0.0" # check latest version
ctor = "0.6"
libc = "0.2.185"
```

The `cdylib` type is required because your hooks run as a shared library loaded via LD_PRELOAD.

**Basic lib.rs:**

```rust
use prehook::interpose;
use std::ffi::CStr;

#[interpose]
fn getenv(name_ptr: *const c_char) -> *mut c_char {
    let name = unsafe { CStr::from_ptr(name_ptr).to_str().unwrap() };

    println!("Intercepted getenv: {}", name);

    if name == "MY_SECRET" {
        return std::ffi::CString::new("secret_value")
            .unwrap()
            .into_raw();
    }

    call_original!(name_ptr)
}
```

Build and use:

```bash
cargo build --release
LD_PRELOAD=./target/release/libyourcrate.so ./target-program
```

## 🗺️ Target Specification

The `symbol`, `offset`, `target`, and `dest` attributes accept various formats for specifying addresses. Using symbols instead of raw offsets makes your hooks resilient to binary updates. If an unrelated part of the binary changes but your target function remains intact, your hook continues to work without modification.

| Format | Example | Description |
|--------|---------|------------|
| Raw hex | `0x1234` | Absolute offset from binary base address |
| Symbol | `"malloc"` | Resolve symbol via `dlsym` |
| Symbol + offset | `"main+0x20"` | Symbol address plus offset |
| Symbol - offset | `"free-0x10"` | Symbol address minus offset |
| Relative | `"+0x10"` | Offset from hook point (dest only) |

- **Raw hex offset:** Use directly as an offset from the binary's base address.

    ```rust
    #[hook(kind = "jmp_back", offset = 0x1234)]
    fn hook_at_offset(reg: &mut Registers) { }
    ```
    or

    ```rust
    #[hook(kind = "jmp_back", symbol = 0x1234)]
    fn hook_at_offset(reg: &mut Registers) { }
    ```

- **Symbol:** Resolve a symbol by name.

    ```rust
    #[hook(kind = "retn", symbol = "add")]
    fn hook_add(a: i32, b: i32) -> i32 { }
    ```

- **Symbol with offset: ** Useful when the function you want to hook is not at the function entry point.

    ```rust
    // Hook at add function + 0x20 bytes (past the prologue)
    #[hook(kind = "jmp_to_ret", target = "add+0x20")]
    fn hook_add_later(reg: &mut Registers, orig: usize) -> Target { }
    ```

    ```rust
    // Hook at some_function - 0x10 bytes (before the expected location)
    #[hook(kind = "jmp_back", target = "open_file-0x10")]
    fn hook_before_open(reg: &mut Registers) { }
    ```

- **Relative offset (dest only):** When using `jmp_to_addr`, you can specify the destination as an offset relative to the hook point.

    ```rust
    // Jump to 0x10 bytes after the hook point
    #[hook(kind = "jmp_to_addr", target = "check_flag", dest = "+0x10")]
    fn skip_check(reg: &mut Registers) { }
    ```

This is useful when you need to skip certain logic by jumping past it, without knowing the absolute address.

## 🪝Hook Types
### 1. Function Hook (`retn` or `FunctionHook`)

The most common hook type. Intercepts at the function entry point. Your hook replaces the original function entirely. Use `call_original!()` to invoke the real function, then modify the return value.

**C Example:**

```c
__attribute__((noinline))
int addition_func(int a, int b) {
    printf("original_add: a=%d b=%d\n", a, b);
    return a + b;
}

int main() {
    int result = addition_func(10, 20);
    printf("add_result: %d\n", result);
}
```

**Hook:**

```rust
#[hook(kind = "retn", symbol = "addition_func")]
fn hook_add(a: i32, b: i32) -> i32 {
    println!("hook: a={}", a);
    println!("hook: b={}", b);

    // Call the original function
    let result = call_original!(a, b);

    println!("hook: original returned {}", result);

    // Modify the result
    result + 5
}
```

**Result:**

| Input | Original Output | Hooked Output |
|-------|-----------------|--------------|
| addition_func(10, 20) | 30 | 35 |

Your hook receives the same arguments as the original. `call_original!()` forwards to the real function. You can modify the return value before returning.

### 2. Inline Hook (`jmp_back` or `InlineHook`)

Intercept an instruction and continue execution at the next instruction. Your hook runs alongside the original code without redirecting control flow.

This is useful for logging, monitoring, or modifying registers in-flight without changing the program's execution path.

**C Example:**

```c
__attribute__((noinline))
int compute(int x, int y) {
    return x + y;
}

int main() {
    int result = compute(5, 3);
    printf("compute_result: %d\n", result);
}
```

**Disassembly:**

```bash
objdump -d test_jmp_back | grep -A 15 compute
```

```
0000000000001140 <compute>:
    1140:   push   %rbp
    1141:   mov    %rsp,%rbp
    1144:   mov    %edi,-0x4(%rbp)
    1147:   mov    %esi,-0x8(%rbp)
    114a:   mov    -0x4(%rbp),%eax
    114d:   add    -0x8(%rbp),%eax
    1150:   pop    %rbp
    1151:   ret
```

The function adds `x + y`. Hook at offset `0x10` (after the add instruction) to modify the result before returning:

**Hook:**

```rust
#[hook(kind = "jmp_back", target = "compute+0x10")]
fn hook_compute(reg: &mut Registers) {
    println!("compute: original rax = {}", reg.rax);
    reg.rax = 999;
}
```

**Result:**

| Input | Original Output | Hooked Output |
|-------|-----------------|--------------|
| compute(5, 3) | 8 | 999 (with log) |

The hook intercepts after the add instruction executes. The original computation runs, then the hook modifies rax to 999 before returning to the caller.

### 3. Dynamic Redirection (`jmp_to_ret` or `DynamicRedirect`)

Intercept an instruction and dynamically choose where execution continues.
Your hook returns a `Target` enum specifying the destination address at runtime.

This is useful for conditional redirection. The difference with hooking just the function using `retn/FunctionHook`
is that this alows you to conditionally skip a very specific chunk of logic within a function while allowing the
rest of the function to run without re-writing possibly complex logic in your hook. 

The following example demonstrates skipping a code block for a specific player ID while allowing normal
 execution for others.

**C Example:**

```c
typedef struct {
    int id;
    int hit_points;
    int is_invincible;
} Player;

void play_hit_sound() {
    printf("oof.mp3\n");
}

void update_ui(int id, int hp) {
    printf("update_ui: id=%d hp=%d\n", id, hp);
}

void check_death(Player *p) {
    if (p->hit_points <= 0) {
        printf("player_dead: %d\n", p->id);
    }
}

__attribute__((noinline))
void process_damage(Player *player, int damage) {
    printf("player_id: %d\n", player->id);

    // --- DAMAGE BLOCK START ---
    // We want to skip this block for specific player IDs
    if (!player->is_invincible) {
        player->hit_points -= damage;
    }
    // --- DAMAGE BLOCK END ---

    // These MUST run regardless
    play_hit_sound();
    update_ui(player->id, player->hit_points);
    check_death(player);
}

int main() {
    Player p1 = { .id = 0x1234, .hit_points = 100, .is_invincible = 0 };
    Player p2 = { .id = 0x4567, .hit_points = 100, .is_invincible = 0 };

    printf("--- p1 damage ---\n");
    process_damage(&p1, 100);
    printf("p1 final hit_points: %d\n", p1.hit_points);

    printf("--- p2 damage ---\n");
    process_damage(&p2, 10);
    printf("p2 final hit_points: %d\n", p2.hit_points);
}
```

**Disassembly:**

```bash
objdump -d test_jmp_to_ret | grep -A 30 process_damage
```

```
0000000000001200 <process_damage>:
    1200:   push   %rbp
    1201:   mov    %rsp,%rbp
    1204:   sub    $0x10,%rsp
    1208:   mov    %rdi,-0x8(%rbp)
    120c:   mov    %esi,-0xc(%rbp)
    120f:   mov    -0x8(%rbp),%rax
    1213:   mov    (%rax),%edx
    1215:   lea    0xde8(%rip),%rdi        # 2004
    121c:   lea    0xe45(%rip),%rsi        # 2068
    1225:   call   1030 <printf@plt>
    122a:   mov    -0x8(%rbp),%rax
    122e:   cmpl   $0x0,0x8(%rax)
    1232:   jne    1243 <process_damage+0x43>   ; <- Jump here if is_invincible != 0
    1234:   mov    -0xc(%rbp),%edx
    1237:   mov    -0x8(%rbp),%rax
    123b:   mov    0x4(%rax),%ecx
    123e:   sub    %edx,%ecx
    1240:   mov    %ecx,0x4(%rax)           ; <- Apply damage
    1243:   call   1190 <play_hit_sound>     ; <- Continue here after damage block
    1248:   mov    -0x8(%rbp),%rax
    124c:   mov    (%rax),%edi
    124e:   mov    -0x8(%rbp),%rax
    1252:   mov    0x4(%rax),%esi
    1255:   call   1140 <update_ui>
    125a:   mov    -0x8(%rbp),%rdi
    125e:   call   11c0 <check_death>
    1263:   add    $0x10,%rsp
    1267:   pop    %rbp
    1268:   ret
```

The damage block is from offset `0x2a` (inside the function, after the printf) to `0x43` (at `play_hit_sound` call). Let's hook at `process_damage+0x2a`:

**Hook:**

```rust
use prehook::hook::{Target, Registers};

#[hook(kind = "jmp_to_ret", target = "process_damage+0x2a")]
fn hook_damage(reg: &mut Registers, _orig: usize) -> Target {
    #[repr(C)]
    pub struct Player {
        pub id: i32,
        pub health: i32,
        pub is_invincible: i32,
    }

    // Read Player from stack (rbp - 0x8 contains the player pointer)
    let player_ptr = (reg.rbp - 0x8) as *const *const Player;
    let player = unsafe {
        let p = *player_ptr;
        if p.is_null() {
            return Target::Relative(0x8);
        }
        &*p
    };

    // Skip damage for player with id 0x4567
    if player.id == 0x4567 {
        println!("cheat: skip damage");
        // Jump past the damage block (0x43 - 0x2a = 0x19 bytes forward)
        Target::Relative(0x19)
    } else if player.is_invincible != 0 {
        // Skip damage for invincible players
        Target::Relative(0x19)
    } else {
        // Continue normal execution (0xa bytes past hook point)
        Target::Relative(0x0a)
    }
}
```

**Result:**

| Input | Hook Action | Output |
|------|-------------|--------|
| Player 0x1234, damage 100 | Normal | hit_points: 0, plays sound, updates UI |
| Player 0x4567, damage 10 | Skipped | hit_points: 100, plays sound, updates UI |

The hooked player still receives sound effects and UI updates because the hook only redirects past the damage calculation block, not the entire function.

### 4. Static Redirection (`jmp_to_addr` or `StaticRedirect`)

Intercept an instruction and unconditionally jump to a fixed destination.
Unlike `jmp_to_ret` which returns a destination at runtime, this destination address is hard coded into the trampoline and will
jump immediately when triggered.

This hook is useful for use cases where you always want to jump over known set of instructions within a function call.
It also allows you to run your own instructions in your hook.


The following demo instruction shows a simple gate that we want to always jump over to the different path unconditionally.

**C Example:**

```c
int security_gate(int key) {
    if (key != 1337) {
        printf("security_gate: denied\n");
        return -1;
    }

    printf("security_gate: access granted\n");
    return 100;
}

int main() {
    int result = security_gate(0);  // Wrong key
    return result;
}
```

**Disassembly:**

```bash
objdump -d test_jmp_to_addr | grep -A 25 security_gate
```

```
0000000000001140 <security_gate>:
    1140:   push   %rbp
    1141:   mov    %rsp,%rbp
    1144:   sub    $0x10,%rsp
    1148:   mov    %edi,-0x8(%rbp)
    114b:   cmpl   $0x539,-0x8(%rbp)     ; Compare key with 1337 (0x539)
    1152:   je     1179 <security_gate+0x39>  ; Jump to access_granted if equal
    1154:   lea    0xea9(%rip),%rdi     # 2004
    115b:   lea    0xeb8(%rip),%rsi     # 201a
    1162:   lea    0xebf(%rip),%rdx     # 2028
    116b:   call   1030 <printf@plt>
    1170:   movl   $0xffffffff,-0x4(%rbp)  ; Return -1
    1177:   jmp    119e <security_gate+0x5e>
    1179:   jmp    117b <security_gate+0x3b>   ; <- Redirect here to skip denied message
    117b:   lea    0xe82(%rip),%rdi     # 2004
    1182:   lea    0xe91(%rip),%rsi     # 201a
    1189:   lea    0xe9f(%rip),%rdx     # 202f
    1192:   call   1030 <printf@plt>
    1197:   movl   $0x64,-0x4(%rbp)     ; Return 100 (0x64)
    119e:   mov    -0x4(%rbp),%eax
    11a1:   add    $0x10,%rsp
    11a5:   pop    %rbp
    11a6:   ret
```

The function checks if `key == 1337`. If not, it prints "denied" and returns -1. We want to bypass this check and jump directly to the "access granted" code.

Hook at offset `0xb` (after the comparison), jump to `+0x30` relative (past the denied block):

**Hook:**

```rust
#[hook(kind = "jmp_to_addr", target = "security_gate+0xb", dest = "+0x30")]
fn bypass_security(_reg: &mut Registers) {
    println!("security_gate: bypassed");
}
```

**Result:**

| Input | Original Output | Hooked Output |
|-------|-----------------|--------------|
| key=0 | denied, -1 | bypassed, 100 |
| key=1337 | access granted, 100 | bypassed, 100 |

The hook intercepts at `security_gate+0xb` and unconditionally jumps past the denied message to the success code. Even wrong keys now grant access.
This hook is useful for use cases where you want to jump over known set of instructions within a function call.

### 5. Interpose

The `#[interpose]` macro overrides symbols from shared libraries. When your Rust library is loaded via `LD_PRELOAD`, it loads before all other shared libraries, including libc. This means your exported symbols take precedence over the originals.

This is fundamentally different from `#[hook]`:
- `#[hook]`: Intercepts via instruction patching at runtime
- `#[interpose]`: Works at the symbol level. Your function becomes the new symbol.

**Why this is powerful**: You can override virtually any function from any shared library that gets loaded
 after yours. This includes libc functions like `malloc`, `free`, `getenv`, `open`, `read`, `write`, and more.
 Since your library loads first, the dynamic linker resolves your symbols instead of the originals. One known limitation
 is when your target calls `dlopen` but you can always override `dlopen` and `dlsym` and point them to your own stuff.

#### Rust library Side

```rust
use prehook::interpose;
use std::ffi::CStr;

/// This hook will intercept all libc getenv listening for
/// our non existent secret variable named "YOU_ARE_MY_FOO"
#[interpose]
fn getenv(name_ptr: *const c_char) -> *mut c_char {
    let name = unsafe { CStr::from_ptr(name_ptr).to_str().unwrap() };

    println!("Intercepted getenv for: {}", name);

    // Detect our secret variable
    if name == "YOU_ARE_MY_FOO" {
        return c"BAR".
            .unwrap()
            .into_raw();
    }

    // Call the original getenv from libc (RTLD_NEXT)
    call_original!(name_ptr)
}
```

Build as a dynamic library:

```bash
cargo build --release
# Produces target/release/libprehook.so (or your crate name)
```

#### C Side (How it works)

Write a simple C program that calls `getenv`:

```c
// test_getenv.c
#include <stdio.h>
#include <stdlib.h>

int main() {
    const char *result = getenv("YOU_ARE_MY_FOO");
    printf("getenv(YOU_ARE_MY_FOO) = %s\n", result);

    result = getenv("PATH");
    printf("getenv(PATH) = %s\n", result);

    return 0;
}
```

Compile and run:

```bash
gcc test_getenv.c -o test_getenv
LD_PRELOAD=./target/release/libprehook.so ./test_getenv
```

**Expected output:**

```
Intercepted getenv for: YOU_ARE_MY_FOO
getenv(YOU_ARE_MY_FOO) = BAR
Intercepted getenv for: PATH
getenv(PATH) = /usr/local/bin:/usr/bin:/bin
```

The `call_original!` macro is automatically injected and resolves the original symbol from `RTLD_NEXT` (the next library in the load chain).

You can also specify a different symbol name if your function differs:

```rust
#[interpose(symbol = "malloc")]
fn my_alloc(size: usize) -> *mut c_void {
    println!("Allocating {} bytes", size);
    call_original!(size)
}
```

> [!WARNING]
> When hooking any function, beware of indirect recursion. Your hook calls some other function, that other function internally calls
> the original hooked function, which triggers your hook again, creating an infinite loop.
> For example above, if you hook `malloc` but your hook code calls `println` for logging,
> and `println` internally calls `malloc`, your hook triggers itself infinitely.
> This is not limited to memory functions. Any hooked function can cause this.
> See [Reentrant Guard](#6-reentrant-guard) for possible solution.

This makes your `my_alloc` function override the real `malloc`.

### 6. Reentrant Guard

The reentrant guard prevents infinite loops caused by indirect recursion. When your hook is executing and calls another function that internally calls the hooked function, your hook triggers itself again, creating an infinite loop.

> [!NOTE]
> `call_original!()` will not trigger a recursion.
> Calling the original is safe because you are explicitly calling the replaced function,
> not your own hook.

**Example of the problem:**

```rust
#[interpose]
fn getenv(name: i32) -> i32 {
    // Your hook logic panics because env var is missing
    panic!("Missing env: {}", some_var);

    call_original!(name)
}
```

#### Explanation
When the panic occurs, Rust's panic handler attempts to read the `RUST_BACKTRACE` environment
variable. This internally calls `getenv` again, which triggers your hook again, which panics again,
which reads `RUST_BACKTRACE` again, infinitely.

**Solution:**

Use `guard = true` to detect and prevent reentry:

```rust
#[interpose(guard = true)]
fn getenv(name: i32) -> i32 {
    // If guard detects reentry, call_original!() is called directly
    // bypassing your hook logic
    // in this case the guard will detect re entry and route getenv directly to libc instead.
    panic!("Missing env: {}", some_var);
    
    let result = call_original!(name);
    result
}
```

When the guard is enabled, if your hook is called while already executing:
- The guard detects the reentry
- Forwards directly to the original function, bypassing your hook logic
- Prevents the infinite recursion loop


Hook guard is only supported in: `retn` and `interpose` hooks. The other hook types are
not supported as they occur at abitrary locations which is hard for hook guard to know how to
re-route.

## How it works

1. **`#[hook]` Macro**: Generates a trampoline and a `ctor` (constructor) that automatically registers the hook when the library is loaded.
2. **`HOOK_REGISTRY`**: A global static that holds all registered hooks.
3. **Application**: Hooks are typically applied in `__libc_start_main` to ensure they are active before the main binary starts executing.

## Hook Management

You can uninstall hooks at any time:

```rust
// Uninstall a specific hook by its function name
prehook::hook::HOOK_REGISTRY.remove_hook("my_hook");

// Uninstall all hooks
prehook::hook::HOOK_REGISTRY.cleanup_all();
```

## License

[MIT](LICENSE)
