inject_lib/lib.rs
1//!This Crate Provides functionality, for injecting dlls into other processes.
2//!Most of the crate is right now accessible through the [Injector] class.
3//!
4//!You will need to provide a pid, and a dll to inject. This crate will do the rest for you.
5//!
6//! The main focus will always be on performing the injection reliable.
7//! If you care about injecting into a 64 bit application whilst needing to compile this library under 32 bits, you will want to enable the "x86tox64" feature.
8//! Be aware, that that feature uses "unofficial" api's located in ntdll.dll.
9//! Compatibility is technically not guaranteed by windows.
10//!
11//!If you have any suggestions, on improving the outfacing api of this crate create an issue, or pr.
12//!I am not sure yet, if I like this design.
13//!
14//!Linux support will probably not come.
15//!It is insanely hard and platform specific, because
16//! 1. we would need to write raw machinecode/shellcode to the target process.
17//! 3. which then has the necessary code to load the .so
18//! 4. we need to somehow redirect the target program's execution, to execute our code
19//! 5. we need to do that, without somehow disrupting ANY of the program's code
20//! 6. we need to return the EXACT state before we did anything, because the other program may need that
21//!
22//! If this library is supposed to be helpful I'd want to not require to run it as root.
23//! Unfortunately some steps involve calling ptrace. Access to the command is restricted, if you are not the parent process of the process you are trying to trace.
24//! These requirements would mean, that we can only inject so files to processes, that the program this library itself created.
25// #![feature(strict_provenance)]
26// #![warn(lossy_provenance_casts)]
27#![warn(missing_docs)]
28#![cfg_attr(not(feature = "std"), no_std)]
29#[cfg(feature = "alloc")]
30extern crate alloc;
31#[cfg(not(feature = "alloc"))]
32compile_error!("inject_lib doesn't yet support no alloc environments");
33extern crate core;
34
35use core::fmt::{Display, Formatter};
36
37///This struct will expose certain module private functions, to actually use the api.
38///The exact contents should be considered implementation detail.
39#[derive(Debug, Clone)]
40#[non_exhaustive]
41pub struct Injector<'a> {
42 ///The path to a dll. This may be in any format, that rust understands
43 pub dll: Data<'a>,
44 ///The pid the dll should be injected into
45 pub pid: u32,
46}
47
48pub(crate) type Result<T, V = error::Error> = core::result::Result<T, V>;
49pub(crate) use log::{debug, error, info, trace, warn};
50
51///Holds all error types
52pub mod error;
53mod platforms;
54///This represents the actions, that are supported with a dll.
55pub trait Inject {
56 ///Injects a dll
57 fn inject(&self) -> Result<()>;
58 ///Ejects a dll
59 fn eject(&self) -> Result<()>;
60 ///This Function will find all currently processes, with a given name.
61 ///Even if no processes are found, an empty Vector should return.
62 fn find_pid(name: Data) -> Result<alloc::vec::Vec<u32>>;
63}
64///Data can be a Path(if we have std), or a String.
65///Data will get handled differently in no_std and std scenarios
66#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
67pub enum Data<'a> {
68 ///This a Path as a String
69 Str(&'a str),
70 ///This is a Path encoded as a Path std object
71 #[cfg(feature = "std")]
72 Path(&'a std::path::Path),
73}
74impl<'a> Display for Data<'a> {
75 fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
76 match self {
77 Data::Str(s) => write!(f, "{}", s),
78 #[cfg(feature = "std")]
79 Data::Path(p) => write!(f, "{}", p.to_string_lossy()),
80 }
81 }
82}
83impl<'a> Data<'a> {
84 ///Gets the contained Str variant, if contained data is a string.
85 ///Note: On no-std, this should always be SOME, because there is no other variant on no-std.
86 #[allow(unused)]
87 pub fn get_str(&self) -> Option<&'a str> {
88 match self {
89 Data::Str(a) => Some(a),
90 _ => None,
91 }
92 }
93 #[cfg(feature = "std")]
94 #[allow(unused)]
95 ///This gets the Path variant, if the contained data is a string.
96 fn get_path(&self) -> Option<&'a std::path::Path> {
97 match self {
98 Data::Path(a) => Some(a),
99 _ => None,
100 }
101 }
102}
103
104impl<'a> Injector<'a> {
105 ///Create a new Injector object.
106 pub fn new(dll: Data<'a>, pid: u32) -> Self {
107 Injector { dll, pid }
108 }
109 ///Sets the dll
110 pub fn set_dll(&mut self, dll: Data<'a>) {
111 self.dll = dll;
112 }
113 ///Sets the pid
114 pub fn set_pid(&mut self, pid: u32) {
115 self.pid = pid;
116 }
117 #[cfg(target_family = "windows")]
118 ///Gets the Platform specific Injector.
119 ///Currently only windows is supported.
120 ///wait indicates, if we should wait on the dll to attach to the process
121 pub fn inject(&self, wait: bool) -> impl Inject + '_ {
122 platforms::windows::InjectWin { inj: self, wait }
123 }
124 #[cfg(target_family = "windows")]
125 ///This Function will find all currently processes, with a given name.
126 ///Even if no processes are found, an empty Vector should return.
127 pub fn find_pid(name: Data) -> Result<alloc::vec::Vec<u32>> {
128 platforms::windows::InjectWin::find_pid(name)
129 }
130}
131
132impl<'a> Default for Injector<'a> {
133 fn default() -> Self {
134 Self::new(crate::Data::Str(""), 0)
135 }
136}
137// #[cfg(feature = "std")]
138// ///This takes a string, and returns only the last path element
139// ///Since this uses rust builtins, it should "just work".
140// pub fn strip_path(dll: &str) -> Result<String> {
141// let pb = std::path::PathBuf::from(dll);
142// match pb.file_name().and_then(|x| x.to_str()) {
143// None => Err(error::Error::Io(std::io::Error::from(
144// std::io::ErrorKind::Unsupported,
145// ))),
146// Some(v) => Ok(v.to_string()),
147// }
148// }
149
150///This truncates all 0 from the end of a Vec
151///This will keep other 0 entries in the Vec perfectly intact.
152///This has a worst case performance of o(n).
153///if fast==true, the data MUST only contain NULL-values at the end of the string O(log n)
154///else O(n)
155pub fn trim_wide_str<const FAST: bool>(v: &[u16]) -> &[u16] {
156 let i = {
157 if FAST {
158 v.partition_point(|x| *x != 0)
159 } else {
160 let mut len = v.len();
161 while v[len - 1] == 0 {
162 len -= 1;
163 }
164 len
165 }
166 };
167 let (out, _) = v.split_at(i);
168 return out;
169}
170
171///This function builds a String, from a WTF-encoded buffer.
172pub fn str_from_wide_str(v: &[u16]) -> Result<alloc::string::String> {
173 let tmp: alloc::vec::Vec<Result<char, widestring::error::DecodeUtf16Error>> =
174 widestring::decode_utf16(v.iter().map(|x| *x)).collect();
175 let mut o = alloc::string::String::with_capacity(v.len());
176 for i in tmp {
177 match i {
178 Err(e) => return Err(crate::error::Error::WTFConvert(e)),
179 Ok(v) => o.push(v),
180 }
181 }
182 o.shrink_to_fit();
183 Ok(o)
184}
185
186///Returns a function, which compares [crate::Data] against some other [crate::Data].
187///If in no_std enviromenmt, the comparison is affected by forward-slash vs back-slash
188//todo: make the second function call better
189fn cmp<'a>(name: crate::Data<'a>) -> impl Fn(crate::Data<'_>) -> bool + 'a {
190 move |s| {
191 return match name {
192 crate::Data::Str(s2) => match s {
193 crate::Data::Str(s) => s2.ends_with(s) || s.ends_with(s2),
194 #[cfg(feature = "std")]
195 crate::Data::Path(p) => {
196 let p1 = std::path::Path::new(s2);
197 p1.ends_with(p) || p.ends_with(p1)
198 }
199 },
200 #[cfg(feature = "std")]
201 crate::Data::Path(p2) => match s {
202 crate::Data::Str(s) => {
203 let p1 = std::path::Path::new(s);
204 p1.ends_with(p2) || p2.ends_with(p1)
205 }
206 #[cfg(feature = "std")]
207 crate::Data::Path(p) => p.ends_with(p2) || p2.ends_with(p),
208 },
209 };
210 }
211}
212
213#[cfg(test)]
214mod test {
215 use alloc::vec::Vec;
216 ///This string contains a bunch of special chars, to test methods operating on strings.
217 pub const STR:&str = "This is just any string, since we are not testing anything else, other than setting the dll.!'\r\n\t%$§\"{\\[()]}=?´`öäü^°,.-;:_#+*~<>|³²@";
218
219 use crate::Result;
220 #[test]
221 fn trim_vec() {
222 let buf: Vec<u16> = (1..u16::MAX).collect();
223 let mut buf2 = buf.clone();
224 buf2.append(&mut [0u16; 100].to_vec());
225
226 assert_eq!(super::trim_wide_str::<true>(buf2.as_slice()), buf);
227 assert_eq!(super::trim_wide_str::<false>(buf2.as_slice()), buf);
228 }
229
230 // #[test]
231 // fn strip_path() -> Result<()> {
232 // #[cfg(target_family = "windows")]
233 // assert_eq!(
234 // super::strip_path("C:\\this\\is\\a\\test\\path\\with\\a\\dir\\at\\the\\end\\")?,
235 // "end",
236 // "strip path failed to strip the end of a win path, with a dir at the end"
237 // );
238 // assert_eq!(
239 // super::strip_path("/this/is/a/test/path/with/a/dir/at/the/end/")?,
240 // "end",
241 // "strip path failed to strip the end of a rust path, with a dir at the end"
242 // );
243 // #[cfg(target_family = "windows")]
244 // assert_eq!(super::strip_path("C:\\this\\is\\a\\test\\path\\with\\a\\dir\\at\\the\\end")?,"end","strip path failed to strip the end of a win path, with a dir/extensionless file at the end");
245 // assert_eq!(super::strip_path("/this/is/a/test/path/with/a/dir/at/the/end")?,"end","strip path failed to strip the end of a rust path, with a dir/extensionless file at the end");
246 // #[cfg(target_family = "windows")]
247 // assert_eq!(
248 // super::strip_path(
249 // "C:\\this\\is\\a\\test\\path\\with\\a\\file\\at\\the\\end\\file.txt"
250 // )?,
251 // "file.txt",
252 // "strip path failed to strip the end of a win path, with a file at the end"
253 // );
254 // assert_eq!(
255 // super::strip_path("/this/is/a/test/path/with/a/file/at/the/end/file.txt")?,
256 // "file.txt",
257 // "strip path failed to strip the end of a rust path, with a file at the end"
258 // );
259 // Ok(())
260 // }
261
262 #[test]
263 fn set_dll() {
264 let mut inj = super::Injector::default();
265 let dll = crate::Data::Str(STR);
266 inj.set_dll(dll);
267 assert_eq!(inj.dll, dll, "Setter did not correctly set the dll string");
268 }
269 #[test]
270 fn set_pid() {
271 let mut inj = super::Injector::default();
272 const PID: u32 = 0;
273 inj.set_pid(PID);
274 assert_eq!(inj.pid, PID, "Setter did not correctly set the PID");
275 }
276
277 #[test]
278 #[cfg(target_os = "windows")]
279 fn cmp() {
280 simple_logger::SimpleLogger::new().init().ok();
281 //Simple case
282 {
283 let f = super::cmp(crate::Data::Str("test"));
284 assert!(f(crate::Data::Str("test")));
285 assert!(f(crate::Data::Str("something test")));
286 assert!(!f(crate::Data::Str("something 1351")));
287 let f = super::cmp(crate::Data::Str("KERNEL32.DLL"));
288 assert!(f(crate::Data::Str("C:\\Windows\\System32\\KERNEL32.DLL")));
289 let f = super::cmp(crate::Data::Str("ntdll.dll"));
290 assert!(f(crate::Data::Str("C:\\Windows\\SYSTEM32\\ntdll.dll")));
291 }
292 //complicated paths
293 #[cfg(feature = "std")]
294 {
295 let f = std::vec![
296 super::cmp(crate::Data::Path(std::path::Path::new(
297 r"C:\this\is\a\test\path\with\a\dir\at\the\end\"
298 ))),
299 super::cmp(crate::Data::Path(std::path::Path::new(
300 r"C:\this\is\a\test\path\with\a\dir\at\the\end"
301 ))),
302 super::cmp(crate::Data::Path(std::path::Path::new(
303 "C:/this/is/a/test/path/with/a/dir/at/the/end/"
304 ))),
305 super::cmp(crate::Data::Path(std::path::Path::new(
306 "C:/this/is/a/test/path/with/a/dir/at/the/end"
307 ))),
308 ];
309 for f in f {
310 assert!(f(crate::Data::Str("end")));
311 assert!(f(crate::Data::Str("the\\end")));
312 assert!(f(crate::Data::Str("the/end")));
313 assert!(f(crate::Data::Str("at/the\\end")));
314 assert!(f(crate::Data::Str("at\\the/end")));
315 assert!(f(crate::Data::Path(std::path::Path::new("end"))));
316 assert!(f(crate::Data::Path(std::path::Path::new("the\\end"))));
317 assert!(f(crate::Data::Path(std::path::Path::new("the/end"))));
318 assert!(f(crate::Data::Path(std::path::Path::new("at/the\\end"))));
319 assert!(f(crate::Data::Path(std::path::Path::new("at\\the/end"))));
320 }
321 }
322 {
323 let f = super::cmp(crate::Data::Str(
324 r"C:\this\is\a\test\path\with\a\dir\at\the\end\",
325 ));
326 assert!(!f(crate::Data::Str("end")));
327 assert!(!f(crate::Data::Str(r"the\end")));
328 assert!(f(crate::Data::Str(r"end\")));
329 let f = super::cmp(crate::Data::Str(
330 r"C:\this\is\a\test\path\with\a\dir\at\the\end",
331 ));
332 assert!(f(crate::Data::Str("end")));
333 assert!(f(crate::Data::Str(r"the\end")));
334 assert!(!f(crate::Data::Str(r"end\")));
335 assert!(!f(crate::Data::Str(r"the\end\")));
336 let f = super::cmp(crate::Data::Str(
337 "C:/this/is/a/test/path/with/a/dir/at/the/end/",
338 ));
339 assert!(f(crate::Data::Str("end/")));
340 assert!(f(crate::Data::Str("the/end/")));
341 assert!(!f(crate::Data::Str("end")));
342 assert!(!f(crate::Data::Str("the/end")));
343 let f = super::cmp(crate::Data::Str(
344 "C:/this/is/a/test/path/with/a/dir/at/the/end",
345 ));
346 assert!(f(crate::Data::Str("end")));
347 assert!(!f(crate::Data::Str("end/")));
348 assert!(!f(crate::Data::Str("the/end/")));
349 }
350 }
351}