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
//! Steroid is a dynamic binary instrumentation library with a strong emphasis on safety.
//!
//! This library aims at instrumenting other running processes, giving the user a high-level
//! interface to system calls such as [`ptrace(2)`] and the possibilities it gives. Steroid uses
//! lifetimes and mutability a lot in order to make sense of a remote process' running
//! state. The two main data types are [`TargetProcess`] and [`TargetController`].
//!
//! # The Process and Controller tandem
//!
//! The former represents a running process. An instance of this type is normally mutably available
//! while a process in running. This means the process can be stopped. Once a process is stopped
//! using [`wait`], a [`RunningState`] is returned. This object tells the user whether the process
//! is stopped but still alive or if it is dead because it has exited or was killed.
//!
//! ```
//! # use anyhow::Error;
//! # use steroid::process::spawn_process;
//! # use steroid::run::{Executing, RunningState};
//! # let process = spawn_process("/bin/ls", ["-l"])?;
//! match process.wait()? {
//! RunningState::Alive(_) => println!("We can manipulate the stopped process"),
//! RunningState::Exited { reason, .. } => println!("The process has exited: {}", reason),
//! }
//! # Ok::<(), Error>(())
//! ```
//!
//! If the process is just stopped, the user is provided with a [`TargetController`]. This
//! controller takes the ownership of the process, forbiding any manipulation of the
//! [`TargetProcess`] that requires the process running. On the other hand, the controller enables
//! any manipulation that requires a stopped process, such as reading the process' registers. The
//! method [`resume`] of the controller consumes the latter and returns the [`TargetProcess`],
//! allowing the process to be manipulated again. A steroid client usually consists of alternating
//! manipulations of a process and its successive controllers.
//!
//! ```
//! # use anyhow::Error;
//! use steroid::process::spawn_process;
//! use steroid::run::{Executing, RunningState, Reason};
//!
//! let process = spawn_process("/bin/ls", ["-l"])?;
//! if let RunningState::Alive(mut ctrl) = process.wait()? {
//! // The process is now inaccessible as mutable.
//! let regs = ctrl.get_registers()?;
//! println!("{:#x?}", regs);
//! let process = ctrl.resume()?;
//! // The controller is dead, the process is available again.
//! let pid = process.pid();
//! // The wait method returns the process state:
//! // * alive (with a controller)
//! // * dead (with the reason why it died)
//! let state = process.wait()?;
//! assert!(state.has_exited());
//! }
//! # Ok::<(), Error>(())
//! ```
//!
//! In some cases, the user knows that when calling [`wait`], the process must be stopped and an
//! exit must be considered an error. Instead of using `match` and writing complicated code,
//! [`RunningState`] has a method [`assume_alive`] that returns a [`TargetController`] if the
//! process is indeed alive or fails with an error if it has exited.
//!
//! ```
//! # use anyhow::Error;
//! use steroid::process::spawn_process;
//! use steroid::run::Executing;
//!
//! let process = spawn_process("/bin/ls", ["-l"])?;
//! let mut ctrl = process.wait()?.assume_alive()?;
//! // The process is now inaccessible as mutable.
//! let regs = ctrl.get_registers()?;
//! println!("{:#x?}", regs);
//! # Ok::<(), Error>(())
//! ```
//!
//! ## `ptrace` implies `!Send + !Sync`
//!
//! Steroid heavily uses the system call [`ptrace(2)`] to control remote processes. It is important
//! to understand that a tracee is traced by a tracer thread. This means that in a steroid client,
//! only the thread that created a [`TargetProcess`], a [`Thread`] or a [`TargetController`] can
//! actually use them. For this reason, these three types are marked as both [`!Send`][Send] and
//! [`!Sync`][Sync].
//!
//! # Remote system calls
//!
//! Steroid has a whole module dedicated to call syscalls from the remote process it is
//! controlling. The insight is to take advantage of the process being stopped at some point to make
//! it execute pieces of code in its own context. For instance, it would be possible to make the
//! remote process allocate memory for future use by the steroid client:
//!
//! ```
//! # use std::path::PathBuf;
//! # use anyhow::Error;
//! use nix::libc::{MAP_ANONYMOUS, MAP_PRIVATE, PROT_READ, PROT_WRITE};
//! # use nix::sys::wait::WaitStatus;
//! # use steroid::process::spawn_process;
//! # use steroid::run::{Executing, Reason};
//! # use steroid::breakpoint::{breakpoint, Mode};
//! use steroid::syscall;
//!
//! # let mut path_buf = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
//! # path_buf.push("resources/test/say_hello_no_pie");
//! # let process = spawn_process::<_, _, &str>(path_buf, vec![])?;
//! # let mut ctrl_start = process.wait()?.assume_alive()?;
//! # breakpoint(&mut ctrl_start, 0x401126, Mode::OneShot)?;
//! # let process = ctrl_start.resume()?;
//! let mut ctrl = process.wait()?.assume_alive()?;
//! let flags = (MAP_PRIVATE | MAP_ANONYMOUS) as u64;
//! let prot = (PROT_READ | PROT_WRITE) as u64; // permissions: read and write
//! let size = 1000;
//! let fd = -1_i64 as u64; // fd must be -1, but syscall::mmap takes u64. It's ok here.
//! let address = syscall::mmap(&mut ctrl, 0, size, prot, flags, fd, 0)?;
//! println!("New page mapped at address {:#x}", address);
//!
//! let process = ctrl.resume()?;
//! # let pid = process.pid();
//! # let state = process.wait()?;
//! # assert!(state.has_exited(), "{}", state.reason());
//! # Ok::<(), Error>(())
//! ```
//!
//! # Limitations and useful crates
//!
//! Steroid is a dynamic binary instrumentation library that relies heavily on Linux features such
//! as [`ptrace(2)`] and [`procfs(5)`]. Therefore, it will only work on Linux. Some efforts can be
//! made to port it to Unix operating systems such as the main BSDs. In addition to that, steroid
//! only targets `x86_64`. It may be possible to port it to different architectures but it is far
//! from being a priority right now.
//!
//! Steroid is still very early in its development and does not provide many features. Nonetheless
//! some excellent crates complete it very well, giving missing features that fall out of the strict
//! scope of steroid's goals:
//!
//! - [`elf`]: crate providing a safe interface to read ELF object files
//! - [`gimli`]: crate providing features to read and write DWARF debugging format
//! - [`capstone`]: bindings to the capstone library disassembly framework
//!
//! [`ptrace(2)`]: https://man7.org/linux/man-pages/man2/ptrace.2.html
//! [`procfs(5)`]: https://man7.org/linux/man-pages/man5/proc.5.html
//! [`TargetProcess`]: process/struct.TargetProcess.html
//! [`TargetController`]: process/struct.TargetController.html
//! [`Thread`]: thread/struct.Thread.html
//! [`RunningState`]: process/enum.RunningState.html
//! [`assume_alive`]: process/enum.RunningState.html#method.assume_alive
//! [`wait`]: process/struct.TargetProcess.html#method.wait
//! [`resume`]: process/struct.TargetController.html#method.resume
//! [`elf`]: https://docs.rs/elf
//! [`gimli`]: https://docs.rs/gimli
//! [`capstone`]: https://docs.rs/capstone