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}