Skip to content

Commit 1ba4a60

Browse files
committed
Reading memory on windows
1 parent 801bd3b commit 1ba4a60

File tree

2 files changed

+124
-87
lines changed

2 files changed

+124
-87
lines changed

crates/rrg/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,7 @@ features = [
150150
"Win32_System_Memory",
151151
"Win32_System_Threading",
152152
"Win32_Storage_FileSystem",
153+
"Win32_System_Diagnostics_Debug",
153154
]
154155

155156
[dependencies.ed25519-dalek]

crates/rrg/src/action/dump_process_memory.rs

Lines changed: 123 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -53,29 +53,13 @@ impl MappedRegion {
5353
}
5454
}
5555

56-
/// Allows to read the contents of a running process' memory.
57-
pub struct ReadableProcessMemory {
58-
mem_file: File,
59-
}
60-
61-
impl ReadableProcessMemory {
62-
pub fn from_file(mem_file: File) -> Self {
63-
Self { mem_file }
64-
}
65-
56+
// Represents a handle to read another process' memory.
57+
pub trait MemoryReader {
6658
/// Reads a slice `\[offset..(offset+length)\]` of the opened process' memory
6759
/// and returns it as a [`Vec<u8>`]. `offset` is considered as an absolute offset
6860
/// in the process' address space. If the slice falls outside the memory's address space,
6961
/// the returned buffer will be truncated.
70-
pub fn read_chunk(&mut self, offset: u64, length: u64) -> std::io::Result<Vec<u8>> {
71-
self.mem_file.seek(SeekFrom::Start(offset))?;
72-
let mut buf = Vec::new();
73-
let mem_file = self.mem_file.by_ref();
74-
// Limit amount of bytes that can be read
75-
let mut mem_file_limited = mem_file.take(length);
76-
mem_file_limited.read_to_end(&mut buf)?;
77-
Ok(buf)
78-
}
62+
fn read_chunk(&mut self, offset: u64, length: u64) -> std::io::Result<Vec<u8>>;
7963
}
8064

8165
#[cfg(target_os = "linux")]
@@ -210,12 +194,37 @@ mod linux {
210194
}
211195
}
212196

197+
/// Allows to read the contents of a running process' memory.
198+
pub struct ReadableProcessMemory {
199+
mem_file: File,
200+
}
201+
213202
impl ReadableProcessMemory {
214203
/// Opens the contents of process `pid`'s memory for reading.
215204
pub fn open(pid: u32) -> std::io::Result<Self> {
216205
let mem_file = File::open(format!("/proc/{pid}/mem"))?;
217206
Ok(Self::from_file(mem_file))
218207
}
208+
209+
pub fn from_file(mem_file: File) -> Self {
210+
Self { mem_file }
211+
}
212+
}
213+
214+
impl MemoryReader for ReadableProcessMemory {
215+
/// Reads a slice `\[offset..(offset+length)\]` of the opened process' memory
216+
/// and returns it as a [`Vec<u8>`]. `offset` is considered as an absolute offset
217+
/// in the process' address space. If the slice falls outside the memory's address space,
218+
/// the returned buffer will be truncated.
219+
fn read_chunk(&mut self, offset: u64, length: u64) -> std::io::Result<Vec<u8>> {
220+
self.mem_file.seek(SeekFrom::Start(offset))?;
221+
let mut buf = Vec::new();
222+
let mem_file = self.mem_file.by_ref();
223+
// Limit amount of bytes that can be read
224+
let mut mem_file_limited = mem_file.take(length);
225+
mem_file_limited.read_to_end(&mut buf)?;
226+
Ok(buf)
227+
}
219228
}
220229

221230
/// Iterator over a running process' memory mappings.
@@ -258,36 +267,9 @@ mod linux {
258267
}
259268

