Crates.io | greenhook |
lib.rs | greenhook |
version | 0.0.4 |
source | src |
created_at | 2023-05-27 15:30:54.365606 |
updated_at | 2023-05-28 16:09:16.860818 |
description | A seccomp-unotify-based syscall hook library |
homepage | |
repository | https://github.com/taoky/greenhook |
max_upload_size | |
id | 875975 |
size | 77,727 |
https://crates.io/crates/greenhook
Greenhook is a seccomp-unotify-based syscall hook library. It is adapted from https://github.com/pdlan/binder.
You could have it a try if you want to find alternatives other than LD_PRELOAD
and ptrace
. However, please note that seccomp unotify IS NOT a full replacement of these techniques, and take some time reading seccomp_unotify(2)
before you start.
To fully utilize this library, you need to have a kernel version >= 5.9.0. And also you need a special seccomp policy file if you want to run this in Docker or other containers (to allow process_vm_readv()
and pidfd_getfd()
to run without capabilities), with this:
# docker run --security-opt seccomp=assets/seccomp.json ...
Also, it is necessary to install libseccomp header and library:
$ sudo apt install libseccomp-dev
You can find some examples inside test code. Here is a simple one that makes programs like whoami(1)
considering you are root (even if you are not), by hooking geteuid(2)
:
use std::process::Command;
use greenhook::{Supervisor, UNotifyEventRequest};
use libseccomp::ScmpSyscall;
fn geteuid_handler(req: &UNotifyEventRequest) -> libseccomp::ScmpNotifResp {
req.return_syscall(0)
}
fn main() {
env_logger::init();
// Get argv[1]
let program = std::env::args().nth(1).unwrap();
let mut supervisor = Supervisor::new(2).unwrap();
supervisor.insert_handler(ScmpSyscall::new("geteuid"), geteuid_handler);
let mut cmd = Command::new(program);
let (mut child, thread_handle, pool) = supervisor.exec(&mut cmd).unwrap();
let _ = Supervisor::wait(&mut child, thread_handle, pool).unwrap();
}
Run this with:
> cargo run --example geteuid -- whoami
root
> whoami
user
A more complicated one, that replaces /etc/passwd
to /etc/resolv.conf
by hooking openat(2)
:
use std::{process::Command, ffi::CStr, fs::File, os::fd::AsRawFd};
use greenhook::{Supervisor, UNotifyEventRequest, RemoteProcess};
use libseccomp::ScmpSyscall;
use log::info;
use nix::{unistd::Pid, libc};
fn openat_handler(req: &UNotifyEventRequest) -> libseccomp::ScmpNotifResp {
let path = req.get_request().data.args[1];
let remote = RemoteProcess::new(Pid::from_raw(req.get_request().pid as i32)).unwrap();
let mut buf = [0u8; 256];
remote.read_mem(&mut buf, path as usize).unwrap();
// debug!("open (read from remote): {:?}", buf);
let path = CStr::from_bytes_until_nul(&buf).unwrap();
if !req.is_valid() {
return req.fail_syscall(libc::EACCES);
}
info!("open (path CStr): {:?}", path);
if path.to_str().unwrap() == "/etc/passwd" {
// open /etc/resolv.conf instead
let file = File::open("/etc/resolv.conf").unwrap();
let fd = file.as_raw_fd();
let remote_fd = req.add_fd(fd).unwrap();
req.return_syscall(remote_fd as i64)
} else {
unsafe { req.continue_syscall() }
}
}
fn main() {
env_logger::init();
// Get argv[1..]
let args = std::env::args().skip(1).collect::<Vec<_>>();
if args.len() == 0 {
panic!("Usage: {} <program> [args...]", std::env::args().nth(0).unwrap());
}
let mut supervisor = Supervisor::new(2).unwrap();
supervisor.insert_handler(ScmpSyscall::new("openat"), openat_handler);
let mut cmd = Command::new(args[0].clone());
let cmd = cmd.args(&args[1..]);
let (mut child, thread_handle, pool) = supervisor.exec(cmd).unwrap();
let _ = Supervisor::wait(&mut child, thread_handle, pool).unwrap();
}
Run this with:
> RUST_LOG=info cargo run --example openat -- cat /etc/passwd
[2023-05-27T14:39:57Z INFO openat] open (path CStr): "/home/taoky/Projects/greenhook/target/debug/deps/glibc-hwcaps/x86-64-v3/libc.so.6"
[2023-05-27T14:39:57Z INFO openat] open (path CStr): "/home/taoky/Projects/greenhook/target/debug/deps/glibc-hwcaps/x86-64-v2/libc.so.6"
[2023-05-27T14:39:57Z INFO openat] open (path CStr): "/home/taoky/Projects/greenhook/target/debug/deps/libc.so.6"
[2023-05-27T14:39:57Z INFO openat] open (path CStr): "/home/taoky/Projects/greenhook/target/debug/glibc-hwcaps/x86-64-v3/libc.so.6"
[2023-05-27T14:39:57Z INFO openat] open (path CStr): "/home/taoky/Projects/greenhook/target/debug/glibc-hwcaps/x86-64-v2/libc.so.6"
[2023-05-27T14:39:57Z INFO openat] open (path CStr): "/home/taoky/Projects/greenhook/target/debug/libc.so.6"
[2023-05-27T14:39:57Z INFO openat] open (path CStr): "/home/taoky/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/glibc-hwcaps/x86-64-v3/libc.so.6"
[2023-05-27T14:39:57Z INFO openat] open (path CStr): "/home/taoky/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/glibc-hwcaps/x86-64-v2/libc.so.6"
[2023-05-27T14:39:57Z INFO openat] open (path CStr): "/home/taoky/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libc.so.6"
[2023-05-27T14:39:57Z INFO openat] open (path CStr): "/home/taoky/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/glibc-hwcaps/x86-64-v3/libc.so.6"
[2023-05-27T14:39:57Z INFO openat] open (path CStr): "/home/taoky/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/glibc-hwcaps/x86-64-v2/libc.so.6"
[2023-05-27T14:39:57Z INFO openat] open (path CStr): "/home/taoky/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/libc.so.6"
[2023-05-27T14:39:57Z INFO openat] open (path CStr): "/etc/ld.so.cache"
[2023-05-27T14:39:57Z INFO openat] open (path CStr): "/usr/lib/libc.so.6"
[2023-05-27T14:39:57Z INFO openat] open (path CStr): "/usr/lib/locale/locale-archive"
[2023-05-27T14:39:57Z INFO openat] open (path CStr): "/etc/passwd"
# Generated by NetworkManager
...
See examples/binder.rs
for a more complicated example. It resembles the logic of https://github.com/pdlan/binder in Rust.
continue_syscall
can be dangerous (thus it is marked as unsafe
here).seccomp_unotify(2)
.