From d8ad2f88c8507b673d44bca9f4cfa8569a6e11f0 Mon Sep 17 00:00:00 2001 From: Kiiya Date: Thu, 21 Jan 2021 22:55:00 +0100 Subject: [PATCH 1/2] Add new DataMember constructors, extend, shift. --- src/architecture.rs | 6 ++ src/data_member.rs | 148 ++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 4 +- 3 files changed, 156 insertions(+), 2 deletions(-) diff --git a/src/architecture.rs b/src/architecture.rs index c6c85a1..b2cf3f3 100644 --- a/src/architecture.rs +++ b/src/architecture.rs @@ -89,4 +89,10 @@ impl Architecture { Architecture::Arch128Bit => u128::from_ne_bytes(bytes.try_into().unwrap()) as usize, } } + + /// Returns the amount of bytes which a pointer takes up on this architecture. + /// E.g. 4 for 32bit, 8 for 64bit, etc. + pub fn pointer_width_bytes(self) -> usize { + self as u8 as usize + } } diff --git a/src/data_member.rs b/src/data_member.rs index 11ae376..5084d8f 100644 --- a/src/data_member.rs +++ b/src/data_member.rs @@ -72,6 +72,100 @@ impl DataMember { _phantom: std::marker::PhantomData, } } + + /// Create a new `DataMember` from a [`ProcessHandle`] and some number of (potentially negative) offsets. + /// You must remember to call [`try_into_process_handle`] on a [`Pid`] as sometimes the `Pid` can have + /// the same backing type as a [`ProcessHandle`], resulting in an error. + /// + /// [`try_into_process_handle`]: trait.TryIntoProcessHandle.html#tymethod.try_into_process_handle + /// [`ProcessHandle`]: type.ProcessHandle.html + /// [`Pid`]: type.Pid.htm + #[must_use] + pub fn new_offset_relative(handle: ProcessHandle, offsets: Vec) -> Self { + Self { + // Yes, we are casting to usize. This will not touch any bits, but due to 2s complement, + // we still get the correct result when adding offsets. + offsets: offsets.into_iter().map(|x| x as usize).collect(), + process: handle, + _phantom: std::marker::PhantomData, + } + } + + /// Create a new `DataMember` from a [`ProcessHandle`], pointing to the memory address in the + /// remote process. Equivalent to `new_offset(handle, vec![addr])`. You must + /// remember to call [`try_into_process_handle`] on a [`Pid`] as sometimes the `Pid` can have + /// the same backing type as a [`ProcessHandle`], resulting in an error. + /// + /// [`try_into_process_handle`]: trait.TryIntoProcessHandle.html#tymethod.try_into_process_handle + /// [`ProcessHandle`]: type.ProcessHandle.html + /// [`Pid`]: type.Pid.html + #[must_use] + pub fn new_addr(handle: ProcessHandle, addr: usize) -> Self { + Self { + offsets: vec![addr], + process: handle, + _phantom: std::marker::PhantomData, + } + } + + /// Create a new `DataMember` from a [`ProcessHandle`], pointing to the memory address in the + /// process, then following a bunch of pointers and offsets, which may be negative. + /// If you use CheatEngine and get a pointer of form "MyModule.dll + 0x12345678", plus a bunch + /// of offsets, then you want to put the base address of the module as `addr`, `0x12345678` as + /// the first offset, then any further offsets etc. + /// This function is merely a convenience function, and is equivalent to + /// `new_offset_relative(handle, vec![addr as isize, offsets[0], offsets[1], ...])`. + /// You must + /// remember to call [`try_into_process_handle`] on a [`Pid`] as sometimes the `Pid` can have + /// the same backing type as a [`ProcessHandle`], resulting in an error. + /// + /// [`try_into_process_handle`]: trait.TryIntoProcessHandle.html#tymethod.try_into_process_handle + /// [`ProcessHandle`]: type.ProcessHandle.html + /// [`Pid`]: type.Pid.html + #[must_use] + pub fn new_addr_offset(handle: ProcessHandle, addr: usize, offsets: Vec) -> Self { + let mut vec = vec![addr]; + // Yes, we are casting to usize. This will not touch any bits, and due to 2s complement, + // we still get the correct result when adding offsets. + vec.extend(offsets.into_iter().map(|x| x as usize)); + Self { + offsets: vec, + process: handle, + _phantom: std::marker::PhantomData, + } + } + + /// Creates a new `DataMember` by appending some more offets. Useful when you have a data + /// structure, and want to refer to multiple fields in it, or use it as a starting point + /// for chasing down more pointers. + /// Since the pointed-to data type might have changed, this function is generic. It is your + /// responsibility to make sure you know what you point to. + #[must_use] + pub fn extend(&self, more_offsets: Vec) -> DataMember { + let mut clone = DataMember { + offsets: self.offsets.clone(), + process: self.process, + _phantom: std::marker::PhantomData, + }; + // Yes, we are casting to usize. This will not touch any bits, and due to 2s complement, + // we still get the correct result when adding offsets. + clone.offsets.extend(more_offsets.into_iter().map(|x| x as usize)); + clone + } + + /// Creates a new `DataMember`, based on self, by shifting the last offset by a number of + /// bytes. Does not append new offsets. This is useful if you have a pointer to a struct + /// and want to address different fields, or access elements in an array. + pub fn shift(&self, n_bytes: isize) -> DataMember { + let mut clone = DataMember { + offsets: self.offsets.clone(), + process: self.process, + _phantom: std::marker::PhantomData, + }; + let new = clone.offsets[self.offsets.len() - 1].wrapping_add(n_bytes as usize); + clone.offsets[self.offsets.len() - 1] = new; + clone + } } impl Memory for DataMember { @@ -147,4 +241,58 @@ mod test { member.write(&0xffff).unwrap(); assert_eq!(test, 0xffff); } + + #[repr(C)] + #[derive(Clone, Copy)] + struct Player { + x: u32, + y: u32, + } + + #[repr(C)] + struct GameState { + garbage: u32, + garbage2: u32, + players: [Box; 2], // note that this array is in-place, since it's fixed size. + } + + #[test] + fn multilevel_pointers() { + let game = GameState { + garbage: 42, + garbage2: 1337, + players: [ + Box::new(Player { x: 1, y: 2 }), + Box::new(Player { x: 3, y: 4 }), + ], + }; + let handle = (std::process::id() as crate::Pid) + .try_into_process_handle() + .unwrap(); + + // point to `game`, then our data is +4 from the base of `game`. + let garbage2 = + DataMember::::new_addr(handle, &game as *const _ as usize + 4); + assert_eq!(1337, garbage2.read().unwrap()); + + let garbage1 = garbage2.shift(-4); + assert_eq!(42u32, garbage1.read().unwrap()); + + // At `game + 2*sizeof(u32) + 1*sizeof(Player*) is where we find + // a pointer to the second player. + // So second_player.read() right now would just get you the pointer to the player. + let second_player = DataMember::<*mut Player>::new_addr( + handle, + (&game as *const _ as usize) + 8 + handle.get_pointer_width().pointer_width_bytes(), + ); + + // But when we add an offset, in this case an offset of 0, we follow the pointer, + // and thus second_player_x points to the beginning of the second player, which in this + // case is also the x coordinate. + let second_player_x = second_player.extend::(vec![0]); + let second_player_y = second_player.extend::(vec![4]); // sizeof u32 = 4 + + assert_eq!(3, second_player_x.read().unwrap()); + assert_eq!(4, second_player_y.read().unwrap()); + } } diff --git a/src/lib.rs b/src/lib.rs index 27024cb..fdce4b3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -115,12 +115,12 @@ pub trait CopyAddress { let noffsets: usize = offsets.len(); let mut copy = vec![0_u8; self.get_pointer_width() as usize]; for next_offset in offsets.iter().take(noffsets - 1) { - offset += next_offset; + offset = offset.wrapping_add(*next_offset); self.copy_address(offset, &mut copy)?; offset = self.get_pointer_width().pointer_from_ne_bytes(©); } - offset += offsets[noffsets - 1]; + offset = offset.wrapping_add(offsets[noffsets - 1]); Ok(offset) } From b92fcefc960af4a8e5e0cd6825e006017aadeafd Mon Sep 17 00:00:00 2001 From: Kiiya Date: Thu, 21 Jan 2021 23:09:11 +0100 Subject: [PATCH 2/2] Clippy, I love you so much... --- src/architecture.rs | 1 + src/data_member.rs | 15 +++++++++++---- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/architecture.rs b/src/architecture.rs index b2cf3f3..4273a00 100644 --- a/src/architecture.rs +++ b/src/architecture.rs @@ -92,6 +92,7 @@ impl Architecture { /// Returns the amount of bytes which a pointer takes up on this architecture. /// E.g. 4 for 32bit, 8 for 64bit, etc. + #[must_use] pub fn pointer_width_bytes(self) -> usize { self as u8 as usize } diff --git a/src/data_member.rs b/src/data_member.rs index 5084d8f..802cdbd 100644 --- a/src/data_member.rs +++ b/src/data_member.rs @@ -81,6 +81,7 @@ impl DataMember { /// [`ProcessHandle`]: type.ProcessHandle.html /// [`Pid`]: type.Pid.htm #[must_use] + #[allow(clippy::cast_sign_loss)] pub fn new_offset_relative(handle: ProcessHandle, offsets: Vec) -> Self { Self { // Yes, we are casting to usize. This will not touch any bits, but due to 2s complement, @@ -123,6 +124,8 @@ impl DataMember { /// [`ProcessHandle`]: type.ProcessHandle.html /// [`Pid`]: type.Pid.html #[must_use] + #[allow(clippy::doc_markdown)] + #[allow(clippy::cast_sign_loss)] pub fn new_addr_offset(handle: ProcessHandle, addr: usize, offsets: Vec) -> Self { let mut vec = vec![addr]; // Yes, we are casting to usize. This will not touch any bits, and due to 2s complement, @@ -140,6 +143,7 @@ impl DataMember { /// for chasing down more pointers. /// Since the pointed-to data type might have changed, this function is generic. It is your /// responsibility to make sure you know what you point to. + #[allow(clippy::cast_sign_loss)] #[must_use] pub fn extend(&self, more_offsets: Vec) -> DataMember { let mut clone = DataMember { @@ -149,13 +153,17 @@ impl DataMember { }; // Yes, we are casting to usize. This will not touch any bits, and due to 2s complement, // we still get the correct result when adding offsets. - clone.offsets.extend(more_offsets.into_iter().map(|x| x as usize)); + clone + .offsets + .extend(more_offsets.into_iter().map(|x| x as usize)); clone } /// Creates a new `DataMember`, based on self, by shifting the last offset by a number of /// bytes. Does not append new offsets. This is useful if you have a pointer to a struct /// and want to address different fields, or access elements in an array. + #[allow(clippy::cast_sign_loss)] + #[must_use] pub fn shift(&self, n_bytes: isize) -> DataMember { let mut clone = DataMember { offsets: self.offsets.clone(), @@ -271,14 +279,13 @@ mod test { .unwrap(); // point to `game`, then our data is +4 from the base of `game`. - let garbage2 = - DataMember::::new_addr(handle, &game as *const _ as usize + 4); + let garbage2 = DataMember::::new_addr(handle, &game as *const _ as usize + 4); assert_eq!(1337, garbage2.read().unwrap()); let garbage1 = garbage2.shift(-4); assert_eq!(42u32, garbage1.read().unwrap()); - // At `game + 2*sizeof(u32) + 1*sizeof(Player*) is where we find + // At `game + 2*sizeof(u32) + 1*sizeof(Player*) is where we find // a pointer to the second player. // So second_player.read() right now would just get you the pointer to the player. let second_player = DataMember::<*mut Player>::new_addr(