akhramov_read_process_memory/
lib.rs1#[macro_use]
27extern crate log;
28extern crate libc;
29
30use std::io;
31
32pub trait CopyAddress {
34 fn copy_address(&self, addr: usize, buf: &mut [u8]) -> io::Result<()>;
36}
37
38pub use platform::Pid;
40pub 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 pub type Pid = pid_t;
74 #[derive(Copy, Clone)]
76 pub struct ProcessHandle(Pid);
77
78 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 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 pub type Pid = pid_t;
139 #[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 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 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 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 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 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 pub type Pid = pid_t;
241 #[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 #[derive(PartialEq)]
259 enum PtraceLockState {
260 Release,
261 NoRelease,
262 }
263
264 extern "C" {
265 fn ptrace(request: c_int,
268 pid: pid_t,
269 io_desc: *const PtraceIoDesc,
270 data: c_int) -> c_int;
271 }
272
273 const PIOD_READ: c_int = 1;
276
277 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 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 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 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 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 pub type Pid = winapi::DWORD;
392 #[derive(Copy, Clone)]
394 pub struct ProcessHandle(RawHandle);
395
396 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 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 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
443pub 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 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 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 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}