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 {
123                Ok(())
124            }
125        }
126    }
127}
128
129#[cfg(target_os = "macos")]
130mod platform {
131    use libc::{c_int, pid_t};
132    use mach::kern_return::{kern_return_t, KERN_SUCCESS};
133    use mach::port::{mach_port_name_t, mach_port_t, MACH_PORT_NULL};
134    use 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 macOS a `Pid` is just a `libc::pid_t`.
150    pub type Pid = pid_t;
151    /// On macOS a `ProcessHandle` is a mach port.
152    #[derive(Clone)]
153    pub struct ProcessHandle(mach_port_name_t);
154
155    extern "C" {
156        fn vm_read_overwrite(
157            target_task: vm_map_t,
158            address: vm_address_t,
159            size: vm_size_t,
160            data: vm_address_t,
161            out_size: *mut vm_size_t,
162        ) -> kern_return_t;
163    }
164
165    /// A small wrapper around `task_for_pid`, which takes a pid and returns the
166    /// mach port representing its task.
167    fn task_for_pid(pid: Pid) -> io::Result<mach_port_name_t> {
168        if pid == unsafe { libc::getpid() } as Pid {
169            return Ok(unsafe { mach::traps::mach_task_self() });
170        }
171
172        let mut task: mach_port_name_t = MACH_PORT_NULL;
173
174        unsafe {
175            let result =
176                mach::traps::task_for_pid(mach::traps::mach_task_self(), pid as c_int, &mut task);
177            if result != KERN_SUCCESS {
178                return Err(io::Error::last_os_error());
179            }
180        }
181
182        Ok(task)
183    }
184
185    /// A `Pid` can be turned into a `ProcessHandle` with `task_for_pid`.
186    impl TryFrom<Pid> for ProcessHandle {
187        type Error = io::Error;
188
189        fn try_from(pid: Pid) -> io::Result<Self> {
190            Ok(Self(task_for_pid(pid)?))
191        }
192    }
193
194    /// On Darwin, process handle is a mach port name.
195    impl TryFrom<mach_port_name_t> for ProcessHandle {
196        type Error = io::Error;
197
198        fn try_from(mach_port_name: mach_port_name_t) -> io::Result<Self> {
199            Ok(Self(mach_port_name))
200        }
201    }
202
203    /// This `TryFrom` impl simply calls the `TryFrom` impl for `Pid`.
204    ///
205    /// Unfortunately spawning a process on macOS does not hand back a mach
206    /// port by default (you have to jump through several hoops to get at it),
207    /// so there's no simple implementation of `TryFrom` Child
208    /// `for::Child`. This implementation is just provided for symmetry
209    /// with other platforms to make writing cross-platform code easier.
210    ///
211    /// Ideally we would provide an implementation of
212    /// `std::process::Command::spawn` that jumped through those hoops and
213    /// provided the task port.
214    impl TryFrom<&Child> for ProcessHandle {
215        type Error = io::Error;
216
217        fn try_from(child: &Child) -> io::Result<Self> {
218            Self::try_from(child.id() as Pid)
219        }
220    }
221
222    /// Use `vm_read` to read memory from another process on macOS.
223    impl CopyAddress for ProcessHandle {
224        fn copy_address(&self, addr: usize, buf: &mut [u8]) -> io::Result<()> {
225            let mut read_len = buf.len() as vm_size_t;
226            let result = unsafe {
227                vm_read_overwrite(
228                    self.0,
229                    addr as vm_address_t,
230                    buf.len() as vm_size_t,
231                    buf.as_mut_ptr() as vm_address_t,
232                    &mut read_len,
233                )
234            };
235
236            if read_len != buf.len() as vm_size_t {
237                return Err(io::Error::new(
238                    io::ErrorKind::Other,
239                    format!(
240                        "Mismatched read sizes for `vm_read` (expected {}, got {})",
241                        buf.len(),
242                        read_len
243                    ),
244                ));
245            }
246
247            if result != KERN_SUCCESS {
248                return Err(io::Error::last_os_error());
249            }
250            Ok(())
251        }
252    }
253}
254
255#[cfg(target_os = "freebsd")]
256mod platform {
257    use libc::{c_int, c_void, pid_t};
258    use libc::{waitpid, EBUSY, PIOD_READ_D, PT_ATTACH, PT_DETACH, PT_IO, WIFSTOPPED};
259    use std::convert::TryFrom;
260    use std::process::Child;
261    use std::{io, ptr};
262
263    use super::CopyAddress;
264
265    /// On FreeBSD a `Pid` is just a `libc::pid_t`.
266    pub type Pid = pid_t;
267    /// On FreeBSD a `ProcessHandle` is just a `libc::pid_t`.
268    #[derive(Clone)]
269    pub struct ProcessHandle(Pid);
270
271    #[repr(C)]
272    struct PtraceIoDesc {
273        piod_op: c_int,
274        piod_offs: *mut c_void,
275        piod_addr: *mut c_void,
276        piod_len: usize,
277    }
278
279    /// If process is already traced, PT_ATTACH call returns
280    /// EBUSY. This structure is needed to avoid double locking the process.
281    /// - `Release` variant means we can safely detach from the process.
282    /// - `NoRelease` variant means that process was already attached, so we
283    ///   shall not attempt to detach from it.
284    #[derive(PartialEq)]
285    enum PtraceLockState {
286        Release,
287        NoRelease,
288    }
289
290    extern "C" {
291        /// libc version of ptrace takes *mut i8 as third argument,
292        /// which is not very ergonomic if we have a struct.
293        fn ptrace(request: c_int, pid: pid_t, io_desc: *const PtraceIoDesc, data: c_int) -> c_int;
294    }
295
296    /// On FreeBSD, process handle is a pid.
297    impl TryFrom<Pid> for ProcessHandle {
298        type Error = io::Error;
299
300        fn try_from(pid: Pid) -> io::Result<Self> {
301            Ok(Self(pid))
302        }
303    }
304
305    /// A `process::Child` always has a pid, which is all we need on FreeBSD.
306    impl TryFrom<&Child> for ProcessHandle {
307        type Error = io::Error;
308
309        fn try_from(child: &Child) -> io::Result<Self> {
310            Self::try_from(child.id() as Pid)
311        }
312    }
313
314    /// Attach to a process `pid` and wait for the process to be stopped.
315    fn ptrace_attach(pid: Pid) -> io::Result<PtraceLockState> {
316        let attach_status = unsafe { ptrace(PT_ATTACH, pid, ptr::null_mut(), 0) };
317
318        let last_error = io::Error::last_os_error();
319
320        if let Some(error) = last_error.raw_os_error() {
321            if attach_status == -1 {
322                return match error {
323                    EBUSY => Ok(PtraceLockState::NoRelease),
324                    _ => Err(last_error),
325                };
326            }
327        }
328
329        let mut wait_status = 0;
330
331        let stopped = unsafe {
332            waitpid(pid, &mut wait_status as *mut _, 0);
333            WIFSTOPPED(wait_status)
334        };
335
336        if !stopped {
337            Err(io::Error::last_os_error())
338        } else {
339            Ok(PtraceLockState::Release)
340        }
341    }
342
343    /// Read process `pid` memory at `addr` to `buf` via PT_IO ptrace call.
344    fn ptrace_io(pid: Pid, addr: usize, buf: &mut [u8]) -> io::Result<()> {
345        let ptrace_io_desc = PtraceIoDesc {
346            piod_op: PIOD_READ_D,
347            piod_offs: addr as *mut c_void,
348            piod_addr: buf.as_mut_ptr() as *mut c_void,
349            piod_len: buf.len(),
350        };
351
352        let result = unsafe { ptrace(PT_IO, pid, &ptrace_io_desc as *const _, 0) };
353
354        if result == -1 {
355            Err(io::Error::last_os_error())
356        } else {
357            Ok(())
358        }
359    }
360
361    /// Detach from the process `pid`.
362    fn ptrace_detach(pid: Pid) -> io::Result<()> {
363        let detach_status = unsafe { ptrace(PT_DETACH, pid, ptr::null_mut(), 0) };
364
365        if detach_status == -1 {
366            Err(io::Error::last_os_error())
367        } else {
368            Ok(())
369        }
370    }
371
372    impl CopyAddress for ProcessHandle {
373        fn copy_address(&self, addr: usize, buf: &mut [u8]) -> io::Result<()> {
374            let should_detach = ptrace_attach(self.0)? == PtraceLockState::Release;
375
376            let result = ptrace_io(self.0, addr, buf);
377            if should_detach {
378                ptrace_detach(self.0)?
379            }
380            result
381        }
382    }
383}
384
385#[cfg(windows)]
386mod platform {
387    use std::convert::TryFrom;
388    use std::io;
389    use std::mem;
390    use std::ops::Deref;
391    use std::os::windows::io::{AsRawHandle, RawHandle};
392    use std::process::Child;
393    use std::ptr;
394    use std::sync::Arc;
395    use winapi::{
396        shared::{basetsd, minwindef},
397        um::{handleapi, memoryapi, processthreadsapi, winnt},
398    };
399
400    use super::CopyAddress;
401
402    /// On Windows a `Pid` is a `DWORD`.
403    pub type Pid = minwindef::DWORD;
404    #[derive(Eq, PartialEq, Hash)]
405    struct ProcessHandleInner(RawHandle);
406    /// On Windows a `ProcessHandle` is a `HANDLE`.
407    #[derive(Clone, Eq, PartialEq, Hash)]
408    pub struct ProcessHandle(Arc<ProcessHandleInner>);
409
410    impl Deref for ProcessHandle {
411        type Target = RawHandle;
412
413        fn deref(&self) -> &Self::Target {
414            &self.0 .0
415        }
416    }
417
418    impl Drop for ProcessHandleInner {
419        fn drop(&mut self) {
420            unsafe { handleapi::CloseHandle(self.0) };
421        }
422    }
423
424    /// A `Pid` can be turned into a `ProcessHandle` with `OpenProcess`.
425    impl TryFrom<Pid> for ProcessHandle {
426        type Error = io::Error;
427
428        fn try_from(pid: Pid) -> io::Result<Self> {
429            let handle = unsafe { processthreadsapi::OpenProcess(winnt::PROCESS_VM_READ, 0, pid) };
430            if handle == (0 as RawHandle) {
431                Err(io::Error::last_os_error())
432            } else {
433                Ok(Self(Arc::new(ProcessHandleInner(handle))))
434            }
435        }
436    }
437
438    /// A `std::process::Child` has a `HANDLE` from calling `CreateProcess`.
439    impl TryFrom<&Child> for ProcessHandle {
440        type Error = io::Error;
441
442        fn try_from(child: &Child) -> io::Result<Self> {
443            Ok(Self(Arc::new(ProcessHandleInner(child.as_raw_handle()))))
444        }
445    }
446
447    impl From<RawHandle> for ProcessHandle {
448        fn from(handle: RawHandle) -> Self {
449            return Self(Arc::new(ProcessHandleInner(handle)));
450        }
451    }
452
453    /// Use `ReadProcessMemory` to read memory from another process on Windows.
454    impl CopyAddress for ProcessHandle {
455        fn copy_address(&self, addr: usize, buf: &mut [u8]) -> io::Result<()> {
456            if buf.len() == 0 {
457                return Ok(());
458            }
459
460            if unsafe {
461                memoryapi::ReadProcessMemory(
462                    self.0 .0,
463                    addr as minwindef::LPVOID,
464                    buf.as_mut_ptr() as minwindef::LPVOID,
465                    mem::size_of_val(buf) as basetsd::SIZE_T,
466                    ptr::null_mut(),
467                )
468            } == 0
469            {
470                Err(io::Error::last_os_error())
471            } else {
472                Ok(())
473            }
474        }
475    }
476}
477
478/// Copy `length` bytes of memory at `addr` from `source`.
479///
480/// This is just a convenient way to call `CopyAddress::copy_address` without
481/// having to provide your own buffer.
482pub fn copy_address<T>(addr: usize, length: usize, source: &T) -> io::Result<Vec<u8>>
483where
484    T: CopyAddress,
485{
486    log::debug!("copy_address: addr: {:x}", addr);
487
488    let mut copy = vec![0; length];
489
490    source
491        .copy_address(addr, &mut copy)
492        .map_err(|e| {
493            log::warn!("copy_address failed for {:x}: {:?}", addr, e);
494            e
495        })
496        .and(Ok(copy))
497}
498
499#[cfg(test)]
500mod test {
501    use super::*;
502    use std::convert::TryFrom;
503    use std::env;
504    use std::io::{self, BufRead, BufReader};
505    use std::path::PathBuf;
506    use std::process::{Child, Command, Stdio};
507
508    fn test_process_path() -> Option<PathBuf> {
509        env::current_exe().ok().and_then(|p| {
510            p.parent().map(|p| {
511                p.with_file_name("test")
512                    .with_extension(env::consts::EXE_EXTENSION)
513            })
514        })
515    }
516
517    fn spawn_with_handle(cmd: &mut Command) -> io::Result<(Child, ProcessHandle)> {
518        let child = cmd.spawn()?;
519        let handle = ProcessHandle::try_from(child.id() as Pid)?;
520        Ok((child, handle))
521    }
522
523    fn read_test_process(args: Option<&[&str]>) -> io::Result<Vec<u8>> {
524        // Spawn a child process and attempt to read its memory.
525        let path = test_process_path().unwrap();
526        let mut cmd = Command::new(&path);
527        {
528            cmd.stdin(Stdio::piped()).stdout(Stdio::piped());
529        }
530        if let Some(a) = args {
531            cmd.args(a);
532        }
533        let (mut child, handle) = spawn_with_handle(&mut cmd)?;
534        // The test program prints the address and size.
535        // See `src/bin/test.rs` for its source.
536        let reader = BufReader::new(child.stdout.take().unwrap());
537        let line = reader.lines().next().unwrap().unwrap();
538        let bits = line.split(' ').collect::<Vec<_>>();
539        let addr = usize::from_str_radix(&bits[0][2..], 16).unwrap();
540        let size = bits[1].parse::<usize>().unwrap();
541        let mem = copy_address(addr, size, &handle)?;
542        child.wait()?;
543        Ok(mem)
544    }
545
546    #[test]
547    fn test_read_small() {
548        let mem = read_test_process(None).unwrap();
549        assert_eq!(mem, (0..32u8).collect::<Vec<u8>>());
550    }
551
552    #[test]
553    fn test_read_large() {
554        // 20,000 should be greater than a single page on most systems.
555        // macOS on ARM is 16384.
556        const SIZE: usize = 20_000;
557        let arg = format!("{}", SIZE);
558        let mem = read_test_process(Some(&[&arg])).unwrap();
559        let expected = (0..SIZE)
560            .map(|v| (v % (u8::max_value() as usize + 1)) as u8)
561            .collect::<Vec<u8>>();
562        assert_eq!(mem, expected);
563    }
564}