├── .gitignore ├── .travis.yml ├── COPYING ├── Cargo.toml ├── README.md ├── examples ├── fakeroot │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── neverfree │ ├── Cargo.toml │ └── src │ │ └── lib.rs └── readlinkspy │ ├── Cargo.toml │ └── src │ └── lib.rs ├── src ├── dyld_insert_libraries.rs ├── ld_preload.rs └── lib.rs └── test.sh /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | 3 | os: 4 | - linux 5 | - osx 6 | 7 | script: 8 | - ./test.sh 9 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015, Geoffrey Thomas . 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are 6 | met: 7 | 8 | 1. Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | 11 | 2. Redistributions in binary form must reproduce the above copyright 12 | notice, this list of conditions and the following disclaimer in the 13 | documentation and/or other materials provided with the distribution. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS 16 | IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 17 | TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 18 | PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 19 | HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 20 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 21 | TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 22 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 23 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 24 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "redhook" 3 | version = "2.0.0" 4 | authors = ["Geoffrey Thomas "] 5 | description = "Dynamic function call interposition / hooking (LD_PRELOAD) for Rust" 6 | repository = "https://github.com/geofft/redhook" 7 | readme = "README.md" 8 | keywords = ["LD_PRELOAD", "interposition"] 9 | license = "BSD-2-Clause" 10 | 11 | [dependencies] 12 | libc = "0.2" 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | redhook 2 | ======= 3 | 4 | redhook is a helper crate for writing interposition libraries 5 | (`LD_PRELOAD`, `DYLD_INSERT_LIBRARIES`, etc.) in Rust. 6 | 7 | To use redhook, edit your `Cargo.toml` to add redhook as a dependency 8 | and configure your library to build as a `dylib`: 9 | 10 | ```toml 11 | [dependencies] 12 | redhook = "2.0" 13 | 14 | [lib] 15 | name = "mylib" 16 | crate_type = ["dylib"] 17 | ``` 18 | 19 | Then use the `hook!` macro to declare the function you want to hook, and 20 | the name you want to give to your hook function: 21 | 22 | ```rust 23 | redhook::hook! { 24 | unsafe fn existing_function(x: i32) -> i32 => my_function { 25 | 42 26 | } 27 | } 28 | ``` 29 | 30 | To access the underlying function, use `redhook::real!(existing_function)`. 31 | 32 | Build your library as usual, find the resulting `.so` file (or `.dylib`, 33 | for macOS) inside the `target` directory, and set the `LD_PRELOAD` (or 34 | `DYLD_INSERT_LIBRARIES`) environment variable to that. 35 | 36 | (You can also use redhook without Cargo, with `rustc --crate-type=dylib`.) 37 | 38 | There are a few [examples](examples) included, as separate crates: 39 | 40 | ``` 41 | geofft@cactuar:~/src/rust/redhook/examples/readlinkspy$ cargo build 42 | Compiling redhook v0.0.1 (file:///home/geofft/src/rust/redhook/examples/readlinkspy) 43 | Compiling redhook_ex_readlinkspy v0.0.1 (file:///home/geofft/src/rust/redhook/examples/readlinkspy) 44 | geofft@cactuar:~/src/rust/redhook/examples/readlinkspy$ LD_PRELOAD=target/debug/libreadlinkspy.so ls -l /bin/sh 45 | readlink("/bin/sh") 46 | lrwxrwxrwx 1 root root 4 Feb 19 2014 /bin/sh -> dash 47 | ``` 48 | 49 | ``` 50 | geofft@cactuar:~/src/rust/redhook/examples/fakeroot$ cargo build 51 | geofft@cactuar:~/src/rust/redhook/examples/fakeroot$ LD_PRELOAD=target/debug/libfakeroot.so id 52 | uid=0(root) gid=1001(geofft) euid=1001(geofft) groups=1001(geofft),27(sudo),111(sbuild) 53 | ``` 54 | 55 | redhook currently supports building interposition libraries for 56 | `LD_PRELOAD` on glibc (GNU/Linux) and `DYLD_INSERT_LIBRARIES` on Apple's 57 | libc (macOS) from the same source code. If you're interested in 58 | support for other platforms, please file an issue or pull request. 59 | 60 | redhook is named after the [Red Hook](http://en.wikipedia.org/wiki/Red_Hook,_Brooklyn) 61 | neighborhood in Brooklyn, New York. Portions of the implementation 62 | borrow heavily from concepts in @Kimundi's 63 | [`lazy_static!`](https://github.com/Kimundi/lazy-static.rs) macro. 64 | 65 | redhook is free software, available under the terms of the 66 | [2-clause BSD license](COPYING). 67 | 68 | Minimum supported Rust version: **1.32** 69 | 70 | [![Build Status](https://travis-ci.org/geofft/redhook.svg?branch=master)](https://travis-ci.org/geofft/redhook) 71 | -------------------------------------------------------------------------------- /examples/fakeroot/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "redhook_ex_fakeroot" 3 | version = "0.0.1" 4 | authors = ["Geoffrey Thomas "] 5 | 6 | [lib] 7 | name = "fakeroot" 8 | crate_type = ["dylib"] 9 | 10 | [dependencies.redhook] 11 | path = "../.." 12 | 13 | [dependencies] 14 | libc = "0.2" 15 | -------------------------------------------------------------------------------- /examples/fakeroot/src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate libc; 2 | 3 | #[macro_use] 4 | extern crate redhook; 5 | 6 | use libc::uid_t; 7 | 8 | hook! { 9 | unsafe fn getuid() -> uid_t => i_am_root { 10 | 0 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /examples/neverfree/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "redhook_ex_neverfree" 3 | version = "0.1.0" 4 | authors = ["Geoffrey Thomas "] 5 | edition = "2018" 6 | 7 | [lib] 8 | name = "neverfree" 9 | crate_type = ["dylib"] 10 | 11 | [dependencies.redhook] 12 | path = "../.." 13 | -------------------------------------------------------------------------------- /examples/neverfree/src/lib.rs: -------------------------------------------------------------------------------- 1 | redhook::hook! { 2 | // Can't have use-after-free vulnerabilities... if you never free anything 3 | unsafe fn free(_ptr: *const ()) => my_free { } 4 | } 5 | -------------------------------------------------------------------------------- /examples/readlinkspy/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "redhook_ex_readlinkspy" 3 | version = "0.0.1" 4 | authors = ["Geoffrey Thomas "] 5 | 6 | [lib] 7 | name = "readlinkspy" 8 | crate_type = ["dylib"] 9 | 10 | [dependencies.redhook] 11 | path = "../.." 12 | 13 | [dependencies] 14 | libc = "0.2" 15 | -------------------------------------------------------------------------------- /examples/readlinkspy/src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate libc; 2 | 3 | #[macro_use] 4 | extern crate redhook; 5 | 6 | use libc::{size_t,ssize_t,c_char}; 7 | 8 | hook! { 9 | unsafe fn readlink(path: *const c_char, buf: *mut c_char, bufsiz: size_t) -> ssize_t => my_readlink { 10 | if let Ok(path) = std::str::from_utf8(std::ffi::CStr::from_ptr(path).to_bytes()) { 11 | if path == "test-panic" { 12 | panic!("Testing panics"); 13 | } 14 | println!("readlink(\"{}\")", path); 15 | } else { 16 | println!("readlink(...)"); 17 | } 18 | 19 | real!(readlink)(path, buf, bufsiz) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/dyld_insert_libraries.rs: -------------------------------------------------------------------------------- 1 | #[macro_export] 2 | macro_rules! hook { 3 | (unsafe fn $real_fn:ident ( $($v:ident : $t:ty),* ) -> $r:ty => $hook_fn:ident $body:block) => { 4 | pub mod $real_fn { 5 | #[allow(non_camel_case_types)] 6 | pub struct $real_fn { 7 | _new: *const (), 8 | _old: *const (), 9 | } 10 | 11 | #[allow(dead_code)] 12 | #[allow(non_upper_case_globals)] 13 | #[link_section="__DATA,__interpose"] 14 | pub static mut $real_fn: $real_fn = $real_fn { 15 | _new: super::$hook_fn as *const (), 16 | _old: super::$real_fn as *const (), 17 | }; 18 | } 19 | 20 | extern { 21 | pub fn $real_fn ( $($v : $t),* ) -> $r; 22 | } 23 | 24 | pub unsafe extern fn $hook_fn ( $($v : $t),* ) -> $r { 25 | ::std::panic::catch_unwind(|| $body ).unwrap_or_else(|_| $real_fn ( $($v),* )) 26 | } 27 | }; 28 | 29 | (unsafe fn $real_fn:ident ( $($v:ident : $t:ty),* ) => $hook_fn:ident $body:block) => { 30 | $crate::hook! { unsafe fn $real_fn ( $($v : $t),* ) -> () => $hook_fn $body } 31 | }; 32 | } 33 | 34 | #[macro_export] 35 | macro_rules! real { 36 | ($real_fn:ident) => { 37 | $real_fn 38 | }; 39 | } 40 | -------------------------------------------------------------------------------- /src/ld_preload.rs: -------------------------------------------------------------------------------- 1 | use libc::{c_char, c_void}; 2 | 3 | #[link(name = "dl")] 4 | extern "C" { 5 | fn dlsym(handle: *const c_void, symbol: *const c_char) -> *const c_void; 6 | } 7 | 8 | const RTLD_NEXT: *const c_void = -1isize as *const c_void; 9 | 10 | pub unsafe fn dlsym_next(symbol: &'static str) -> *const u8 { 11 | let ptr = dlsym(RTLD_NEXT, symbol.as_ptr() as *const c_char); 12 | if ptr.is_null() { 13 | panic!("redhook: Unable to find underlying function for {}", symbol); 14 | } 15 | ptr as *const u8 16 | } 17 | 18 | #[macro_export] 19 | macro_rules! hook { 20 | (unsafe fn $real_fn:ident ( $($v:ident : $t:ty),* ) -> $r:ty => $hook_fn:ident $body:block) => { 21 | #[allow(non_camel_case_types)] 22 | pub struct $real_fn {__private_field: ()} 23 | #[allow(non_upper_case_globals)] 24 | static $real_fn: $real_fn = $real_fn {__private_field: ()}; 25 | 26 | impl $real_fn { 27 | fn get(&self) -> unsafe extern fn ( $($v : $t),* ) -> $r { 28 | use ::std::sync::Once; 29 | 30 | static mut REAL: *const u8 = 0 as *const u8; 31 | static mut ONCE: Once = Once::new(); 32 | 33 | unsafe { 34 | ONCE.call_once(|| { 35 | REAL = $crate::ld_preload::dlsym_next(concat!(stringify!($real_fn), "\0")); 36 | }); 37 | ::std::mem::transmute(REAL) 38 | } 39 | } 40 | 41 | #[no_mangle] 42 | pub unsafe extern fn $real_fn ( $($v : $t),* ) -> $r { 43 | ::std::panic::catch_unwind(|| $hook_fn ( $($v),* )).unwrap_or_else(|_| $real_fn.get() ( $($v),* )) 44 | } 45 | } 46 | 47 | pub unsafe fn $hook_fn ( $($v : $t),* ) -> $r { 48 | $body 49 | } 50 | }; 51 | 52 | (unsafe fn $real_fn:ident ( $($v:ident : $t:ty),* ) => $hook_fn:ident $body:block) => { 53 | $crate::hook! { unsafe fn $real_fn ( $($v : $t),* ) -> () => $hook_fn $body } 54 | }; 55 | } 56 | 57 | #[macro_export] 58 | macro_rules! real { 59 | ($real_fn:ident) => { 60 | $real_fn.get() 61 | }; 62 | } 63 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate libc; 2 | 3 | #[cfg(target_env = "gnu")] 4 | pub mod ld_preload; 5 | 6 | #[cfg(any(target_os = "macos", target_os = "ios"))] 7 | pub mod dyld_insert_libraries; 8 | -------------------------------------------------------------------------------- /test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ "$(uname)" = "Darwin" ]; then 4 | tmpdir="$(mktemp -d)" 5 | cp /bin/ls "$tmpdir" 6 | PATH=$tmpdir:$PATH 7 | trap 'rm -r "$tmpdir"' exit 8 | fi 9 | 10 | preload () { 11 | local library 12 | library=$1 13 | shift 14 | if [ "$(uname)" = "Darwin" ]; then 15 | DYLD_INSERT_LIBRARIES=target/debug/"$library".dylib "$@" 16 | else 17 | LD_PRELOAD=target/debug/"$library".so "$@" 18 | fi 19 | } 20 | 21 | set -ex 22 | set -o pipefail 23 | 24 | cd examples/readlinkspy 25 | cargo update 26 | cargo build 27 | preload libreadlinkspy ls -l /dev/stdin | grep readlink 28 | 29 | cd ../neverfree 30 | cargo update 31 | cargo build 32 | --------------------------------------------------------------------------------