Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support reading from 32-bit processes on 64-bit hosts and vice versa #1

Merged
merged 2 commits into from
Apr 26, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 95 additions & 0 deletions src/architecture.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
use std::convert::TryInto;

/// Enum representing the architecture of a process
#[derive(Clone, Debug, Copy)]
#[repr(u8)]
pub enum Architecture {
/// 8-bit architecture
#[cfg(any(
target_pointer_width = "8",
target_pointer_width = "16",
target_pointer_width = "32",
target_pointer_width = "64",
target_pointer_width = "128"
))]
Arch8Bit = 1,
/// 16-bit architecture
#[cfg(any(
target_pointer_width = "16",
target_pointer_width = "32",
target_pointer_width = "64",
target_pointer_width = "128"
))]
Arch16Bit = 2,
/// 32-bit architecture
#[cfg(any(
target_pointer_width = "32",
target_pointer_width = "64",
target_pointer_width = "128"
))]
Arch32Bit = 4,
/// 64-bit architecture
#[cfg(any(target_pointer_width = "64", target_pointer_width = "128"))]
Arch64Bit = 8,
/// 128-bit architecture
#[cfg(target_pointer_width = "128")]
Arch128Bit = 16,
}

impl Architecture {
/// Create an Architecture matching that of the host process.
pub fn from_native() -> Architecture {
#[cfg(target_pointer_width = "8")]
return Architecture::Arch8Bit;
#[cfg(target_pointer_width = "16")]
return Architecture::Arch16Bit;
#[cfg(target_pointer_width = "32")]
return Architecture::Arch32Bit;
#[cfg(target_pointer_width = "64")]
return Architecture::Arch64Bit;
#[cfg(target_pointer_width = "128")]
return Architecture::Arch128Bit;
}

/// Convert bytes read from memory into a pointer in the
/// current architecture.
pub fn pointer_from_ne_bytes(self, bytes: &[u8]) -> usize {
match self {
#[cfg(any(
target_pointer_width = "8",
target_pointer_width = "16",
target_pointer_width = "32",
target_pointer_width = "64",
target_pointer_width = "128"
))]
Architecture::Arch8Bit => {
u8::from_ne_bytes(bytes.try_into().unwrap()) as usize
}
#[cfg(any(
target_pointer_width = "16",
target_pointer_width = "32",
target_pointer_width = "64",
target_pointer_width = "128"
))]
Architecture::Arch16Bit => {
u16::from_ne_bytes(bytes.try_into().unwrap()) as usize
}
#[cfg(any(
target_pointer_width = "32",
target_pointer_width = "64",
target_pointer_width = "128"
))]
Architecture::Arch32Bit => {
u32::from_ne_bytes(bytes.try_into().unwrap()) as usize
}
#[cfg(any(target_pointer_width = "64", target_pointer_width = "128"))]
Architecture::Arch64Bit => {
u64::from_ne_bytes(bytes.try_into().unwrap()) as usize
}
#[cfg(target_pointer_width = "128")]
Architecture::Arch128Bit => {
u128::from_ne_bytes(bytes.try_into().unwrap()) as usize
}
}
}
}
40 changes: 36 additions & 4 deletions src/data_member.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::{CopyAddress, Memory, ProcessHandle, PutAddress};
use crate::{Architecture, CopyAddress, Memory, ProcessHandle, PutAddress};

/// # Tools for working with memory of other programs
/// This module provides functions for modifying the memory of a program from outside of the
Expand Down Expand Up @@ -27,10 +27,30 @@ use crate::{CopyAddress, Memory, ProcessHandle, PutAddress};
/// println!("New x-value: {}", x);
/// assert_eq!(x, 6u32);
/// ```
/// ```no_run
/// # use process_memory::{Memory, DataMember, Pid, TryIntoProcessHandle, Architecture};
/// # fn get_pid(process_name: &str) -> Pid {
/// # std::process::id() as Pid
/// # }
/// // We get a handle for a target process with a different architecture to ourselves
/// let handle = get_pid("32Bit.exe").try_into_process_handle().unwrap();
/// // We make a `DataMember` that has a series of offsets refering to a known value in
/// // the target processes memory
/// let member = DataMember::new_offset(handle, vec![0x01_02_03_04, 0x04, 0x08, 0x10])
/// // We tell the `DataMember` that the process is a 32 bit process, and thus it should
/// // use 32 bit pointers while traversing offsets
/// .set_arch(Architecture::Arch32Bit);
/// // The memory offset can now be correctly calculated:
/// println!("Target memory location: {}", member.get_offset().unwrap());
/// // The memory offset can now be used to retrieve and modify values:
/// println!("Current value: {}", member.read().unwrap());
/// member.write(&123_u32).unwrap();
/// ```
#[derive(Clone, Debug)]
pub struct DataMember<T> {
offsets: Vec<usize>,
process: ProcessHandle,
arch: Architecture,
_phantom: std::marker::PhantomData<*mut T>,
}

Expand All @@ -53,6 +73,7 @@ impl<T: Sized + Copy> DataMember<T> {
Self {
offsets: Vec::new(),
process: handle,
arch: Architecture::from_native(),
_phantom: std::marker::PhantomData,
}
}
Expand All @@ -69,9 +90,20 @@ impl<T: Sized + Copy> DataMember<T> {
Self {
offsets,
process: handle,
arch: Architecture::from_native(),
_phantom: std::marker::PhantomData,
}
}

