forked from headcrab-rs/headcrab
-
Notifications
You must be signed in to change notification settings - Fork 0
/
linux.rs
197 lines (167 loc) · 5.72 KB
/
linux.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
use nix::unistd::{getpid, Pid};
use std::{
fs::File,
io::{BufRead, BufReader},
marker::PhantomData,
mem,
};
use crate::target::unix::{self, UnixTarget};
/// This structure holds the state of a debuggee on Linux based systems
/// You can use it to read & write debuggee's memory, pause it, set breakpoints, etc.
pub struct LinuxTarget {
pid: Pid,
}
impl UnixTarget for LinuxTarget {
/// Provides the Pid of the debugee process
fn pid(&self) -> Pid {
self.pid
}
}
impl LinuxTarget {
/// Launches a new debuggee process
pub fn launch(path: &str) -> Result<LinuxTarget, Box<dyn std::error::Error>> {
let pid = unix::launch(path)?;
Ok(LinuxTarget { pid })
}
/// Attaches process as a debugee.
pub fn attach(pid: Pid) -> Result<LinuxTarget, Box<dyn std::error::Error>> {
unix::attach(pid)?;
Ok(LinuxTarget { pid })
}
/// Uses this process as a debuggee.
pub fn me() -> LinuxTarget {
LinuxTarget { pid: getpid() }
}
/// Reads memory from a debuggee process.
pub fn read(&self) -> ReadMemory {
ReadMemory::new(self.pid())
}
/// Reads the register values from the main thread of a debuggee process.
pub fn read_regs(&self) -> Result<libc::user_regs_struct, Box<dyn std::error::Error>> {
nix::sys::ptrace::getregs(self.pid()).map_err(|err| err.into())
}
}
/// A single memory read operation.
struct ReadOp {
// Remote memory location.
remote_base: usize,
// Size of the `local_ptr` buffer.
len: usize,
// Pointer to a local destination buffer.
local_ptr: *mut libc::c_void,
}
impl ReadOp {
/// Converts the memory read operation into a remote IoVec.
fn as_remote_iovec(&self) -> libc::iovec {
libc::iovec {
iov_base: self.remote_base as *const libc::c_void as *mut _,
iov_len: self.len,
}
}
/// Converts the memory read operation into a local IoVec.
fn as_local_iovec(&self) -> libc::iovec {
libc::iovec {
iov_base: self.local_ptr,
iov_len: self.len,
}
}
}
/// Allows to read memory from different locations in debuggee's memory as a single operation.
/// On Linux, this will correspond to a single system call / context switch.
pub struct ReadMemory<'a> {
pid: Pid,
read_ops: Vec<ReadOp>,
_marker: PhantomData<&'a mut ()>,
}
impl<'a> ReadMemory<'a> {
fn new(pid: Pid) -> Self {
ReadMemory {
pid,
read_ops: Vec::new(),
_marker: PhantomData,
}
}
/// Reads a value of type `T` from debuggee's memory at location `remote_base`.
/// This value will be written to the provided variable `val`.
/// You should call `apply` in order to execute the memory read operation.
/// The provided variable `val` can't be accessed until either `apply` is called or `self` is
/// dropped.
///
/// # Safety
///
/// The type `T` must not have any invalid values.
/// For example `T` must not be a `bool`, as `transmute::<u8, bool>(2)` is not a valid value for a bool.
/// In case of doubt, wrap the type in [`mem::MaybeUninit`].
// todo: further document mem safety - e.g., what happens in the case of partial read
pub unsafe fn read<T>(mut self, val: &'a mut T, remote_base: usize) -> Self {
self.read_ops.push(ReadOp {
remote_base,
len: mem::size_of::<T>(),
local_ptr: val as *mut T as *mut libc::c_void,
});
self
}
/// Executes the memory read operation.
pub fn apply(self) -> Result<(), Box<dyn std::error::Error>> {
// Create a list of `IoVec`s and remote `IoVec`s
let remote_iov = self
.read_ops
.iter()
.map(ReadOp::as_remote_iovec)
.collect::<Vec<_>>();
let local_iov = self
.read_ops
.iter()
.map(ReadOp::as_local_iovec)
.collect::<Vec<_>>();
let bytes_read = unsafe {
// todo: document unsafety
libc::process_vm_readv(
self.pid.into(),
local_iov.as_ptr(),
local_iov.len() as libc::c_ulong,
remote_iov.as_ptr(),
remote_iov.len() as libc::c_ulong,
0,
)
};
if bytes_read == -1 {
// fixme: return a proper error type
return Err(Box::new(nix::Error::last()));
}
// fixme: check that it's an expected number of read bytes and account for partial reads
Ok(())
}
}
/// Returns the start of a process's virtual memory address range.
/// This can be useful for calculation of relative addresses in memory.
pub fn get_addr_range(pid: Pid) -> Result<usize, Box<dyn std::error::Error>> {
let file = File::open(format!("/proc/{}/maps", pid))?;
let mut bufread = BufReader::new(file);
let mut proc_map = String::new();
bufread.read_line(&mut proc_map)?;
let proc_data: Vec<_> = proc_map.split(' ').collect();
let addr_range: Vec<_> = proc_data[0].split('-').collect();
Ok(usize::from_str_radix(addr_range[0], 16)?)
}
#[cfg(test)]
mod tests {
use super::ReadMemory;
use nix::unistd::getpid;
#[test]
fn read_memory() {
let var: usize = 52;
let var2: u8 = 128;
let mut read_var_op: usize = 0;
let mut read_var2_op: u8 = 0;
unsafe {
ReadMemory::new(getpid())
.read(&mut read_var_op, &var as *const _ as usize)
.read(&mut read_var2_op, &var2 as *const _ as usize)
.apply()
.expect("Failed to apply memop");
}
assert_eq!(read_var2_op, var2);
assert_eq!(read_var_op, var);
}
}