benfred_read_process_memory/
lib.rs

1//! Read memory from another process' address space.
2//!
3//! This crate provides a trait—[`CopyAddress`](trait.CopyAddress.html),
4//! and a helper function—[`copy_address`](fn.copy_address.html) that
5//! allow reading memory from another process.
6//!
7//! Note: you may not always have permission to read memory from another
8//! process! This may require `sudo` on some systems, and may fail even with
9//! `sudo` on OS X. You are most likely to succeed if you are attempting to
10//! read a process that you have spawned yourself.
11//!
12//! # Examples
13//!
14//! ```rust,no_run
15//! # use std::convert::TryInto;
16//! # use std::io;
17//! use benfred_read_process_memory::*;
18//!
19//! # fn foo(pid: Pid, address: usize, size: usize) -> io::Result<()> {
20//! let handle: ProcessHandle = pid.try_into()?;
21//! let bytes = copy_address(address, size, &handle)?;
22//! # Ok(())
23//! # }
24//! ```
25
26#[macro_use]
27extern crate log;
28extern crate libc;
29
30use std::io;
31
32/// A trait that provides a method for reading memory from another process.
33pub trait CopyAddress {
34    /// Try to copy `buf.len()` bytes from `addr` in the process `self`, placing them in `buf`.
35    fn copy_address(&self, addr: usize, buf: &mut [u8]) -> io::Result<()>;
36}
37
38/// A process ID.
39pub use platform::Pid;
40/// A handle to a running process. This is not a process ID on all platforms.
41///
42/// For convenience, this crate implements `TryFrom`-backed conversions from
43/// `Pid` to `ProcessHandle`.
44///
45/// # Examples
46///
47/// ```rust,no_run
48/// use std::convert::TryInto;
49/// use std::io;
50/// use benfred_read_process_memory::*;
51///
52/// fn pid_to_handle(pid: Pid) -> io::Result<ProcessHandle> {
53///   Ok(pid.try_into()?)
54/// }
55/// ```
56///
57/// This operation is not guaranteed to succeed. Specifically, on Windows
58/// `OpenProcess` may fail, and on OS X `task_for_pid` will generally fail
59/// unless run as root, and even then it may fail when called on certain
60/// programs.
61pub use platform::ProcessHandle;
62
63#[cfg(target_os="linux")]
64mod platform {
65    use libc::{pid_t, c_void, iovec, process_vm_readv};
66    use std::convert::TryFrom;
67    use std::io;
68    use std::fs;
69    use std::io::Seek;
70    use std::io::Read;
71    use std::process::Child;
72
73    use super::{CopyAddress};
74
75    /// On Linux a `Pid` is just a `libc::pid_t`.
76    pub type Pid = pid_t;
77    /// On Linux a `ProcessHandle` is just a `libc::pid_t`.
78    #[derive(Copy, Clone)]
79    pub struct ProcessHandle(Pid);
80
81    /// On Linux, process handle is a pid.
82    impl TryFrom<Pid> for ProcessHandle {
83        type Error = io::Error;
84
85        fn try_from(pid: Pid) -> io::Result<Self> {
86            Ok(Self(pid))
87        }
88    }
89
90    /// A `process::Child` always has a pid, which is all we need on Linux.
91    impl TryFrom<&Child> for ProcessHandle {
92        type Error = io::Error;
93
94        fn try_from(child: &Child) -> io::Result<Self> {
95            Self::try_from(child.id() as Pid)
96        }
97    }
98
99    impl CopyAddress for ProcessHandle {
100        fn copy_address(&self, addr: usize, buf: &mut [u8]) -> io::Result<()> {
101            let local_iov = iovec {
102                iov_base: buf.as_mut_ptr() as *mut c_void,
103                iov_len: buf.len(),
104            };
105            let remote_iov = iovec {
106                iov_base: addr as *mut c_void,
107                iov_len: buf.len(),
108            };
109            let result = unsafe { process_vm_readv(self.0, &local_iov, 1, &remote_iov, 1, 0) };
110            if result == -1 {
111                if let Some(libc::ENOSYS) = io::Error::last_os_error().raw_os_error() {
112                    // fallback to reading /proc/$pid/mem if kernel does not
113                    // implement process_vm_readv()
114                    let mut procmem = fs::File::open(format!("/proc/{}/mem", self.0))?;
115                    procmem.seek(io::SeekFrom::Start(addr as u64))?;
116                    return procmem.read_exact(buf);
117                } else {
118                    Err(io::Error::last_os_error())
119                }
120            } else {
121                Ok(())
122            }
123        }
124    }
125}
126
127#[cfg(target_os="macos")]
128mod platform {
129    extern crate mach;
130
131    use libc::{pid_t, c_int};
132    use self::mach::kern_return::{kern_return_t, KERN_SUCCESS};
133    use self::mach::port::{mach_port_t, mach_port_name_t, MACH_PORT_NULL};
134    use self::mach::vm_types::{mach_vm_address_t, mach_vm_size_t};
135
136    use std::convert::TryFrom;
137    use std::io;
138    use std::process::Child;
139
140    use super::{CopyAddress};
141
142    #[allow(non_camel_case_types)]
143    type vm_map_t = mach_port_t;
144    #[allow(non_camel_case_types)]
145    type vm_address_t = mach_vm_address_t;
146    #[allow(non_camel_case_types)]
147    type vm_size_t = mach_vm_size_t;
148
149    /// On OS X a `Pid` is just a `libc::pid_t`.
150    pub type Pid = pid_t;
151    /// On OS X a `ProcessHandle` is a mach port.
152    #[derive(Copy, Clone)]
153    pub struct ProcessHandle(mach_port_name_t);
154
155    extern "C" {
156        fn vm_read_overwrite(target_task: vm_map_t,
157                             address: vm_address_t,
158                             size: vm_size_t,
159                             data: vm_address_t,
160                             out_size: *mut vm_size_t) -> kern_return_t;
161    }
162
163    /// A small wrapper around `task_for_pid`, which takes a pid and returns the mach port
164    /// representing its task.
165    fn task_for_pid(pid: Pid) -> io::Result<mach_port_name_t> {
166        let mut task: mach_port_name_t = MACH_PORT_NULL;
167
168        unsafe {
169            let result =
170                mach::traps::task_for_pid(mach::traps::mach_task_self(), pid as c_int, &mut task);
171            if result != KERN_SUCCESS {
172                return Err(io::Error::last_os_error());
173            }
174        }
175
176        Ok(task)
177    }
178
179    /// A `Pid` can be turned into a `ProcessHandle` with `task_for_pid`.
180    impl TryFrom<Pid> for ProcessHandle {
181        type Error = io::Error;
182
183        fn try_from(pid: Pid) -> io::Result<Self> {
184            Ok(Self(task_for_pid(pid)?))
185        }
186    }
187
188    /// On Darwin, process handle is a mach port name.
189    impl TryFrom<mach_port_name_t> for ProcessHandle {
190        type Error = io::Error;
191
192        fn try_from(mach_port_name: mach_port_name_t) -> io::Result<Self> {
193            Ok(Self(mach_port_name))
194        }
195    }
196
197    /// This `TryFrom` impl simply calls the `TryFrom` impl for `Pid`.
198    ///
199    /// Unfortunately spawning a process on OS X does not hand back a mach
200    /// port by default (you have to jump through several hoops to get at it),
201    /// so there's no simple implementation of `TryFrom` Child
202    /// `for::Child`. This implementation is just provided for symmetry
203    /// with other platforms to make writing cross-platform code easier.
204    ///
205    /// Ideally we would provide an implementation of `std::process::Command::spawn`
206    /// that jumped through those hoops and provided the task port.
207    impl TryFrom<&Child> for ProcessHandle {
208        type Error = io::Error;
209
210        fn try_from(child: &Child) -> io::Result<Self> {
211            Self::try_from(child.id() as Pid)
212        }
213    }
214
215    /// Use `vm_read` to read memory from another process on OS X.
216    impl CopyAddress for ProcessHandle {
217        fn copy_address(&self, addr: usize, buf: &mut [u8]) -> io::Result<()> {
218            let mut read_len = buf.len() as vm_size_t;
219            let result = unsafe {
220                vm_read_overwrite(self.0,
221                                  addr as vm_address_t,
222                                  buf.len() as vm_size_t,
223                                  buf.as_mut_ptr() as vm_address_t,
224                                  &mut read_len)
225            };
226
227            if read_len != buf.len() as vm_size_t {
228                panic!("Mismatched read sizes for `vm_read` (expected {}, got {})",
229                       buf.len(),
230                       read_len)
231            }
232
233            if result != KERN_SUCCESS {
234                return Err(io::Error::last_os_error());
235            }
236            Ok(())
237        }
238    }
239}
240
241#[cfg(target_os="freebsd")]
242mod platform {
243    use libc::{pid_t, c_void, c_int};
244    use libc::{waitpid, WIFSTOPPED, PT_ATTACH, PT_DETACH, PT_IO, EBUSY};
245    use std::convert::TryFrom;
246    use std::{io, ptr};
247    use std::process::Child;
248
249    use super::{CopyAddress};
250
251    /// On FreeBSD a `Pid` is just a `libc::pid_t`.
252    pub type Pid = pid_t;
253    /// On FreeBSD a `ProcessHandle` is just a `libc::pid_t`.
254    #[derive(Copy, Clone)]
255    pub struct ProcessHandle(Pid);
256
257    #[repr(C)]
258    struct PtraceIoDesc {
259        piod_op: c_int,
260        piod_offs: *mut c_void,
261        piod_addr: *mut c_void,
262        piod_len: usize,
263    }
264
265    /// If process is already traced, PT_ATTACH call returns
266    /// EBUSY. This structure is needed to avoid double locking the process.
267    /// - `Release` variant means we can safely detach from the process.
268    /// - `NoRelease` variant means that process was already attached, so we
269    ///    shall not attempt to detach from it.
270    #[derive(PartialEq)]
271    enum PtraceLockState {
272        Release,
273        NoRelease,
274    }
275
276    extern "C" {
277        /// libc version of ptrace takes *mut i8 as third argument,
278        /// which is not very ergonomic if we have a struct.
279        fn ptrace(request: c_int,
280                  pid: pid_t,
281                  io_desc: *const PtraceIoDesc,
282                  data: c_int) -> c_int;
283    }
284
285    /// Following variable is not exposed via libc, yet.
286    /// https://github.com/freebsd/freebsd/blob/1d6e4247415d264485ee94b59fdbc12e0c566fd0/sys/sys/ptrace.h#L112
287    const PIOD_READ: c_int = 1;
288
289    /// On FreeBSD, process handle is a pid.
290    impl TryFrom<Pid> for ProcessHandle {
291        type Error = io::Error;
292
293        fn try_from(pid: Pid) -> io::Result<Self> {
294            Ok(Self(pid))
295        }
296    }
297
298    /// A `process::Child` always has a pid, which is all we need on FreeBSD.
299    impl TryFrom<&Child> for ProcessHandle {
300        type Error = io::Error;
301
302        fn try_from(child: &Child) -> io::Result<Self> {
303            Self::try_from(child.id() as Pid)
304        }
305    }
306
307    /// Attach to a process `pid` and wait for the process to be stopped.
308    fn ptrace_attach(pid: Pid) -> io::Result<PtraceLockState> {
309        let attach_status = unsafe {
310            ptrace(PT_ATTACH, pid, ptr::null_mut(), 0)
311        };
312
313        let last_error = io::Error::last_os_error();
314
315        if let Some(error) = last_error.raw_os_error() {
316            if attach_status == -1 {
317                return match error {
318                    EBUSY => Ok(PtraceLockState::NoRelease),
319                    _ => Err(last_error),
320                }
321            }
322        }
323
324        let mut wait_status = 0;
325
326        let stopped = unsafe {
327            waitpid(pid, &mut wait_status as *mut _, 0);
328            WIFSTOPPED(wait_status)
329        };
330
331        if !stopped {
332            Err(io::Error::last_os_error())
333        } else {
334            Ok(PtraceLockState::Release)
335        }
336    }
337
338    /// Read process `pid` memory at `addr` to `buf` via PT_IO ptrace call.
339    fn ptrace_io(pid: Pid, addr: usize, buf: &mut [u8])
340                 -> io::Result<()> {
341        let ptrace_io_desc = PtraceIoDesc {
342            piod_op: PIOD_READ,
343            piod_offs: addr as *mut c_void,
344            piod_addr: buf.as_mut_ptr() as *mut c_void,
345            piod_len: buf.len(),
346        };
347
348        let result = unsafe {
349            ptrace(PT_IO, pid, &ptrace_io_desc as *const _, 0)
350        };
351
352        if result == -1 {
353            Err(io::Error::last_os_error())
354        } else {
355            Ok(())
356        }
357    }
358
359
360    /// Detach from the process `pid`.
361    fn ptrace_detach(pid: Pid) -> io::Result<()> {
362        let detach_status = unsafe {
363            ptrace(PT_DETACH, pid, ptr::null_mut(), 0)
364        };
365
366        if detach_status == -1 {
367            Err(io::Error::last_os_error())
368        } else {
369            Ok(())
370        }
371    }
372
373    impl CopyAddress for ProcessHandle {
374        fn copy_address(&self, addr: usize, buf: &mut [u8]) -> io::Result<()> {
375            let should_detach = ptrace_attach(self.0)? == PtraceLockState::Release;
376
377            let result = ptrace_io(self.0, addr, buf);
378            if should_detach {
379                ptrace_detach(self.0)?
380            }
381           result
382        }
383    }
384}
385
386#[cfg(windows)]
387mod platform {
388    extern crate winapi;
389    extern crate kernel32;
390
391    use std::convert::TryFrom;
392    use std::io;
393    use std::mem;
394    use std::os::windows::io::{AsRawHandle, RawHandle};
395    use std::process::Child;
396    use std::ptr;
397
398    use super::{CopyAddress};
399
400    /// On Windows a `Pid` is a `DWORD`.
401    pub type Pid = winapi::DWORD;
402    /// On Windows a `ProcessHandle` is a `HANDLE`.
403    #[derive(Copy, Clone, Eq, PartialEq, Hash)]
404    pub struct ProcessHandle(pub RawHandle);
405
406    /// A `Pid` can be turned into a `ProcessHandle` with `OpenProcess`.
407    impl TryFrom<Pid> for ProcessHandle {
408        type Error = io::Error;
409
410        fn try_from(pid: Pid) -> io::Result<Self> {
411            let handle = unsafe {
412                kernel32::OpenProcess(winapi::winnt::PROCESS_VM_READ, winapi::FALSE, pid)
413            };
414            if handle == (0 as RawHandle) {
415                Err(io::Error::last_os_error())
416            } else {
417                Ok(Self(handle))
418            }
419        }
420    }
421
422    /// A `std::process::Child` has a `HANDLE` from calling `CreateProcess`.
423    impl TryFrom<&Child> for ProcessHandle {
424        type Error = io::Error;
425
426        fn try_from(child: &Child) -> io::Result<Self> {
427            Ok(Self(child.as_raw_handle()))
428        }
429    }
430
431    /// Use `ReadProcessMemory` to read memory from another process on Windows.
432    impl CopyAddress for ProcessHandle {
433        fn copy_address(&self, addr: usize, buf: &mut [u8]) -> io::Result<()> {
434            if buf.len() == 0 {
435                return Ok(());
436            }
437
438            if unsafe {
439                kernel32::ReadProcessMemory(self.0,
440                                            addr as winapi::LPVOID,
441                                            buf.as_mut_ptr() as winapi::LPVOID,
442                                            mem::size_of_val(buf) as winapi::SIZE_T,
443                                            ptr::null_mut())
444            } == winapi::FALSE {
445                Err(io::Error::last_os_error())
446            } else {
447                Ok(())
448            }
449        }
450    }
451}
452
453/// Copy `length` bytes of memory at `addr` from `source`.
454///
455/// This is just a convenient way to call `CopyAddress::copy_address` without
456/// having to provide your own buffer.
457pub fn copy_address<T>(addr: usize, length: usize, source: &T) -> io::Result<Vec<u8>>
458    where T: CopyAddress
459{
460    debug!("copy_address: addr: {:x}", addr);
461
462    let mut copy = vec![0; length];
463
464    source.copy_address(addr, &mut copy)
465        .map_err(|e| {
466            warn!("copy_address failed for {:x}: {:?}", addr, e);
467            e
468        })
469        .and(Ok(copy))
470}
471
472#[cfg(test)]
473mod test {
474    use super::*;
475    use std::convert::TryFrom;
476    use std::env;
477    use std::io::{self, BufRead, BufReader};
478    use std::path::PathBuf;
479    use std::process::{Child, Command, Stdio};
480
481    fn test_process_path() -> Option<PathBuf> {
482        env::current_exe()
483            .ok()
484            .and_then(|p| {
485                p.parent().map(|p| {
486                    p.with_file_name("test")
487                        .with_extension(env::consts::EXE_EXTENSION)
488                })
489            })
490    }
491
492    fn spawn_with_handle(cmd: &mut Command) -> io::Result<(Child, ProcessHandle)> {
493        let child = cmd.spawn()?;
494        let handle = ProcessHandle::try_from(child.id() as Pid)?;
495        Ok((child, handle))
496    }
497
498    fn read_test_process(args: Option<&[&str]>) -> io::Result<Vec<u8>> {
499        // Spawn a child process and attempt to read its memory.
500        let path = test_process_path().unwrap();
501        let mut cmd = Command::new(&path);
502        {
503            cmd.stdin(Stdio::piped())
504                .stdout(Stdio::piped());
505        }
506        if let Some(a) = args {
507            cmd.args(a);
508        }
509        let (mut child, handle) = spawn_with_handle(&mut cmd)?;
510        // The test program prints the address and size.
511        // See `src/bin/test.rs` for its source.
512        let reader = BufReader::new(child.stdout.take().unwrap());
513        let line = reader.lines().next().unwrap().unwrap();
514        let bits = line.split(' ').collect::<Vec<_>>();
515        let addr = usize::from_str_radix(&bits[0][2..], 16).unwrap();
516        let size = bits[1].parse::<usize>().unwrap();
517        let mem = copy_address(addr, size, &handle)?;
518        child.wait()?;
519        Ok(mem)
520    }
521
522    #[test]
523    fn test_read_small() {
524        let mem = read_test_process(None).unwrap();
525        assert_eq!(mem, (0..32u8).collect::<Vec<u8>>());
526    }
527
528    #[test]
529    fn test_read_large() {
530        // 5000 should be greater than a single page on most systems.
531        const SIZE: usize = 5000;
532        let arg = format!("{}", SIZE);
533        let mem = read_test_process(Some(&[&arg])).unwrap();
534        let expected =
535            (0..SIZE).map(|v| (v % (u8::max_value() as usize + 1)) as u8).collect::<Vec<u8>>();
536        assert_eq!(mem, expected);
537    }
538}