ptrace-do

Crates.ioptrace-do
lib.rsptrace-do
version0.1.3
sourcesrc
created_at2023-05-13 13:48:21.126814
updated_at2025-02-14 13:22:44.447952
descriptionFeatureful library for interacting with unix processes through ptrace, supports x86_64, i686, arm, aarch64 remote function calls
homepagehttps://github.com/ohchase/ptrace-do/
repositoryhttps://github.com/ohchase/ptrace-do/
max_upload_size
id863743
size57,906
(ohchase)

documentation

https://docs.rs/ptrace-do

README

Ptrace-do

Rust Crates.io Docs.rs Downloads Crates.io License

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

  • i686-unknown-linux-gnu
  • x86_64-unknown-linux-gnu
  • aarch64-unknown-linux-gnu
  • arm-unknown-linux-gnueabi
  • i686-linux-android
  • x86_64-linux-android
  • aarch64-linux-android
  • arm-linux-androideabi
  • armv7-linux-androideabi

Relevant

Yaui

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.

Plt-rs

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.

ptrace_do

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...
}
Commit count: 30

cargo fmt