read_process_memory_ptrace/
lib.rs1#[doc(hidden)]
27#[doc = include_str!("../README.md")]
28mod readme {}
29
30use std::io;
31
32pub trait CopyAddress {
34 fn copy_address(&self, addr: usize, buf: &mut [u8]) -> io::Result<()>;
37}
38
39pub use crate::platform::Pid;
41pub 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 pub type Pid = pid_t;
78 #[derive(Clone)]
80 pub struct ProcessHandle(Pid);
81
82 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 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 if let Some(libc::EPERM) = io::Error::last_os_error().raw_os_error() {
113 let mut procmem = fs::File::open(format!("/proc/{}/mem", self.0))?;
116 procmem.seek(io::SeekFrom::Start(addr as u64))?;
117 return procmem.read_exact(buf);
118 } else {
119 Err(io::Error::last_os_error())
120 }
121 } else {
122 Ok(())
123 }
124 }
125 }
126}
127
128#[cfg(target_os = "macos")]
129mod platform {
130 use libc::{c_int, pid_t};
131 use mach::kern_return::{kern_return_t, KERN_SUCCESS};
132 use mach::port::{mach_port_name_t, mach_port_t, MACH_PORT_NULL};
133 use mach::vm_types::{mach_vm_address_t, mach_vm_size_t};
134
135 use std::convert::TryFrom;
136 use std::io;
137 use std::process::Child;
138
139 use super::CopyAddress;
140
141 #[allow(non_camel_case_types)]
142 type vm_map_t = mach_port_t;
143 #[allow(non_camel_case_types)]
144 type vm_address_t = mach_vm_address_t;
145 #[allow(non_camel_case_types)]
146 type vm_size_t = mach_vm_size_t;
147
148 pub type Pid = pid_t;
150 #[derive(Clone)]
152 pub struct ProcessHandle(mach_port_name_t);
153
154 extern "C" {
155 fn vm_read_overwrite(
156 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,
161 ) -> kern_return_t;
162 }
163
164 fn task_for_pid(pid: Pid) -> io::Result<mach_port_name_t> {
167 let mut task: mach_port_name_t = MACH_PORT_NULL;
168
169 unsafe {
170 let result =
171 mach::traps::task_for_pid(mach::traps::mach_task_self(), pid as c_int, &mut task);
172 if result != KERN_SUCCESS {
173 return Err(io::Error::last_os_error());
174 }
175 }
176
177 Ok(task)
178 }
179
180 impl TryFrom<Pid> for ProcessHandle {
182 type Error = io::Error;
183
184 fn try_from(pid: Pid) -> io::Result<Self> {
185 Ok(Self(task_for_pid(pid)?))
186 }
187 }
188
189 impl TryFrom<mach_port_name_t> for ProcessHandle {
191 type Error = io::Error;
192
193 fn try_from(mach_port_name: mach_port_name_t) -> io::Result<Self> {
194 Ok(Self(mach_port_name))
195 }
196 }
197
198 impl TryFrom<&Child> for ProcessHandle {
210 type Error = io::Error;
211
212 fn try_from(child: &Child) -> io::Result<Self> {
213 Self::try_from(child.id() as Pid)
214 }
215 }
216
217 impl CopyAddress for ProcessHandle {
219 fn copy_address(&self, addr: usize, buf: &mut [u8]) -> io::Result<()> {
220 let mut read_len = buf.len() as vm_size_t;
221 let result = unsafe {
222 vm_read_overwrite(
223 self.0,
224 addr as vm_address_t,
225 buf.len() as vm_size_t,
226 buf.as_mut_ptr() as vm_address_t,
227 &mut read_len,
228 )
229 };
230
231 if read_len != buf.len() as vm_size_t {
232 panic!(
233 "Mismatched read sizes for `vm_read` (expected {}, got {})",
234 buf.len(),
235 read_len
236 )
237 }
238
239 if result != KERN_SUCCESS {
240 return Err(io::Error::last_os_error());
241 }
242 Ok(())
243 }
244 }
245}
246
247#[cfg(target_os = "freebsd")]
248mod platform {
249 use libc::{c_int, c_void, pid_t};
250 use libc::{waitpid, EBUSY, PIOD_READ_D, PT_ATTACH, PT_DETACH, PT_IO, WIFSTOPPED};
251 use std::convert::TryFrom;
252 use std::process::Child;
253 use std::{io, ptr};
254
255 use super::CopyAddress;
256
257 pub type Pid = pid_t;
259 #[derive(Clone)]
261 pub struct ProcessHandle(Pid);
262
263 #[repr(C)]
264 struct PtraceIoDesc {
265 piod_op: c_int,
266 piod_offs: *mut c_void,
267 piod_addr: *mut c_void,
268 piod_len: usize,
269 }
270
271 #[derive(PartialEq)]
277 enum PtraceLockState {
278 Release,
279 NoRelease,
280 }
281
282 extern "C" {
283 fn ptrace(request: c_int, pid: pid_t, io_desc: *const PtraceIoDesc, data: c_int) -> c_int;
286 }
287
288 impl TryFrom<Pid> for ProcessHandle {
290 type Error = io::Error;
291
292 fn try_from(pid: Pid) -> io::Result<Self> {
293 Ok(Self(pid))
294 }
295 }
296
297 impl TryFrom<&Child> for ProcessHandle {
299 type Error = io::Error;
300
301 fn try_from(child: &Child) -> io::Result<Self> {
302 Self::try_from(child.id() as Pid)
303 }
304 }
305
306 fn ptrace_attach(pid: Pid) -> io::Result<PtraceLockState> {
308 let attach_status = unsafe { ptrace(PT_ATTACH, pid, ptr::null_mut(), 0) };
309
310 let last_error = io::Error::last_os_error();
311
312 if let Some(error) = last_error.raw_os_error() {
313 if attach_status == -1 {
314 return match error {
315 EBUSY => Ok(PtraceLockState::NoRelease),
316 _ => Err(last_error),
317 };
318 }
319 }
320
321 let mut wait_status = 0;
322
323 let stopped = unsafe {
324 waitpid(pid, &mut wait_status as *mut _, 0);
325 WIFSTOPPED(wait_status)
326 };
327
328 if !stopped {
329 Err(io::Error::last_os_error())
330 } else {
331 Ok(PtraceLockState::Release)
332 }
333 }
334
335 fn ptrace_io(pid: Pid, addr: usize, buf: &mut [u8]) -> io::Result<()> {
337 let ptrace_io_desc = PtraceIoDesc {
338 piod_op: PIOD_READ_D,
339 piod_offs: addr as *mut c_void,
340 piod_addr: buf.as_mut_ptr() as *mut c_void,
341 piod_len: buf.len(),
342 };
343
344 let result = unsafe { ptrace(PT_IO, pid, &ptrace_io_desc as *const _, 0) };
345
346 if result == -1 {
347 Err(io::Error::last_os_error())
348 } else {
349 Ok(())
350 }
351 }
352
353 fn ptrace_detach(pid: Pid) -> io::Result<()> {
355 let detach_status = unsafe { ptrace(PT_DETACH, pid, ptr::null_mut(), 0) };
356
357 if detach_status == -1 {
358 Err(io::Error::last_os_error())
359 } else {
360 Ok(())
361 }
362 }
363
364 impl CopyAddress for ProcessHandle {
365 fn copy_address(&self, addr: usize, buf: &mut [u8]) -> io::Result<()> {
366 let should_detach = ptrace_attach(self.0)? == PtraceLockState::Release;
367
368 let result = ptrace_io(self.0, addr, buf);
369 if should_detach {
370 ptrace_detach(self.0)?
371 }
372 result
373 }
374 }
375}
376
377#[cfg(windows)]
378mod platform {
379 use std::convert::TryFrom;
380 use std::io;
381 use std::mem;
382 use std::ops::Deref;
383 use std::os::windows::io::{AsRawHandle, RawHandle};
384 use std::process::Child;
385 use std::ptr;
386 use std::sync::Arc;
387 use winapi::{
388 shared::{basetsd, minwindef},
389 um::{handleapi, memoryapi, processthreadsapi, winnt},
390 };
391
392 use super::CopyAddress;
393
394 pub type Pid = minwindef::DWORD;
396 #[derive(Eq, PartialEq, Hash)]
397 struct ProcessHandleInner(RawHandle);
398 #[derive(Clone, Eq, PartialEq, Hash)]
400 pub struct ProcessHandle(Arc<ProcessHandleInner>);
401
402 impl Deref for ProcessHandle {
403 type Target = RawHandle;
404
405 fn deref(&self) -> &Self::Target {
406 &self.0 .0
407 }
408 }
409
410 impl Drop for ProcessHandleInner {
411 fn drop(&mut self) {
412 unsafe { handleapi::CloseHandle(self.0) };
413 }
414 }
415
416 impl TryFrom<Pid> for ProcessHandle {
418 type Error = io::Error;
419
420 fn try_from(pid: Pid) -> io::Result<Self> {
421 let handle = unsafe { processthreadsapi::OpenProcess(winnt::PROCESS_VM_READ, 0, pid) };
422 if handle == (0 as RawHandle) {
423 Err(io::Error::last_os_error())
424 } else {
425 Ok(Self(Arc::new(ProcessHandleInner(handle))))
426 }
427 }
428 }
429
430 impl TryFrom<&Child> for ProcessHandle {
432 type Error = io::Error;
433
434 fn try_from(child: &Child) -> io::Result<Self> {
435 Ok(Self(Arc::new(ProcessHandleInner(child.as_raw_handle()))))
436 }
437 }
438
439 impl From<RawHandle> for ProcessHandle {
440 fn from(handle: RawHandle) -> Self {
441 return Self(Arc::new(ProcessHandleInner(handle)));
442 }
443 }
444
445 impl CopyAddress for ProcessHandle {
447 fn copy_address(&self, addr: usize, buf: &mut [u8]) -> io::Result<()> {
448 if buf.len() == 0 {
449 return Ok(());
450 }
451
452 if unsafe {
453 memoryapi::ReadProcessMemory(
454 self.0 .0,
455 addr as minwindef::LPVOID,
456 buf.as_mut_ptr() as minwindef::LPVOID,
457 mem::size_of_val(buf) as basetsd::SIZE_T,
458 ptr::null_mut(),
459 )
460 } == 0
461 {
462 Err(io::Error::last_os_error())
463 } else {
464 Ok(())
465 }
466 }
467 }
468}
469
470pub fn copy_address<T>(addr: usize, length: usize, source: &T) -> io::Result<Vec<u8>>
475where
476 T: CopyAddress,
477{
478 log::debug!("copy_address: addr: {:x}", addr);
479
480 let mut copy = vec![0; length];
481
482 source
483 .copy_address(addr, &mut copy)
484 .map_err(|e| {
485 log::warn!("copy_address failed for {:x}: {:?}", addr, e);
486 e
487 })
488 .and(Ok(copy))
489}
490
491#[cfg(test)]
492mod test {
493 use super::*;
494 use std::convert::TryFrom;
495 use std::env;
496 use std::io::{self, BufRead, BufReader};
497 use std::path::PathBuf;
498 use std::process::{Child, Command, Stdio};
499
500 fn test_process_path() -> Option<PathBuf> {
501 env::current_exe().ok().and_then(|p| {
502 p.parent().map(|p| {
503 p.with_file_name("test")
504 .with_extension(env::consts::EXE_EXTENSION)
505 })
506 })
507 }
508
509 fn spawn_with_handle(cmd: &mut Command) -> io::Result<(Child, ProcessHandle)> {
510 let child = cmd.spawn()?;
511 let handle = ProcessHandle::try_from(child.id() as Pid)?;
512 Ok((child, handle))
513 }
514
515 fn read_test_process(args: Option<&[&str]>) -> io::Result<Vec<u8>> {
516 let path = test_process_path().unwrap();
518 let mut cmd = Command::new(&path);
519 {
520 cmd.stdin(Stdio::piped()).stdout(Stdio::piped());
521 }
522 if let Some(a) = args {
523 cmd.args(a);
524 }
525 let (mut child, handle) = spawn_with_handle(&mut cmd)?;
526 let reader = BufReader::new(child.stdout.take().unwrap());
529 let line = reader.lines().next().unwrap().unwrap();
530 let bits = line.split(' ').collect::<Vec<_>>();
531 let addr = usize::from_str_radix(&bits[0][2..], 16).unwrap();
532 let size = bits[1].parse::<usize>().unwrap();
533 let mem = copy_address(addr, size, &handle)?;
534 child.wait()?;
535 Ok(mem)
536 }
537
538 #[test]
539 fn test_read_small() {
540 let mem = read_test_process(None).unwrap();
541 assert_eq!(mem, (0..32u8).collect::<Vec<u8>>());
542 }
543
544 #[test]
545 fn test_read_large() {
546 const SIZE: usize = 5000;
548 let arg = format!("{}", SIZE);
549 let mem = read_test_process(Some(&[&arg])).unwrap();
550 let expected = (0..SIZE)
551 .map(|v| (v % (u8::max_value() as usize + 1)) as u8)
552 .collect::<Vec<u8>>();
553 assert_eq!(mem, expected);
554 }
555}