260269
#[cfg(test)]
261-
mod tests {
262-
270+
mod test {
263271
use super::*;
264272

265-
#[test]
266-
fn iterate_this_process_regions() {
267-
let pid = std::process::id();
268-
let iterator = MappedRegionIter::from_pid(pid)
269-
.expect("Could not read memory regions of current process");
270-
assert!(iterator.collect::<Result<Vec<MappedRegion>, _>>().is_ok());
271-
}
272-
273-
#[test]
274-
fn can_read_this_process_memory() {
275-
let pid = std::process::id();
276-
let regions: Vec<MappedRegion> = MappedRegionIter::from_pid(pid)
277-
.expect("Could not read memory regions of current process")
278-
.collect::<Result<_, _>>()
279-
.expect("Reading maps");
280-
281-
let mut memory =
282-
ReadableProcessMemory::open(pid).expect("Could not open process memory");
283-
assert! {
284-
regions.iter().any(|region| memory.read_chunk(
285-
region.start_address(),
286-
region.size(),
287-
).is_ok())
288-
}
289-
}
290-
291273
#[test]
292274
fn region_iter_detects_mmap() {
293275
// `mmap` a file and check that the mapping is detected among those returned by
@@ -349,6 +331,7 @@ mod windows {
349331
use core::ffi::c_void;
350332
use std::mem::MaybeUninit;
351333
use windows_sys::Win32::Foundation::{CloseHandle, HANDLE};
334+
use windows_sys::Win32::System::Diagnostics::Debug::ReadProcessMemory;
352335
use windows_sys::Win32::System::Memory::{MEMORY_BASIC_INFORMATION, VirtualQueryEx};
353336
use windows_sys::Win32::System::Threading::{OpenProcess, PROCESS_ALL_ACCESS};
354337

@@ -429,18 +412,42 @@ mod windows {
429412
}
430413
}
431414

432-
#[cfg(test)]
433-
mod tests {
434-
use super::*;
415+
/// Allows to read the contents of a running process' memory.
416+
pub struct ReadableProcessMemory {
417+
process: ProcessHandle,
418+
}
435419

436-
#[test]
437-
fn iterate_this_process_regions() {
438-
let pid = std::process::id();
439-
let iterator = MappedRegionIter::from_pid(pid)
440-
.expect("Could not read memory regions of current process");
441-
let result = iterator.collect::<Result<Vec<MappedRegion>, _>>().unwrap();
442-
dbg!(&result);
443-
assert!(!result.is_empty());
420+
impl ReadableProcessMemory {
421+
/// Opens the contents of process `pid`'s memory for reading.
422+
pub fn open(pid: u32) -> std::io::Result<Self> {
423+
Ok(Self {
424+
process: ProcessHandle::open(pid)?,
425+
})
426+
}
427+
}
428+
429+
impl MemoryReader for ReadableProcessMemory {
430+
/// Reads a slice `\[offset..(offset+length)\]` of the opened process' memory
431+
/// and returns it as a [`Vec<u8>`]. `offset` is considered as an absolute offset
432+
/// in the process' address space. If the slice falls outside the memory's address space,
433+
/// the returned buffer will be truncated.
434+
fn read_chunk(&mut self, offset: u64, length: u64) -> std::io::Result<Vec<u8>> {
435+
let mut buf = vec![0; length as usize];
436+
let mut bytes_read = 0;
437+
if unsafe {
438+
ReadProcessMemory(
439+
self.process.0,
440+
std::ptr::without_provenance_mut(offset as usize),
441+
buf.as_mut_ptr().cast(),
442+
buf.len(),
443+
&mut bytes_read,
444+
)
445+
} == 0
446+
{
447+
return Err(std::io::Error::last_os_error());
448+
}
449+
buf.truncate(bytes_read);
450+
Ok(buf)
444451
}
445452
}
446453
}
@@ -683,15 +690,16 @@ pub fn sort_by_priority(
683690
/// Reads the contents of all of process `pid`'s memory mappings
684691
/// and sends them to the session.
685692
/// `regions` and `memory` are passed as arguments for testability.
686-
pub fn dump_regions<S>(
693+
pub fn dump_regions<S, Mem>(
687694
session: &mut S,
688695
regions: impl Iterator<Item = MappedRegion>,
689-
memory: &mut ReadableProcessMemory,
696+
memory: &mut Mem,
690697
pid: u32,
691698
total_size_left: &mut u64,
692699
) -> crate::session::Result<()>
693700
where
694701
S: crate::session::Session,
702+
Mem: MemoryReader,
695703
{
696704
use sha2::Digest as _;
697705

@@ -738,7 +746,7 @@ where
738746
Ok(())
739747
}
740748

741-
#[cfg(not(target_os = "linux"))]
749+
#[cfg(not(any(target_os = "linux", target_os = "windows")))]
742750
pub fn handle<S>(_session: &mut S, _args: Args) -> crate::session::Result<()>
743751
where
744752
S: crate::session::Session,
@@ -749,7 +757,7 @@ where
749757
)))
750758
}
751759

752-
#[cfg(target_os = "linux")]
760+
#[cfg(any(target_os = "linux", target_os = "windows"))]
753761
pub fn handle<S>(session: &mut S, mut args: Args) -> crate::session::Result<()>
754762
where
755763
S: crate::session::Session,
@@ -813,7 +821,46 @@ where
813821
#[cfg(test)]
814822
mod test {
815823
use super::*;
816-
use std::io::Write;
824+
825+
struct FakeProcessMemory {
826+
contents: Vec<u8>,
827+
}
828+
829+
impl MemoryReader for FakeProcessMemory {
830+
fn read_chunk(&mut self, offset: u64, length: u64) -> std::io::Result<Vec<u8>> {
831+
let start = offset as usize;
832+
let end = (offset + length) as usize;
833+
Ok(self.contents[start..end].to_owned())
834+
}
835+
}
836+
837+
#[cfg(any(target_os = "linux", target_os = "windows"))]
838+
#[test]
839+
fn iterate_this_process_regions() {
840+
let pid = std::process::id();
841+
let iterator = MappedRegionIter::from_pid(pid)
842+
.expect("Could not read memory regions of current process");
843+
let result = iterator.collect::<Result<Vec<MappedRegion>, _>>().unwrap();
844+
assert!(!result.is_empty());
845+
}
846+
847+
#[cfg(any(target_os = "linux", target_os = "windows"))]
848+
#[test]
849+
fn can_read_this_process_memory() {
850+
let pid = std::process::id();
851+
let regions: Vec<MappedRegion> = MappedRegionIter::from_pid(pid)
852+
.expect("Could not read memory regions of current process")
853+
.collect::<Result<_, _>>()
854+
.expect("Reading maps");
855+
856+
let mut memory = ReadableProcessMemory::open(pid).expect("Could not open process memory");
857+
assert! {
858+
regions.iter().any(|region| memory.read_chunk(
859+
region.start_address(),
860+
region.size(),
861+
).is_ok())
862+
}
863+
}
817864

818865
#[test]
819866
fn offset_priority() {
@@ -886,25 +933,19 @@ mod test {
886933

887934
#[test]
888935
fn dumps_regions() {
889-
let tempdir = tempfile::tempdir().unwrap();
890-
891936
let regions = vec![
892937
MappedRegion::from_bounds(0, 1000),
893938
MappedRegion::from_bounds(1000, 3000),
894939
MappedRegion::from_bounds(3000, 3500),
895940
];
896941

897942
// Write fake memory contents and check that they're read correctly
898-
let file_path = tempdir.path().join("memfile.txt");
899-
let mut file = File::create_new(file_path).expect("could not create memfile");
900-
file.write_all(vec![1; 1000].as_slice())
901-
.expect("could not write to memfile");
902-
file.write_all(vec![2; 2000].as_slice())
903-
.expect("could not write to memfile");
904-
file.write_all(vec![3; 500].as_slice())
905-
.expect("could not write to memfile");
943+
let mut fake_mem = Vec::new();
944+
fake_mem.extend(std::iter::repeat_n(1, 1000));
945+
fake_mem.extend(std::iter::repeat_n(2, 2000));
946+
fake_mem.extend(std::iter::repeat_n(3, 500));
906947

907-
let mut memory = ReadableProcessMemory::from_file(file);
948+
let mut memory = FakeProcessMemory { contents: fake_mem };
908949

909950
let mut session = crate::session::FakeSession::new();
910951
let mut limit = u64::MAX;
@@ -928,24 +969,19 @@ mod test {
928969

929970
#[test]
930971
fn size_limit() {
931-
let tempdir = tempfile::tempdir().unwrap();
932-
933972
let regions = vec![
934973
MappedRegion::from_bounds(0, 1000),
935974
MappedRegion::from_bounds(1000, 3000),
936975
MappedRegion::from_bounds(3000, 3500),
937976
];
938977

939-
let file_path = tempdir.path().join("memfile.txt");
940-
let mut file = File::create_new(file_path).expect("could not create memfile");
941-
file.write_all(vec![1; 1000].as_slice())
942-
.expect("could not write to memfile");
943-
file.write_all(vec![2; 2000].as_slice())
944-
.expect("could not write to memfile");
945-
file.write_all(vec![3; 500].as_slice())
946-
.expect("could not write to memfile");
978+
// Write fake memory contents and check that they're read correctly
979+
let mut fake_mem = Vec::new();
980+
fake_mem.extend(std::iter::repeat_n(1, 1000));
981+
fake_mem.extend(std::iter::repeat_n(2, 2000));
982+
fake_mem.extend(std::iter::repeat_n(3, 500));
947983

948-
let mut memory = ReadableProcessMemory::from_file(file);
984+
let mut memory = FakeProcessMemory { contents: fake_mem };
949985

950986
let mut session = crate::session::FakeSession::new();
951987
// Should dump first region fully, second partially
@@ -976,7 +1012,7 @@ mod test {
9761012
assert_eq!(second_region.region.end_address(), 3000);
9771013
}
9781014

979-
#[cfg(target_os = "linux")]
1015+
#[cfg(any(target_os = "linux", target_os = "windows"))]
9801016
#[test]
9811017
fn handle_dumps_current_process_regions() {
9821018
let mut session = crate::session::FakeSession::new();
@@ -995,7 +1031,7 @@ mod test {
9951031
assert!(session.replies::<Item>().any(Result::is_ok));
9961032
}
9971033

998-
#[cfg(target_os = "linux")]
1034+
#[cfg(any(target_os = "linux", target_os = "windows"))]
9991035
#[test]
10001036
fn filters_regions() {
10011037
let mut session = crate::session::FakeSession::new();

0 commit comments

Comments
 (0)