4 releases
0.1.3 | Feb 14, 2025 |
---|---|
0.1.2 | Nov 23, 2024 |
0.1.1 | May 14, 2023 |
0.1.0 | May 13, 2023 |
#216 in Unix APIs
161 downloads per month
Used in yaui
35KB
672 lines
Ptrace-do
Provides ability to use ptrace to execute functions in remote processes. Mostly for runtime shared library injection.
Platform Support
Ptrace-do supports the primary intended platform targets where this library would be of usage
Relevant
A fully Rust command line application providing a worked example command line interface for injecting shared objects into running unix processes. Serves as a great example of how this crate can be used to its fully capacity.
A fully Rust library providing the ability to hook a unix's application Procedural Link Table, PLT, at runtime. If you are striving to both inject a shared object into a running unix process, and would then like to detour functions such as libc::recv or libc::send for network packet inspection/augmentation; this library may of benefit for you.
This Rust library was named ptrace-do
, and I want to explicitly acknowledge this could have been an inappropriate and poor decision on my part. I used the same name as a historically popular c project, because the objectives of the projects were similar in that they both strive to provide an ergonomic interface over syscall injection with ptrace. To clarify the work in this crate has absolutely no relationship to the ptrace_do implemenation by emptymonkey. This crate is completely designed in Rust and not a crate providing a type safe rust api of the c ffi of emptymonkey's ptrace_do implementation.
Example
Invoking Libc Getpid in a remote process
use libc::pid_t;
use proc_maps::MapRange;
use ptrace_do::{ProcessIdentifier, RawProcess, TracedProcess};
fn find_mod_map_fuzzy(mod_name: &str, process: &impl ProcessIdentifier) -> Option<MapRange> {
use proc_maps::get_process_maps;
let maps = get_process_maps(process.pid()).expect("able to access proc maps");
maps.into_iter().find(|m| match m.filename() {
Some(p) => p.to_str().map(|s| s.contains(mod_name)).unwrap_or(false),
None => false,
})
}
pub fn find_remote_procedure(
mod_name: &str,
owned_process: &impl ProcessIdentifier,
remote_process: &impl ProcessIdentifier,
function_address: usize,
) -> Option<usize> {
let internal_module = find_mod_map_fuzzy(mod_name, owned_process)?;
println!(
"Identifed internal range {mod_name:?} ({:?}) at {:X?}",
internal_module.filename(),
internal_module.start()
);
let remote_module = find_mod_map_fuzzy(mod_name, remote_process)?;
println!(
"Identifed remote range {mod_name:?} ({:?}) at {:X?}",
remote_module.filename(),
remote_module.start()
);
Some(function_address - internal_module.start() + remote_module.start())
}
fn main() {
let target_pid: pid_t = 7777;
let traced_process = TracedProcess::attach(RawProcess::new(target_pid))
.expect("active process running with desired pid");
println!("Successfully attached to the process");
let libc_path = "libc";
let getpid_remote_procedure = find_remote_procedure(
libc_path,
&RawProcess::new(std::process::id() as pid_t),
&traced_process,
libc::getpid as usize,
)
.expect("active process links libc::getpid");
println!("Found remote getpid procedure at: {getpid_remote_procedure:X?}");
let frame = traced_process
.next_frame()
.expect("able to wait for a process frame");
println!("Successfully waited for a frame");
// we do not need the frame any further after this, but if you wanted to do more function calls you would hold on to the frame for further execution.
let (regs, _frame) = frame
.invoke_remote(getpid_remote_procedure, 0, &[])
.expect("able to execute getpid");
println!("Successfully executed remote getpid");
let traceed_pid = regs.return_value() as pid_t;
println!("The return value (Traceed Pid) was {traceed_pid}");
// we didn't hold on to the frame any further, but you could for instance recall getpid again here or chroot, etc...
}
Dependencies
~0.5–1.1MB
~23K SLoC