/// Sets the architecture of the DataMember.
///
/// This can be used for reading memory offsets of programs that are of
/// different architectures to the host program.
/// This defaults to the architecture of the host program.
pub fn set_arch(mut self, arch: Architecture) -> Self {
self.arch = arch;
self
}
}

impl<T: Sized + Copy> Memory<T> for DataMember<T> {
Expand All @@ -80,11 +112,11 @@ impl<T: Sized + Copy> Memory<T> for DataMember<T> {
}

fn get_offset(&self) -> std::io::Result<usize> {
self.process.get_offset(&self.offsets)
self.process.get_offset(&self.offsets, self.arch)
}

fn read(&self) -> std::io::Result<T> {
let offset = self.process.get_offset(&self.offsets)?;
let offset = self.process.get_offset(&self.offsets, self.arch)?;
// This can't be [0_u8;size_of::<T>()] because no const generics.
// It will be freed at the end of the function because no references are held to it.
let mut buffer = vec![0_u8; std::mem::size_of::<T>()];
Expand All @@ -94,7 +126,7 @@ impl<T: Sized + Copy> Memory<T> for DataMember<T> {

fn write(&self, value: &T) -> std::io::Result<()> {
use std::slice;
let offset = self.process.get_offset(&self.offsets)?;
let offset = self.process.get_offset(&self.offsets, self.arch)?;
let buffer: &[u8] =
unsafe { slice::from_raw_parts(value as *const _ as _, std::mem::size_of::<T>()) };
self.process.put_address(offset, &buffer)
Expand Down
27 changes: 24 additions & 3 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,25 @@
//! println!("New x-value: {}", x);
//! assert_eq!(x, 6_u32);
//! ```
//! ```no_run
//! # use process_memory::{Memory, DataMember, Pid, TryIntoProcessHandle, Architecture};
//! # fn get_pid(process_name: &str) -> Pid {
//! # std::process::id() as Pid
//! # }
//! // We get a handle for a target process with a different architecture to ourselves
//! let handle = get_pid("32Bit.exe").try_into_process_handle().unwrap();
//! // We make a `DataMember` that has a series of offsets refering to a known value in
//! // the target processes memory
//! let member = DataMember::new_offset(handle, vec![0x01_02_03_04, 0x04, 0x08, 0x10])
//! // We tell the `DataMember` that the process is a 32 bit process, and thus it should
//! // use 32 bit pointers while traversing offsets
//! .set_arch(Architecture::Arch32Bit);
//! // The memory offset can now be correctly calculated:
//! println!("Target memory location: {}", member.get_offset().unwrap());
//! // The memory offset can now be used to retrieve and modify values:
//! println!("Current value: {}", member.read().unwrap());
//! member.write(&123_u32).unwrap();
//! ```
#![deny(missing_docs)]
#![deny(unused_results)]
#![deny(unreachable_pub)]
Expand All @@ -53,9 +72,11 @@
#![deny(unused)]
#![deny(clippy::pedantic)]

mod architecture;
mod data_member;
mod local_member;

pub use architecture::Architecture;
pub use data_member::DataMember;
pub use local_member::LocalMember;

Expand All @@ -79,15 +100,15 @@ pub trait CopyAddress {
///
/// If [`copy_address`] is already defined, then we can provide a standard implementation that
/// will work across all operating systems.
fn get_offset(&self, offsets: &[usize]) -> std::io::Result<usize> {
fn get_offset(&self, offsets: &[usize], arch: Architecture) -> std::io::Result<usize> {
// Look ma! No unsafes!
let mut offset: usize = 0;
let noffsets: usize = offsets.len();
let mut copy = vec![0_u8; arch as usize];
for next_offset in offsets.iter().take(noffsets - 1) {
offset += next_offset;
let mut copy: [u8; std::mem::size_of::<usize>()] = [0; std::mem::size_of::<usize>()];
self.copy_address(offset, &mut copy)?;
offset = usize::from_ne_bytes(copy);
offset = arch.pointer_from_ne_bytes(&copy);
}

offset += offsets[noffsets - 1];
Expand Down
2 changes: 1 addition & 1 deletion src/macos.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ impl CopyAddress for ProcessHandle {
*self,
addr as _,
buf.len() as _,
buf.as_ptr() as _,
buf.as_mut_ptr() as _,
&mut read_len,
)
};
Expand Down
5 changes: 2 additions & 3 deletions src/windows.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
use winapi;
use winapi::shared::minwindef;

use std::mem;
use std::os::windows::io::AsRawHandle;
use std::process::Child;
use std::ptr;
Expand Down Expand Up @@ -64,7 +63,7 @@ impl CopyAddress for ProcessHandle {
*self,
addr as minwindef::LPVOID,
buf.as_mut_ptr() as minwindef::LPVOID,
mem::size_of_val(buf) as winapi::shared::basetsd::SIZE_T,
buf.len() as winapi::shared::basetsd::SIZE_T,
ptr::null_mut(),
)
} == winapi::shared::minwindef::FALSE
Expand All @@ -87,7 +86,7 @@ impl PutAddress for ProcessHandle {
*self,
addr as minwindef::LPVOID,
buf.as_ptr() as minwindef::LPCVOID,
mem::size_of_val(buf) as winapi::shared::basetsd::SIZE_T,
buf.len() as winapi::shared::basetsd::SIZE_T,
ptr::null_mut(),
)
} == winapi::shared::minwindef::FALSE
Expand Down