Skip to main content

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 macOS. 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 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#[doc(hidden)]
27#[doc = include_str!("../README.md")]
28mod readme {}
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
35    /// them in `buf`.
36    fn copy_address(&self, addr: usize, buf: &mut [u8]) -> io::Result<()>;
37}
38
39/// A process ID.
40pub use crate::platform::Pid;
41/// A handle to a running process. This is not a process ID on all platforms.
42///
43/// For convenience, this crate implements `TryFrom`-backed conversions from
44/// `Pid` to `ProcessHandle`.
45///
46/// # Examples
47///
48/// ```rust,no_run
49/// use read_process_memory::*;
50/// use std::convert::TryInto;
51/// use std::io;
52///
53/// fn pid_to_handle(pid: Pid) -> io::Result<ProcessHandle> {
54///     Ok(pid.try_into()?)
55/// }
56/// ```
57///
58/// This operation is not guaranteed to succeed. Specifically, on Windows
59/// `OpenProcess` may fail. On macOS `task_for_pid` will generally fail
60/// unless run as root, and even then it may fail when called on certain
61/// programs; it may however run without root on the current process.
62pub use crate::platform::ProcessHandle;
63
64#[cfg(target_os = "linux")]
65mod platform {
66    use libc::{c_void, iovec, pid_t, process_vm_readv};
67    use std::convert::TryFrom;
68    use std::fs;
69    use std::io;
70    use std::io::Read;
71    use std::io::Seek;
72    use std::process::Child;
73
74    use super::CopyAddress;
75
76    /// On Linux a `Pid` is just a `libc::pid_t`.
77    pub type Pid = pid_t;
78    /// On Linux a `ProcessHandle` is just a `libc::pid_t`.
79    #[derive(Clone)]
80    pub struct ProcessHandle(Pid);
81
82    /// On Linux, process handle is a pid.
83    impl TryFrom<Pid> for ProcessHandle {
84        type Error = io::Error;
85
86        fn try_from(pid: Pid) -> io::Result<Self> {
87            Ok(Self(pid))
88        }
89    }
90
91    /// A `process::Child` always has a pid, which is all we need on Linux.
92    impl TryFrom<&Child> for ProcessHandle {
93        type Error = io::Error;
94
95        fn try_from(child: &Child) -> io::Result<Self> {
96            Self::try_from(child.id() as Pid)
97        }
98    }
99
100    impl CopyAddress for ProcessHandle {
101        fn copy_address(&self, addr: usize, buf: &mut [u8]) -> io::Result<()> {
102            let local_iov = iovec {
103                iov_base: buf.as_mut_ptr() as *mut c_void,
104                iov_len: buf.len(),
105            };
106            let remote_iov = iovec {
107                iov_base: addr as *mut c_void,
108                iov_len: buf.len(),
109            };
110            let result = unsafe { process_vm_readv(self.0, &local_iov, 1, &remote_iov, 1, 0) };
111            if result == -1 {
112                match io::Error::last_os_error().raw_os_error() {
113                    Some(libc::ENOSYS) | Some(libc::EPERM) => {
114                        // fallback to reading /proc/$pid/mem if kernel does not
115                        // implement process_vm_readv()
116                        let mut procmem = fs::File::open(format!("/proc/{}/mem", self.0))?;
117                        procmem.seek(io::SeekFrom::Start(addr as u64))?;
118                        procmem.read_exact(buf)
119                    }
120                    _ => Err(io::Error::last_os_error()),
121                }
122            } else if result as usize != buf.len() {
123                Err(io::Error::new(
124                    io::ErrorKind::UnexpectedEof,
125                    format!(
126                        "Mismatched read sizes for `process_vm_readv` (expected {}, got {})",
127                        buf.len(),
128                        result
129                    ),
130                ))
131            } else {
132                Ok(())
133            }
134        }
135    }
136}
137
138#[cfg(target_os = "macos")]
139mod platform {
140    use libc::{c_int, pid_t};
141    use mach2::kern_return::{kern_return_t, KERN_SUCCESS};
142    use mach2::port::{mach_port_name_t, mach_port_t, MACH_PORT_NULL};
143    use mach2::vm_types::{mach_vm_address_t, mach_vm_size_t};
144
145    use std::convert::TryFrom;
146    use std::io;
147    use std::process::Child;
148
149    use super::CopyAddress;
150
151    #[allow(non_camel_case_types)]
152    type vm_map_t = mach_port_t;
153    #[allow(non_camel_case_types)]
154    type vm_address_t = mach_vm_address_t;
155    #[allow(non_camel_case_types)]
156    type vm_size_t = mach_vm_size_t;
157
158    /// On macOS a `Pid` is just a `libc::pid_t`.
159    pub type Pid = pid_t;
160    /// On macOS a `ProcessHandle` is a mach port.
161    #[derive(Clone)]
162    pub struct ProcessHandle(mach_port_name_t);
163
164    extern "C" {
165        fn vm_read_overwrite(
166            target_task: vm_map_t,
167            address: vm_address_t,
168            size: vm_size_t,
169            data: vm_address_t,
170            out_size: *mut vm_size_t,
171        ) -> kern_return_t;
172    }
173
174    /// A small wrapper around `task_for_pid`, which takes a pid and returns the
175    /// mach port representing its task.
176    fn task_for_pid(pid: Pid) -> io::Result<mach_port_name_t> {
177        if pid == unsafe { libc::getpid() } as Pid {
178            return Ok(unsafe { mach2::traps::mach_task_self() });
179        }
180
181        let mut task: mach_port_name_t = MACH_PORT_NULL;
182
183        unsafe {
184            let result =
185                mach2::traps::task_for_pid(mach2::traps::mach_task_self(), pid as c_int, &mut task);
186            if result != KERN_SUCCESS {
187                return Err(io::Error::last_os_error());
188            }
189        }
190
191        Ok(task)
192    }
193
194    /// A `Pid` can be turned into a `ProcessHandle` with `task_for_pid`.
195    impl TryFrom<Pid> for ProcessHandle {
196        type Error = io::Error;
197
198        fn try_from(pid: Pid) -> io::Result<Self> {
199            Ok(Self(task_for_pid(pid)?))
200        }
201    }
202
203    /// On Darwin, process handle is a mach port name.
204    impl TryFrom<mach_port_name_t> for ProcessHandle {
205        type Error = io::Error;
206
207        fn try_from(mach_port_name: mach_port_name_t) -> io::Result<Self> {
208            Ok(Self(mach_port_name))
209        }
210    }
211
212    /// This `TryFrom` impl simply calls the `TryFrom` impl for `Pid`.
213    ///
214    /// Unfortunately spawning a process on macOS does not hand back a mach
215    /// port by default (you have to jump through several hoops to get at it),
216    /// so there's no simple implementation of `TryFrom` Child
217    /// `for::Child`. This implementation is just provided for symmetry
218    /// with other platforms to make writing cross-platform code easier.
219    ///
220    /// Ideally we would provide an implementation of
221    /// `std::process::Command::spawn` that jumped through those hoops and
222    /// provided the task port.
223    impl TryFrom<&Child> for ProcessHandle {
224        type Error = io::Error;
225
226        fn try_from(child: &Child) -> io::Result<Self> {
227            Self::try_from(child.id() as Pid)
228        }
229    }
230
231    /// Use `vm_read` to read memory from another process on macOS.
232    impl CopyAddress for ProcessHandle {
233        fn copy_address(&self, addr: usize, buf: &mut [u8]) -> io::Result<()> {
234            let mut read_len = buf.len() as vm_size_t;
235            let result = unsafe {
236                vm_read_overwrite(
237                    self.0,
238                    addr as vm_address_t,
239                    buf.len() as vm_size_t,
240                    buf.as_mut_ptr() as vm_address_t,
241                    &mut read_len,
242                )
243            };
244
245            if read_len != buf.len() as vm_size_t {
246                return Err(io::Error::new(
247                    io::ErrorKind::UnexpectedEof,
248                    format!(
249                        "Mismatched read sizes for `vm_read` (expected {}, got {})",
250                        buf.len(),
251                        read_len
252                    ),
253                ));
254            }
255
256            if result != KERN_SUCCESS {
257                return Err(io::Error::last_os_error());
258            }
259            Ok(())
260        }
261    }
262}
263
264#[cfg(target_os = "freebsd")]
265mod platform {
266    use libc::{c_int, c_void, pid_t};
267    use libc::{waitpid, EBUSY, PIOD_READ_D, PT_ATTACH, PT_DETACH, PT_IO, WIFSTOPPED};
268    use std::convert::TryFrom;
269    use std::process::Child;
270    use std::{io, ptr};
271
272    use super::CopyAddress;
273
274    /// On FreeBSD a `Pid` is just a `libc::pid_t`.
275    pub type Pid = pid_t;
276    /// On FreeBSD a `ProcessHandle` is just a `libc::pid_t`.
277    #[derive(Clone)]
278    pub struct ProcessHandle(Pid);
279
280    #[repr(C)]
281    struct PtraceIoDesc {
282        piod_op: c_int,
283        piod_offs: *mut c_void,
284        piod_addr: *mut c_void,
285        piod_len: usize,
286    }
287
288    /// If process is already traced, PT_ATTACH call returns
289    /// EBUSY. This structure is needed to avoid double locking the process.
290    /// - `Release` variant means we can safely detach from the process.
291    /// - `NoRelease` variant means that process was already attached, so we
292    ///   shall not attempt to detach from it.
293    #[derive(PartialEq)]
294    enum PtraceLockState {
295        Release,
296        NoRelease,
297    }
298
299    extern "C" {
300        /// libc version of ptrace takes *mut i8 as third argument,
301        /// which is not very ergonomic if we have a struct.
302        fn ptrace(request: c_int, pid: pid_t, io_desc: *mut PtraceIoDesc, data: c_int) -> c_int;
303    }
304
305    /// On FreeBSD, process handle is a pid.
306    impl TryFrom<Pid> for ProcessHandle {
307        type Error = io::Error;
308
309        fn try_from(pid: Pid) -> io::Result<Self> {
310            Ok(Self(pid))
311        }
312    }
313
314    /// A `process::Child` always has a pid, which is all we need on FreeBSD.
315    impl TryFrom<&Child> for ProcessHandle {
316        type Error = io::Error;
317
318        fn try_from(child: &Child) -> io::Result<Self> {
319            Self::try_from(child.id() as Pid)
320        }
321    }
322
323    /// Attach to a process `pid` and wait for the process to be stopped.
324    fn ptrace_attach(pid: Pid) -> io::Result<PtraceLockState> {
325        let attach_status = unsafe { ptrace(PT_ATTACH, pid, ptr::null_mut(), 0) };
326
327        let last_error = io::Error::last_os_error();
328
329        if let Some(error) = last_error.raw_os_error() {
330            if attach_status == -1 {
331                return match error {
332                    EBUSY => Ok(PtraceLockState::NoRelease),
333                    _ => Err(last_error),
334                };
335            }
336        }
337
338        let mut wait_status = 0;
339
340        let stopped = unsafe {
341            waitpid(pid, &mut wait_status as *mut _, 0);
342            WIFSTOPPED(wait_status)
343        };
344
345        if !stopped {
346            Err(io::Error::last_os_error())
347        } else {
348            Ok(PtraceLockState::Release)
349        }
350    }
351
352    /// Read process `pid` memory at `addr` to `buf` via PT_IO ptrace call.
353    fn ptrace_io(pid: Pid, addr: usize, buf: &mut [u8]) -> io::Result<()> {
354        let mut ptrace_io_desc = PtraceIoDesc {
355            piod_op: PIOD_READ_D,
356            piod_offs: addr as *mut c_void,
357            piod_addr: buf.as_mut_ptr() as *mut c_void,
358            piod_len: buf.len(),
359        };
360
361        let result = unsafe { ptrace(PT_IO, pid, &mut ptrace_io_desc as *mut _, 0) };
362
363        if result == -1 {
364            Err(io::Error::last_os_error())
365        } else if ptrace_io_desc.piod_len != buf.len() {
366            Err(io::Error::new(
367                io::ErrorKind::UnexpectedEof,
368                format!(
369                    "Mismatched read sizes for `PT_IO` (expected {}, got {})",
370                    buf.len(),
371                    ptrace_io_desc.piod_len
372                ),
373            ))
374        } else {
375            Ok(())
376        }
377    }
378
379    /// Detach from the process `pid`.
380    fn ptrace_detach(pid: Pid) -> io::Result<()> {
381        let detach_status = unsafe { ptrace(PT_DETACH, pid, ptr::null_mut(), 0) };
382
383        if detach_status == -1 {
384            Err(io::Error::last_os_error())
385        } else {
386            Ok(())
387        }
388    }
389
390    impl CopyAddress for ProcessHandle {
391        fn copy_address(&self, addr: usize, buf: &mut [u8]) -> io::Result<()> {
392            let should_detach = ptrace_attach(self.0)? == PtraceLockState::Release;
393
394            let result = ptrace_io(self.0, addr, buf);
395            if should_detach {
396                ptrace_detach(self.0)?
397            }
398            result
399        }
400    }
401}
402
403#[cfg(windows)]
404mod platform {
405    use std::convert::TryFrom;
406    use std::io;
407    use std::mem;
408    use std::ops::Deref;
409    use std::os::raw::c_void;
410    use std::os::windows::io::RawHandle;
411    use std::process::Child;
412    use std::ptr;
413    use std::sync::Arc;
414    use windows_sys::Win32::Foundation::{CloseHandle, HANDLE};
415    use windows_sys::Win32::System::Diagnostics::Debug::ReadProcessMemory;
416    use windows_sys::Win32::System::Threading::{OpenProcess, PROCESS_VM_READ};
417
418    use super::CopyAddress;
419
420    /// On Windows a `Pid` is a `DWORD`.
421    pub type Pid = u32;
422    #[derive(Eq, PartialEq, Hash)]
423    struct ProcessHandleInner(HANDLE);
424
425    // A HANDLE is an index into the process-wide kernel handle table rather than
426    // an address, so it stays valid on any thread
427    unsafe impl Send for ProcessHandleInner {}
428    unsafe impl Sync for ProcessHandleInner {}
429
430    /// On Windows a `ProcessHandle` is a `HANDLE`.
431    #[derive(Clone, Eq, PartialEq, Hash)]
432    pub struct ProcessHandle(Arc<ProcessHandleInner>);
433
434    impl Deref for ProcessHandle {
435        type Target = HANDLE;
436
437        fn deref(&self) -> &Self::Target {
438            &self.0 .0
439        }
440    }
441
442    impl Drop for ProcessHandleInner {
443        fn drop(&mut self) {
444            unsafe { CloseHandle(self.0) };
445        }
446    }
447
448    /// A `Pid` can be turned into a `ProcessHandle` with `OpenProcess`.
449    impl TryFrom<Pid> for ProcessHandle {
450        type Error = io::Error;
451
452        fn try_from(pid: Pid) -> io::Result<Self> {
453            let handle = unsafe { OpenProcess(PROCESS_VM_READ, 0, pid) };
454            if handle.is_null() {
455                Err(io::Error::last_os_error())
456            } else {
457                Ok(Self(Arc::new(ProcessHandleInner(handle))))
458            }
459        }
460    }
461
462    /// A `std::process::Child` can be turned into a `ProcessHandle` with
463    /// `OpenProcess`. The `Child` keeps ownership of its own handle.
464    impl TryFrom<&Child> for ProcessHandle {
465        type Error = io::Error;
466
467        fn try_from(child: &Child) -> io::Result<Self> {
468            Self::try_from(child.id() as Pid)
469        }
470    }
471
472    /// Wrap an existing `RawHandle` in a `ProcessHandle`.
473    ///
474    /// This **takes ownership** of `handle`. The resulting `ProcessHandle`
475    /// closes it with `CloseHandle` once the last clone is dropped. Pass an
476    /// owned handle (e.g. from `OpenProcess`), not a borrowed one such as
477    /// `Child::as_raw_handle`.
478    impl From<RawHandle> for ProcessHandle {
479        fn from(handle: RawHandle) -> Self {
480            Self(Arc::new(ProcessHandleInner(handle as HANDLE)))
481        }
482    }
483
484    /// Use `ReadProcessMemory` to read memory from another process on Windows.
485    impl CopyAddress for ProcessHandle {
486        fn copy_address(&self, addr: usize, buf: &mut [u8]) -> io::Result<()> {
487            if buf.is_empty() {
488                return Ok(());
489            }
490
491            if unsafe {
492                ReadProcessMemory(
493                    self.0 .0,
494                    addr as *const c_void,
495                    buf.as_mut_ptr().cast(),
496                    mem::size_of_val(buf),
497                    ptr::null_mut(),
498                )
499            } == 0
500            {
501                Err(io::Error::last_os_error())
502            } else {
503                Ok(())
504            }
505        }
506    }
507}
508
509/// Copy `length` bytes of memory at `addr` from `source`.
510///
511/// This is just a convenient way to call `CopyAddress::copy_address` without
512/// having to provide your own buffer.
513pub fn copy_address<T>(addr: usize, length: usize, source: &T) -> io::Result<Vec<u8>>
514where
515    T: CopyAddress,
516{
517    log::debug!("copy_address: addr: {:x}", addr);
518
519    let mut copy = vec![0; length];
520
521    source
522        .copy_address(addr, &mut copy)
523        .map_err(|e| {
524            log::warn!("copy_address failed for {:x}: {:?}", addr, e);
525            e
526        })
527        .and(Ok(copy))
528}
529
530#[cfg(test)]
531mod test {
532    use super::*;
533    use std::convert::TryFrom;
534    use std::env;
535    use std::io::{self, BufRead, BufReader};
536    use std::path::PathBuf;
537    use std::process::{Child, Command, Stdio};
538
539    #[allow(unused)]
540    const fn assert_send_sync<T: Send + Sync>() {}
541    const _: () = assert_send_sync::<ProcessHandle>();
542    const _: () = assert_send_sync::<Pid>();
543
544    fn test_process_path() -> Option<PathBuf> {
545        env::current_exe().ok().and_then(|p| {
546            p.parent().map(|p| {
547                p.with_file_name("test")
548                    .with_extension(env::consts::EXE_EXTENSION)
549            })
550        })
551    }
552
553    fn read_test_process(args: Option<&[&str]>) -> io::Result<Vec<u8>> {
554        read_test_process_with(args, |child| ProcessHandle::try_from(child.id() as Pid))
555    }
556
557    /// Spawn the test child, build a handle to it via `make_handle`, and read
558    /// the block of memory it advertises on stdout.
559    ///
560    /// The handle is dropped before `child.wait()` so that both the wrapper's
561    /// `CloseHandle` (on Windows) and the `Child`'s own run against a live
562    /// process: if the two ever closed the same handle, the `wait()` here would
563    /// fail. This is what exercises the `TryFrom<&Child>` ownership contract.
564    fn read_test_process_with<F>(args: Option<&[&str]>, make_handle: F) -> io::Result<Vec<u8>>
565    where
566        F: FnOnce(&Child) -> io::Result<ProcessHandle>,
567    {
568        let path = test_process_path().unwrap();
569        let mut cmd = Command::new(&path);
570        {
571            cmd.stdin(Stdio::piped()).stdout(Stdio::piped());
572        }
573        if let Some(a) = args {
574            cmd.args(a);
575        }
576        let mut child = cmd.spawn()?;
577        let handle = make_handle(&child)?;
578        // The test program prints the address and size.
579        // See `src/bin/test.rs` for its source.
580        let reader = BufReader::new(child.stdout.take().unwrap());
581        let line = reader.lines().next().unwrap().unwrap();
582        let bits = line.split(' ').collect::<Vec<_>>();
583        let addr = usize::from_str_radix(&bits[0][2..], 16).unwrap();
584        let size = bits[1].parse::<usize>().unwrap();
585        let mem = copy_address(addr, size, &handle)?;
586        drop(handle);
587        child.wait()?;
588        Ok(mem)
589    }
590
591    #[test]
592    fn test_read_small() {
593        let mem = read_test_process(None).unwrap();
594        assert_eq!(mem, (0..32u8).collect::<Vec<u8>>());
595    }
596
597    #[test]
598    fn test_read_large() {
599        // 20,000 should be greater than a single page on most systems.
600        // macOS on ARM is 16384.
601        const SIZE: usize = 20_000;
602        let arg = format!("{}", SIZE);
603        let mem = read_test_process(Some(&[&arg])).unwrap();
604        let expected = (0..SIZE)
605            .map(|v| (v % (u8::max_value() as usize + 1)) as u8)
606            .collect::<Vec<u8>>();
607        assert_eq!(mem, expected);
608    }
609
610    #[cfg(target_os = "linux")]
611    #[test]
612    fn test_read_fails_on_short_read() {
613        let page = unsafe { libc::sysconf(libc::_SC_PAGESIZE) } as usize;
614        let mem = unsafe {
615            libc::mmap(
616                std::ptr::null_mut(),
617                2 * page,
618                libc::PROT_READ | libc::PROT_WRITE,
619                libc::MAP_PRIVATE | libc::MAP_ANONYMOUS,
620                -1,
621                0,
622            )
623        };
624        assert_ne!(mem, libc::MAP_FAILED);
625        assert_eq!(
626            unsafe { libc::mprotect((mem as usize + page) as *mut _, page, libc::PROT_NONE) },
627            0
628        );
629
630        let handle = ProcessHandle::try_from(std::process::id() as Pid).unwrap();
631        let end = mem as usize + page;
632        let mut buf = [0u8; 8];
633        assert!(handle.copy_address(end - 8, &mut buf).is_ok());
634        let err = handle.copy_address(end - 4, &mut buf).unwrap_err();
635        assert_eq!(err.kind(), io::ErrorKind::UnexpectedEof);
636        unsafe { libc::munmap(mem, 2 * page) };
637    }
638
639    #[test]
640    fn test_read_from_child_handle() {
641        let mem = read_test_process_with(None, |child| ProcessHandle::try_from(child)).unwrap();
642        assert_eq!(mem, (0..32u8).collect::<Vec<u8>>());
643    }
644}