1mod arch;
2use arch::UserRegs;
3
4use libc::{pid_t, ptrace, PTRACE_ATTACH, PTRACE_CONT, PTRACE_DETACH};
5use std::{io, mem};
6use thiserror::Error;
7
8fn usize_arr_to_u8(data: &[usize]) -> Vec<u8> {
10 let mut arr: Vec<u8> = Vec::new();
11 for p in data {
12 if cfg!(target_endian = "big") {
13 arr.extend_from_slice(&p.to_be_bytes());
14 } else {
15 arr.extend_from_slice(&p.to_le_bytes());
16 }
17 }
18 arr
19}
20
21#[derive(Debug, Error)]
23pub enum TraceError {
24 #[error("Ptrace error: `{0}`")]
26 Ptrace(io::Error),
27
28 #[error("IO Error: `{0}`")]
30 Io(#[from] io::Error),
31
32 #[error("General `{0}`")]
34 General(&'static str),
35}
36
37pub type TraceResult<T> = Result<T, TraceError>;
39
40#[allow(unused)]
42enum WaitOptions {
43 None,
44 NoHang,
45 Untraced,
46 Continued,
47}
48
49impl From<WaitOptions> for i32 {
51 fn from(val: WaitOptions) -> Self {
52 match val {
53 WaitOptions::None => 0,
54 WaitOptions::NoHang => libc::WNOHANG,
55 WaitOptions::Untraced => libc::WUNTRACED,
56 WaitOptions::Continued => libc::WCONTINUED,
57 }
58 }
59}
60
61struct WaitStatus(i32);
63
64impl WaitStatus {
65 fn is_stop(&self) -> bool {
67 libc::WIFSTOPPED(self.0)
68 }
69
70 fn is_signaled(&self) -> bool {
72 libc::WIFSIGNALED(self.0)
73 }
74
75 fn is_continued(&self) -> bool {
77 libc::WIFCONTINUED(self.0)
78 }
79
80 fn is_exited(&self) -> bool {
82 libc::WIFEXITED(self.0)
83 }
84
85 fn stop_code(&self) -> i32 {
87 libc::WSTOPSIG(self.0)
88 }
89}
90
91impl std::fmt::Debug for WaitStatus {
92 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
93 f.debug_struct("WaitStatus")
94 .field("is_stopped", &self.is_stop())
95 .field("is_signaled", &self.is_signaled())
96 .field("is_continued", &self.is_continued())
97 .field("is_exited", &self.is_exited())
98 .field("stop_code", &self.stop_code())
99 .finish()
100 }
101}
102
103pub struct ProcessFrame {
107 process: TracedProcess,
108}
109
110impl ProcessFrame {
111 #[cfg(target_arch = "aarch64")]
114 pub fn query_registers(&mut self) -> TraceResult<UserRegs> {
115 let mut registers: UserRegs = unsafe { mem::zeroed() };
116 let mut iovec = libc::iovec {
117 iov_base: &mut registers as *mut _ as *mut core::ffi::c_void,
118 iov_len: std::mem::size_of::<UserRegs>() as libc::size_t,
119 };
120
121 let result = unsafe { ptrace(libc::PTRACE_GETREGSET, self.process.pid(), 1, &mut iovec) };
122
123 match result == -1 {
124 true => Err(TraceError::Ptrace(std::io::Error::last_os_error())),
125 false => Ok(registers),
126 }
127 }
128
129 #[cfg(target_arch = "aarch64")]
132 pub fn set_registers(&mut self, mut registers: UserRegs) -> TraceResult<()> {
133 let mut iovec = libc::iovec {
134 iov_base: &mut registers as *mut _ as *mut core::ffi::c_void,
135 iov_len: std::mem::size_of::<UserRegs>() as libc::size_t,
136 };
137
138 let result = unsafe { ptrace(libc::PTRACE_SETREGSET, self.process.pid(), 1, &mut iovec) };
139
140 match result == -1 {
141 true => Err(TraceError::Ptrace(std::io::Error::last_os_error())),
142 false => Ok(()),
143 }
144 }
145
146 #[cfg(target_arch = "aarch64")]
150 pub fn invoke_remote(
151 mut self,
152 func_address: usize,
153 return_address: usize,
154 parameters: &[usize],
155 ) -> TraceResult<(UserRegs, ProcessFrame)> {
156 use std::mem::size_of;
157
158 const REGISTER_ARGUMENTS: usize = 8;
159 let mut current_registers = self.query_registers()?;
160 tracing::trace!(
161 "Initial registers acquired Current PC: {:X?}",
162 current_registers.program_counter()
163 );
164
165 let cached_registers = current_registers.clone();
166 current_registers.set_stack_pointer(current_registers.stack_pointer() & !0xfusize);
167 for (i, param) in parameters[..std::cmp::min(parameters.len(), REGISTER_ARGUMENTS)]
168 .iter()
169 .enumerate()
170 {
171 let reg: usize = *param;
172 current_registers.regs[i] = reg as u64;
173 tracing::trace!("Applying register {i} with param {}", reg);
174 }
175
176 if parameters.len() > REGISTER_ARGUMENTS {
177 let stack_arguments = ¶meters[REGISTER_ARGUMENTS..];
178 tracing::trace!("Remaining stack arguments: {:?}", stack_arguments);
179
180 current_registers.set_stack_pointer(
182 current_registers.stack_pointer()
183 - (((stack_arguments.len() + 1) & !1usize) * size_of::<usize>()),
184 );
185
186 self.write_memory(
187 current_registers.stack_pointer(),
188 usize_arr_to_u8(stack_arguments).as_slice(),
189 )?;
190 };
191
192 current_registers.set_program_counter(func_address);
194 if (current_registers.program_counter() & 1) != 0 {
195 current_registers.set_program_counter(current_registers.program_counter() & !1);
196 current_registers
197 .set_cpsr((current_registers.cpsr() as u32 | (arch::CPSR_T_MASK)) as usize);
198 } else {
199 current_registers
200 .set_cpsr((current_registers.cpsr() as u32 & !(arch::CPSR_T_MASK)) as usize);
201 }
202 current_registers.set_lr(return_address);
203 tracing::trace!(
204 "Executing with PC: {:X?}, and arguments {parameters:?}",
205 func_address
206 );
207
208 self.set_registers(current_registers)?;
209 tracing::trace!("Registers successfully injected.");
210
211 let mut frame = self.step_cont()?;
212 let result_regs = frame.query_registers()?;
213 tracing::trace!("Result {result_regs:#?}");
214
215 frame.set_registers(cached_registers)?;
216 Ok((result_regs, frame))
217 }
218
219 #[cfg(any(target_arch = "x86", target_arch = "x86_64", target_arch = "arm"))]
222 pub fn query_registers(&mut self) -> TraceResult<UserRegs> {
223 let mut registers: UserRegs = unsafe { mem::zeroed() };
224 let result = unsafe { ptrace(libc::PTRACE_GETREGS, self.process.pid(), 0, &mut registers) };
225
226 match result == -1 {
227 true => Err(TraceError::Ptrace(std::io::Error::last_os_error())),
228 false => Ok(registers),
229 }
230 }
231
232 #[cfg(any(target_arch = "x86", target_arch = "x86_64", target_arch = "arm"))]
235 pub fn set_registers(&mut self, registers: UserRegs) -> TraceResult<()> {
236 let result = unsafe { ptrace(libc::PTRACE_SETREGS, self.process.pid(), 0, ®isters) };
237
238 match result == -1 {
239 true => Err(TraceError::Ptrace(std::io::Error::last_os_error())),
240 false => Ok(()),
241 }
242 }
243
244 #[cfg(target_arch = "arm")]
248 pub fn invoke_remote(
249 mut self,
250 func_address: usize,
251 return_address: usize,
252 parameters: &[usize],
253 ) -> TraceResult<(UserRegs, ProcessFrame)> {
254 use std::mem::size_of;
255
256 const REGISTER_ARGUMENTS: usize = 4;
257 let mut current_registers = self.query_registers()?;
258 tracing::debug!(
259 "Initial registers acquired Current PC: {:X?}",
260 current_registers.program_counter()
261 );
262
263 let cached_registers = current_registers.clone();
264 current_registers.set_stack_pointer(current_registers.stack_pointer() & !0xfusize);
265 for (i, param) in parameters[..std::cmp::min(parameters.len(), REGISTER_ARGUMENTS)]
266 .iter()
267 .enumerate()
268 {
269 let reg: u32 = (*param).try_into().unwrap();
270 current_registers.regs[i] = reg;
271 tracing::trace!("Applying register {i} with param {}", reg);
272 }
273
274 if parameters.len() > REGISTER_ARGUMENTS {
275 let stack_arguments = ¶meters[REGISTER_ARGUMENTS..];
276 tracing::trace!("Remaining stack arguments: {:?}", stack_arguments);
277
278 current_registers.set_stack_pointer(
280 current_registers.stack_pointer()
281 - (((stack_arguments.len() + 3) & !3usize) * size_of::<usize>()),
282 );
283
284 self.write_memory(
285 current_registers.stack_pointer(),
286 usize_arr_to_u8(stack_arguments).as_slice(),
287 )?;
288 };
289
290 current_registers.set_program_counter(func_address);
292 if (current_registers.program_counter() & 1) != 0 {
293 current_registers.set_program_counter(current_registers.program_counter() & !1);
294 current_registers
295 .set_cpsr((current_registers.cpsr() as u32 | (arch::CPSR_T_MASK)) as usize);
296 } else {
297 current_registers
298 .set_cpsr((current_registers.cpsr() as u32 & !(arch::CPSR_T_MASK)) as usize);
299 }
300 current_registers.set_lr(return_address);
301 tracing::trace!(
302 "Executing with PC: {:X?}, and arguments {parameters:?}",
303 func_address
304 );
305
306 self.set_registers(current_registers)?;
307 tracing::trace!("Registers successfully injected.");
308
309 let mut frame = self.step_cont()?;
310 let result_regs = frame.query_registers()?;
311 tracing::trace!("Result {result_regs:#?}");
312
313 frame.set_registers(cached_registers)?;
314 Ok((result_regs, frame))
315 }
316
317 #[cfg(target_arch = "x86")]
321 pub fn invoke_remote(
322 mut self,
323 func_address: usize,
324 return_address: usize,
325 parameters: &[usize],
326 ) -> TraceResult<(UserRegs, ProcessFrame)> {
327 use std::mem::size_of;
328
329 let mut current_registers = self.query_registers()?;
330 tracing::trace!(
331 "Initial registers acquired Current PC: {:X?}",
332 current_registers.program_counter()
333 );
334
335 let cached_registers = current_registers.clone();
336 let param_count = parameters.len();
337 current_registers.set_stack_pointer(current_registers.stack_pointer() & !0xfusize);
338
339 tracing::trace!("Function parameters: {:?}", parameters);
340
341 if param_count > 0 {
342 current_registers.set_stack_pointer(
344 current_registers.stack_pointer()
345 - (((param_count + 3) & !3usize) * size_of::<usize>()),
346 );
347 self.write_memory(
348 current_registers.stack_pointer(),
349 usize_arr_to_u8(parameters).as_slice(),
350 )?;
351 }
352
353 current_registers.set_stack_pointer(current_registers.stack_pointer() - size_of::<usize>());
355 self.write_memory(
356 current_registers.stack_pointer(),
357 &return_address.to_le_bytes(),
358 )?;
359
360 current_registers.eax = 0;
361 current_registers.orig_eax = 0;
362
363 current_registers.set_program_counter(func_address);
365 tracing::trace!(
366 "Executing with PC: {:X?}, and arguments {parameters:?}",
367 func_address
368 );
369
370 self.set_registers(current_registers)?;
371 tracing::trace!("Registers successfully injected.");
372
373 let mut frame = self.step_cont()?;
374 let result_regs = frame.query_registers()?;
375 tracing::trace!("Result {result_regs:#?}");
376
377 frame.set_registers(cached_registers)?;
378 Ok((result_regs, frame))
379 }
380
381 #[cfg(target_arch = "x86_64")]
385 pub fn invoke_remote(
386 mut self,
387 func_address: usize,
388 return_address: usize,
389 parameters: &[usize],
390 ) -> TraceResult<(UserRegs, ProcessFrame)> {
391 use std::mem::size_of;
392
393 const REGISTER_ARGUMENTS: usize = 6;
394 let mut current_registers = self.query_registers()?;
395 tracing::debug!(
396 "Initial registers acquired Current PC: {:X?}",
397 current_registers.program_counter()
398 );
399
400 let cached_registers = current_registers.clone();
401 let param_count = parameters.len();
402 current_registers.set_stack_pointer(current_registers.stack_pointer() & !0xfusize);
403
404 if param_count > 0 {
406 current_registers.rdi = parameters[0] as u64;
407 }
408 if param_count > 1 {
409 current_registers.rsi = parameters[1] as u64;
410 }
411 if param_count > 2 {
412 current_registers.rdx = parameters[2] as u64;
413 }
414 if param_count > 3 {
415 current_registers.rcx = parameters[3] as u64;
416 }
417 if param_count > 4 {
418 current_registers.r8 = parameters[4] as u64;
419 }
420 if param_count > 5 {
421 current_registers.r9 = parameters[5] as u64;
422 }
423
424 if parameters.len() > REGISTER_ARGUMENTS {
425 let stack_arguments = ¶meters[REGISTER_ARGUMENTS..];
426 tracing::trace!("Remaining stack arguments: {:?}", stack_arguments);
427
428 current_registers.set_stack_pointer(
430 current_registers.stack_pointer()
431 - (((stack_arguments.len() + 1) & !1usize) * size_of::<usize>()),
432 );
433 self.write_memory(
434 current_registers.stack_pointer(),
435 usize_arr_to_u8(stack_arguments).as_slice(),
436 )?;
437 };
438
439 current_registers.set_stack_pointer(current_registers.stack_pointer() - size_of::<usize>());
441 self.write_memory(
442 current_registers.stack_pointer(),
443 &return_address.to_le_bytes(),
444 )?;
445
446 current_registers.rax = 0;
447 current_registers.orig_rax = 0;
448
449 current_registers.set_program_counter(func_address);
451 tracing::trace!(
452 "Executing with PC: {:X?}, and arguments {parameters:?}",
453 func_address
454 );
455
456 self.set_registers(current_registers)?;
457 tracing::trace!("Registers successfully injected.");
458
459 let mut frame = self.step_cont()?;
460 let result_regs = frame.query_registers()?;
461 tracing::debug!("Result {result_regs:#?}");
462
463 frame.set_registers(cached_registers)?;
464 Ok((result_regs, frame))
465 }
466
467 pub fn read_memory(&mut self, addr: usize, len: usize) -> TraceResult<Vec<u8>> {
469 let mut data = vec![0; len];
470 let len_read = self.read_memory_mut(addr, &mut data)?;
471 data.truncate(len_read);
472 Ok(data)
473 }
474
475 pub fn read_memory_mut(&self, addr: usize, data: &mut [u8]) -> TraceResult<usize> {
477 use std::os::unix::fs::FileExt;
478 let mem = std::fs::File::open(self.process.proc_mem_path())?;
479 let len = mem.read_at(data, addr.try_into().unwrap())?;
480 Ok(len)
481 }
482
483 pub fn write_memory(&mut self, addr: usize, data: &[u8]) -> TraceResult<usize> {
485 use std::os::unix::fs::FileExt;
486 let mem = std::fs::OpenOptions::new()
487 .write(true)
488 .open(self.process.proc_mem_path())?;
489 let len = mem.write_at(data, addr.try_into().unwrap())?;
490 Ok(len)
491 }
492
493 fn step_cont(mut self) -> TraceResult<ProcessFrame> {
495 self.process.cont()?;
496 self.process.next_frame()
497 }
498}
499
500pub struct TracedProcess {
502 pid: pid_t,
503}
504
505impl Drop for TracedProcess {
508 fn drop(&mut self) {
509 let pid = self.pid();
510 match self.detach() {
511 Ok(()) => tracing::debug!("Successfully detached from Pid: {pid}"),
512 Err(e) => tracing::error!("Failed to detach from Pid: {pid}, {e:#?}"),
513 }
514 }
515}
516
517impl TracedProcess {
518 fn detach(&mut self) -> TraceResult<()> {
520 let result = unsafe { ptrace(PTRACE_DETACH, self.pid(), 0, 0) };
521 match result == -1 {
522 true => Err(TraceError::Ptrace(std::io::Error::last_os_error())),
523 false => Ok(()),
524 }
525 }
526
527 pub fn attach(pid: pid_t) -> TraceResult<Self> {
529 let result = unsafe { ptrace(PTRACE_ATTACH, pid, 0, 0) };
530 match result == -1 {
531 true => Err(TraceError::Ptrace(std::io::Error::last_os_error())),
532 false => Ok(Self { pid }),
533 }
534 }
535
536 pub fn pid(&self) -> pid_t {
538 self.pid
539 }
540
541 fn cont(&mut self) -> TraceResult<()> {
543 let result = unsafe { ptrace(PTRACE_CONT, self.pid(), 0, 0) };
544 match result == -1 {
545 true => Err(TraceError::Ptrace(std::io::Error::last_os_error())),
546 false => Ok(()),
547 }
548 }
549
550 fn wait(&mut self, options: WaitOptions) -> TraceResult<WaitStatus> {
552 let mut raw_status = 0;
553 let result = unsafe { libc::waitpid(self.pid, &mut raw_status, options.into()) };
554 if result == -1 {
555 return Err(TraceError::Ptrace(std::io::Error::last_os_error()));
556 }
557
558 let status = WaitStatus(raw_status);
559 tracing::trace!("wait status {status:?}");
560 Ok(status)
561 }
562
563 pub fn next_frame(mut self) -> TraceResult<ProcessFrame> {
565 let wait_status = self.wait(WaitOptions::Untraced)?;
566 if wait_status.is_exited() {
567 return Err(TraceError::General("Waiting stop received an exit signal"));
568 }
569
570 Ok(ProcessFrame { process: self })
571 }
572
573 fn proc_mem_path(&self) -> String {
575 format!("/proc/{}/mem", self.pid)
576 }
577}