From f3a453a88e15cd5d6e72bd7dc126fa5fc7f47ea2 Mon Sep 17 00:00:00 2001 From: Anton Livaja Date: Wed, 17 Jul 2024 13:24:15 -0400 Subject: [PATCH] initial commit --- .gitignore | 1 + Cargo.lock | 16 +++++ Cargo.toml | 10 +++ LICENSE | 23 +++++++ Makefile | 11 ++++ README.md | 39 ++++++++++++ src/lib.rs | 178 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 278 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 README.md create mode 100644 src/lib.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..ce1bbac --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,16 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "fakerand" +version = "0.1.0" +dependencies = [ + "libc", +] + +[[package]] +name = "libc" +version = "0.2.155" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..c894fe9 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "fakerand" +version = "0.1.0" +edition = "2021" + +[dependencies] +libc = "0.2" + +[lib] +crate-type = ["cdylib"] \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..6c40a95 --- /dev/null +++ b/LICENSE @@ -0,0 +1,23 @@ +The MIT License (MIT) +===================== + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the “Software”), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..1f5228c --- /dev/null +++ b/Makefile @@ -0,0 +1,11 @@ +CARGO_BUILD=cargo build --release +TARGET_DIR=target/release +LIB_NAME=libfakerand.so +INSTALL_DIR=/usr/lib + +all: build + +build: + $(CARGO_BUILD) + +.PHONY: all build \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..5c7cc0d --- /dev/null +++ b/README.md @@ -0,0 +1,39 @@ +# libfakerand + +## Introduction + +`libfakerand` intercepts various system calls that programs use to +retrieve entropy. It replaces the entropy which would otherwise be +used with the one supplied by the user. This means you can force +programs to use a fixed value instead of a random one. + +`libfakerand` overrides the following functions: +* `rand()` +* `read()` function for `/dev/urandom` and `/dev/random` +* `getrandom()` +* `RAND_bytes()` + +## Usage + +You may use env variables `LD_PRELOAD` and `FAKERAND` along with a command: +``` +LD_PRELOAD=./target/release/libfakerand.so FAKERAND=0 openssl rand -hex 16 +``` + +Alternatively, set environment variables so that all programs running in that shell are effected: +``` +export LD_PRELOAD=/usr/lib/fakerand/fakerand.so +export FAKERAND=42 +``` + +If you will be using it this way, it may make sense to place the .so file in `/usr/lib/fakerand` + +## Notes +Statically linked libraries can't be overriden by `LD_PRELOAD`. For +this reason, `libfakerand` can't override cases such as: + +``` +FAKERAND=42 LD_PRELOAD=./target/release/libfakerand.so awk 'BEGIN{srand(); print rand()}' 2>/dev/null +``` + +This is because `awk` is statically linked. \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..40b2904 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,178 @@ +use std::env; +use std::mem; +use std::ffi::CStr; +use libc::{ + c_int, + c_uchar, + c_uint, + c_void, + dlsym, + getrandom as libc_getrandom, + RTLD_NEXT, + ssize_t +}; + +fn fakerand_value() -> Option { + env::var("FAKERAND").ok().and_then(|val| val.parse().ok()) +} + +#[no_mangle] +pub extern "C" fn rand() -> c_int { + println!("rand() called"); + if let Some(fakerand) = fakerand_value() { + println!("rand() value {}", fakerand); + return fakerand; + } + + unsafe { + let symbol = dlsym(RTLD_NEXT, "rand\0".as_ptr() as *const _); + + if !symbol.is_null() { + let original_rand: extern "C" fn() -> c_int = std::mem::transmute(symbol); + return original_rand(); + } else { + eprintln!("Error: original rand() not found"); + std::process::exit(1); + } + } +} + +#[no_mangle] +pub extern "C" fn read(fd: c_int, buf: *mut c_void, count: usize) -> ssize_t { + static mut ORIGINAL_READ: Option ssize_t> = None; + + unsafe { + if ORIGINAL_READ.is_none() { + let symbol = dlsym(RTLD_NEXT, "read\0".as_ptr() as *const _); + if symbol.is_null() { + eprintln!("Error: original read() not found"); + std::process::exit(1); + } + ORIGINAL_READ = Some(std::mem::transmute(symbol)); + } + + let original_read = ORIGINAL_READ.unwrap(); + + let path = format!("/proc/self/fd/{}", fd); + let mut actualpath = [0u8; 1024]; + + let len = libc::readlink( + path.as_ptr() as *const i8, + actualpath.as_mut_ptr() as *mut i8, + actualpath.len() as _ + ); + + if len != -1 { + let actualpath = CStr::from_ptr(actualpath.as_ptr() as *const i8).to_str().unwrap(); + if (actualpath == "/dev/urandom" || actualpath == "/dev/random") && fakerand_value().is_some() { + let fakerand = fakerand_value().unwrap(); + *(buf as *mut c_int) = fakerand; + return std::mem::size_of::() as ssize_t; + } + } + + original_read(fd, buf, count) + } +} + +#[no_mangle] +pub extern "C" fn getrandom(buf: *mut c_void, buflen: usize, flags: c_uint) -> ssize_t { + if let Some(fakerand) = fakerand_value() { + unsafe { + *(buf as *mut c_int) = fakerand; + } + return std::mem::size_of::() as ssize_t; + } + + unsafe { + libc_getrandom(buf, buflen, flags) + } +} + +#[no_mangle] +pub extern "C" fn RAND_bytes(buf: *mut c_uchar, num: c_int) -> c_int { + if let Some(fakerand) = fakerand_value() { + unsafe { + for i in 0..num { + *buf.add(i as usize) = fakerand as c_uchar; + } + } + return 1; + } + + unsafe { + let symbol = dlsym(RTLD_NEXT, "RAND_bytes\0".as_ptr() as *const _); + if !symbol.is_null() { + let original_rand_bytes: extern "C" fn(*mut c_uchar, c_int) -> c_int = mem::transmute(symbol); + return original_rand_bytes(buf, num); + } else { + eprintln!("Error: original RAND_bytes() not found"); + std::process::exit(1); + } + } +} + +#[cfg(test)] +mod tests { + use std::fs::OpenOptions; + use std::os::unix::io::AsRawFd; + + use super::*; + + #[test] + fn test_rand_with_fakerand() { + env::set_var("FAKERAND", "42"); + assert_eq!(rand(), 42); + env::remove_var("FAKERAND"); + } + + #[test] + fn test_rand_without_fakerand() { + env::remove_var("FAKERAND"); + let rand_value = rand(); + // TODO: Does this mean the test will fail once in a while? + assert!(rand_value != 42); + assert!(rand_value >= 0 && rand_value <= libc::RAND_MAX); + } + + #[test] + fn test_read_with_fakerand() { + env::set_var("FAKERAND", "42"); + + let file = OpenOptions::new().read(true).open("/dev/urandom").expect("Failed to open /dev/urandom"); + let fd = file.as_raw_fd(); + let mut buf = [0u8; 4]; + let result = read(fd, buf.as_mut_ptr() as *mut c_void, buf.len()); + + assert_eq!(result, std::mem::size_of::() as ssize_t); + assert_eq!(buf, [42, 0, 0, 0]); + + env::remove_var("FAKERAND"); + } + + #[test] + fn test_read_without_fakerand() { + env::remove_var("FAKERAND"); + + let file = OpenOptions::new().read(true).open("/dev/urandom").expect("Failed to open /dev/urandom"); + let fd = file.as_raw_fd(); + let mut buf = [0u8; 4]; + let result = read(fd, buf.as_mut_ptr() as *mut c_void, buf.len()); + + assert_eq!(result as usize, buf.len()); + assert!(buf != [0, 0, 0, 0]); + } + + #[test] + fn test_rand_bytes_with_fakerand() { + env::set_var("FAKERAND", "42"); + + let mut buf = [0u8; 4]; + let result = RAND_bytes(buf.as_mut_ptr(), buf.len() as c_int); + + assert_eq!(result, 1); + assert_eq!(buf, [42, 42, 42, 42]); + + env::remove_var("FAKERAND"); + } +} \ No newline at end of file