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